转载

手写源码(一):自己实现Spring事务

Spring事务分为声明式事务(注解或包扫描)和编程式(在代码里提交或回滚)事务,声明式事务就是在编程式事务的基础上加上AOP计数进行包装

这个工程为了实验事务的回滚,使用用了数据库,使用了jdbc模板连接数据库 ,数据库连接池配置再RootConfig里

我导入的Maven依赖如下

<dependencies>
        <!-- 引入Spring-AOP等相关Jar -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>4.3.20.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>4.3.20.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>4.3.20.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aop</artifactId>
            <version>4.3.20.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-orm</artifactId>
            <version>4.3.20.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjrt</artifactId>
            <version>1.6.1</version>
        </dependency>
        <dependency>
            <groupId>aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.5.3</version>
        </dependency>
        <dependency>
            <groupId>cglib</groupId>
            <artifactId>cglib</artifactId>
            <version>2.1_2</version>
        </dependency>
        <!--mysql连接驱动-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.13</version>
        </dependency>
        <!--连接池-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.1.10</version>
        </dependency>
        <!--测试-->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>
    </dependencies>

复制代码

配置类如下,用于代替有些过时的XML配置Spring

@Configuration
@ComponentScan(basePackages = {"com.libi"})
@EnableAspectJAutoProxy
public class RootConfig {
    @Bean
    public DataSource dataSource() {
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setUrl("jdbc:mysql://localhost:3306/sms?userSSL=true&useUnicode=true&characterEncoding=UTF8&serverTimezone=GMT");
        dataSource.setUsername("root");
        dataSource.setPassword("root");
        dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
        return dataSource;
    }

    @Bean
    public JdbcTemplate jdbcTemplate(DataSource dataSource) {
        JdbcTemplate jdbcTemplate = new JdbcTemplate();
        jdbcTemplate.setDataSource(dataSource);
        return jdbcTemplate;
    }

    @Bean
    public DataSourceTransactionManager dataSourceTransactionManager(DataSource dataSource) {
        DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager();
        dataSourceTransactionManager.setDataSource(dataSource);
        return dataSourceTransactionManager;
    }
}
复制代码

需要加入事务的方法如下userDao是会操作数据的,在中间的间隔会抛出异常

@Service
 public class UserServiceImpl implements UserService {
     @Autowired
     private UserDao userDao;
     public void add() {
         userDao.add("test001","1233321");
         System.out.println("中间的间隔,且出现异常");
         int i = 1 / 0;
         userDao.add("test002","135365987");
     }
 }
复制代码

这时只会插入test001的语句,test002不会插入成功。

编程式事务

这时我们封装一个事务工具

@Component
@Scope("prototype")
public class TransactionUtils {
    @Autowired
    private DataSourceTransactionManager dataSourceTransactionManager;

    private TransactionStatus status;
    
    /** 开启事务*/
    public TransactionStatus begin() {
        //使用默认的传播级别
        TransactionStatus transaction = dataSourceTransactionManager.getTransaction(new DefaultTransactionAttribute());
        return transaction;
    }

    /** 提交事务 需要传入这个事务状态*/
    public void commit() {
        dataSourceTransactionManager.commit(status);
    }

    /**回滚事务 需要传入这个事务状态*/
    public void rollBack() {
        //获取当前事务,如果有,就回滚
        if (status != null) {
            dataSourceTransactionManager.rollback(status);
        }
    }
}
复制代码

再这样使用,修改add方法

public void add() {
        TransactionStatus begin = null;
        try {
            begin = transactionUtils.begin();
            userDao.add("test001", "1233321");
            System.out.println("中间的间隔,且出现异常");
            int i = 1 / 0;
            userDao.add("test002", "135365987");
            transactionUtils.commit();
        } catch (Exception e) {
            e.printStackTrace();
            transactionUtils.rollBack();
        }
    }
复制代码

声明式事务

我们使用AOP编程把刚刚的事务工具封装一下

@Component
@Aspect
public class AopTransaction {
    @Autowired
    private TransactionUtils transactionUtils;

    @Around("execution(* com.libi.service.UserService.add(..))")
    public void around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        System.out.println("开启事务");
        proceedingJoinPoint.proceed();
        System.out.println("提交事务");
        transactionUtils.commit();
    }

    @AfterThrowing("execution(* com.libi.service.UserService.add(..))")
    public void afterThrowing() {
        System.out.println("回滚事务");
        //获取当前事务,直接回滚
        TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
    }
    
}
复制代码

