美文网首页SpringFramework
手写源码(一):自己实现Spring事务

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

作者: 団长大人 | 来源:发表于2019-03-23 15:04 被阅读0次

    手写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--以非事务方式执行,如果当前存在事务,则抛出异常。

    相关文章

      网友评论

        本文标题:手写源码(一):自己实现Spring事务

        本文链接:https://www.haomeiwen.com/subject/bwjsvqtx.html