然后清空原来的方法里所有的try代码,让他回到最初的状态( 不能捕获异常,否者出现异常后不能被异常通知捕获到,导致事务不生效

注解式事务

在Spring里已经帮我们实现类注解事务,需要在配置类里添加下面的注解来开启注解事务的支持

@EnableTransactionManagement
复制代码

然后 注释掉我们上次的AOP注解 ,使用 @Transactional(rollbackFor = Exception.class) 的注解开启这个方法的事务, rollbackFor 标识需要回滚的异常类,整个方法如下

@Transactional(rollbackFor = Exception.class)
    public void add() {
        userDao.add("test001", "1233321");
        System.out.println("中间的间隔,且出现异常");
        int i = 1 / 0;
        userDao.add("test002", "135365987");
    }
复制代码

这样也可以实现这个方法的事务。 当然,这个方法里也不能捕获异常,这样仍然会导致无法触发异常通知而导致事务无效

我们就以这种效果作为模板手写事务的框架

具体步骤

  • 定义注解
/**
 * @author libi
 * 自己实现的事务注解
 */
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface ExtTransaction {
    
}
复制代码
  • 封装手动事务(使用原来的TransactionUtils类)
  • 使用AOP扫描规定包下的注解
    • 在AOP上封装找到注解并且加上注解的操作
@Component
@Aspect
public class AopAnnotationTransaction {
    @Autowired
    private TransactionUtils transactionUtils;
    /**这边规定扫描service下的所有方法*/
    @Around("execution(* com.libi.service.*.*(..))")
        //获取方法上的注解,这里把获取注解的方法单独提出来了
        ExtTransaction extTransaction = getExtTransaction(proceedingJoinPoint);

        TransactionStatus status = null;
        if (extTransaction != null) {
            //若果有事务,开启事务
            System.out.println("开启事务");
            status = transactionUtils.begin();
        }
        //调用代理目标方法
        proceedingJoinPoint.proceed();
        if (status != null) {
            //提交事务
            System.out.println("提交事务");
            transactionUtils.commit();
        }
    }

    /**事务的异常通知*/
    @AfterThrowing("execution(* com.libi.service.*.*.*(..))")
    public void afterThrowing() {
        System.out.println("回滚事务");
       transactionUtils.rollBack();
    }

    /**获取方法上的注解*/
    private ExtTransaction getExtTransaction(ProceedingJoinPoint proceedingJoinPoint) throws NoSuchMethodException {
        //获取代理对象的方法
        String methodName = proceedingJoinPoint.getSignature().getName();
        Class<?> targetClass = proceedingJoinPoint.getTarget().getClass();
        Class[] parameterTypes = ((MethodSignature) (proceedingJoinPoint.getSignature())).getParameterTypes();
        Method targetMethod = targetClass.getMethod(methodName, parameterTypes);
        //获取方法上的注解
        return targetMethod.getAnnotation(ExtTransaction.class);
    }
}
复制代码

还要注意的是,TransactionUtils类仍然需要时多例的,不然会出现线程安全问题

事务传播行为

  • 什么是传播行为(Propagation) :事务的传播行为产生在调用事务中,也就是说当小个事务嵌套在大事务里时,会发生怎样的行为
  • 传播行为的种类
    • PROPAGATION_REQUIRED—如果当前有事务,就用当前事务,如果当前没有事务,就新建一个事务。这是最常见的选择。( 如果大的方法有事务,那么需要事务的小方法就加入到这个事务里去,如果大方法没有事务,就创建事务
    • PROPAGATION_SUPPORTS--支持当前事务,如果当前没有事务,就以非事务方式执行。//( 如果外层方法没有事务,就会以非事务进行执行。这样相当于默认没有事务
    • PROPAGATION_MANDATORY--支持当前事务,如果当前没有事务,就抛出异常。
    • PROPAGATION_REQUIRES_NEW--新建事务,如果当前存在事务,把当前事务挂起( 互不影响,运行到小事务时暂停大事务 )。
    • PROPAGATION_NOT_SUPPORTED--以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
    • --- 如果当前有事务,就是以非事务进行执行
    • PROPAGATION_NEVER--以非事务方式执行,如果当前存在事务,则抛出异常。
原文  https://juejin.im/post/5d1216bf6fb9a07ee63f73e2
正文到此结束
Loading...