美文网首页
Spring事务的传播特性引发的一场血案

Spring事务的传播特性引发的一场血案

作者: _小毛驴 | 来源:发表于2020-12-22 13:21 被阅读0次

    Spring事务的传播特性是对于Spring事务管理的一项特殊配置;
    Spring事务基于Spring AOP特性,是AOP的特殊场景处理,由于事务管理很强的通用性,所以,Spring把它抽离出来,做一个特殊化的配置,包括一些关于事务参数的配置。但是本质上,配置事务和配置AOP没有区别;
    Spring AOP特性又基于JDK动态代理。

    所以,配置事务的传播参数,就要受到底层技术动态代理的影响了。

    缘起

    很早开始学习SSM框架整合的时候,就不得不学到事务的配置。而事务的配置里传播特性参数,大多数老师讲解都是只讲一些理论的知识,没有进行实际场景的演示。讲解的过程中,还有很多错漏的地方。网络上的文章,也大多只是搬运一些理论,表示自己完全理解掌握了。
    在这样的背景下,我就特别想尝试,把事务传播特性这个抽象的理论扎实的在实际场景中应用起来。但是,昨天一整天,我竟然掉在这个巨大无比的坑里。本身自己对于传播特性的概念感觉抽象不可理解,加上自己动手实践过程中,踩进了底层动态代理局限性带来的坑中,浪费了一天时间,而且还异常难熬。
    今天,我要是不做一个复盘和记录,就实在是中了无妄之灾。

    踩坑一

    image.png
    上面这是从Spring官网的Using @Transactional这个章节的截图。翻译过来就是:
    在代理模式(这是默认模式)中,只有通过代理调用的外部方法会被拦截。这意味着自调用(实际上,目标对象中的方法调用目标对象的另一个方法)不会在运行时导致实际的事务,即使调用的方法被标记为@Transactional。另外,代理必须完全初始化以提供预期的行为,因此您不应该在初始化代码(即@PostConstruct)中依赖此特性。
    说的已经很明确了,即使在方法上添加了@Transactional注解,但是,是在类内部相互调用,那么事务也是不会起作用的。
    我翻看了官网,在Spring开始支持@Transactional注解之后,第二版,就把这个问题说到了。但是,也拦不住那么多的人踩坑啊。网上很多人还在看堆栈日志信息,也有通过伪代码原理分析的,无一不是血的代价,无一不是大量时间的消耗,无一不是没有认真看官网的详细文档,无一不是对Spring AOP特性没有深刻的理解。
    如果对于AOP有一个深刻的理解的话,在Spring AOP文档章节也有同样的注意事项。
    如果对于AOP的底层实现有一个清醒的认识,也不会犯这样的错误。AOP底层技术是动态代理。而动态代理,只能代理到方法级别。请看下面场景:
    @Service
    public class UserService {
        public void a() {
            this.b();
        }
        @Transactional
        public void b() {
            
        }
    }
    

    a()方法调用b()事务方法。
    问题:b()事务方法是否开启呢?答案:不会开启。
    因为此时@Transactional这个注解发挥作用最底层是动态代理技术,也就是只有代理对象调用这个方法的时候,才能够对这个方法做事务增强。而这里的this指向的是被代理对象。如果直接从外部调用b()方法,我们可以看到Spring帮我们注入的是动态生成的代理对象,所以可以做到事务增强。

    踩坑二

    为了图方便,在测试事务传播特性的时候,两个事务方法,我都使用了相同的数据库操作方法,如果这个操作恰好又是修改的话,那么数据库就会产生行锁,导致测试又一次失败。

    总结

    如果想要又快又好的做事务传播配置的测试,那么需要注意:

    1. 必须把事务方法定义在不同的service类中,这样做本身也是很符合事务传播要解决的业务场景;
    2. 不同service类要操作的数据库表应该不同,这也是符合业务场景需求。
      总得来说,要使用相关的配置,就应该了解这个配置设计出来是为了解决在什么样的业务场景下,什么样的问题。而我们在实际的测试过程中,往往图省事,就简化了很多的操作,这样反而得不偿失,付出血的代价!
    • 项目结构:


      image.png
    • controller层
    public class SaveMoneyController {
    
        public static void main(String[] args) {
             ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext(
                    "cn/keqing/tx-propagation.xml");
             AccountService accountService = context.getBean(AccountService.class);
             accountService.insertAccount();
        }
    }
    
    • service层
    @Service
    public class AccountService {
    
        @Autowired
        private AccountDao accountDao;
        
        @Autowired
        private BankService bankService;
        
    //  @Transactional
        public void insertAccount() {
            accountDao.insert();
            bankService.insertBank();
    //      throw new RuntimeException();
        }
    }
    
    @Service
    public class BankService {
    
        @Autowired
        private BankDao bankDao;
        
        @Transactional(propagation = Propagation.MANDATORY)
        public void insertBank() {
            bankDao.insert();
            
        }
    }
    
    • dao层
    @Repository
    public class AccountDao {
    
        @Autowired
        private JdbcTemplate jdbcTemplate;
        
        public void insert() {
            String sql = "insert into account(name,money) values(?,?)";
            jdbcTemplate.update(sql, new Date(),Math.random());
        }
    }
    
    @Repository
    public class BankDao {
    
        @Autowired
        private JdbcTemplate jdbcTemplate;
        
        public void insert() {
            String sql = "insert into bank(bank_name) values(?)";
            jdbcTemplate.update(sql, new Date());
        }
    }
    
    • spring配置文件
    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xmlns:context="http://www.springframework.org/schema/context"
           xmlns:aop="http://www.springframework.org/schema/aop"
           xmlns:tx="http://www.springframework.org/schema/tx"
           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
                            http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
                            http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
                            http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
    
        <!-- 组件扫描 -->
        <context:component-scan base-package="cn.keqing"></context:component-scan>
    
        <!-- 数据库连接池 -->
        <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"
              destroy-method="close">
            <property name="url" value="jdbc:mysql://10.72.49.169:3306/test?characterEncoding=UTF-8&amp;serverTimezone=GMT" />
            <property name="username" value="root" />
            <property name="password" value="111111" />
            <property name="driverClassName" value="com.mysql.cj.jdbc.Driver" />
        </bean>
    
        <!-- JdbcTemplate对象 -->
        <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
            <!--注入dataSource-->
            <property name="dataSource" ref="dataSource"></property>
        </bean>
    
        <!--创建事务管理器-->
        <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
            <!--注入数据源-->
            <property name="dataSource" ref="dataSource"></property>
        </bean>
        <!--开启事务注解-->
       <tx:annotation-driven transaction-manager="transactionManager" ></tx:annotation-driven>
    </beans>
    

    上面的代码基本就是把我们的业务场景完全情景复现了。然后,修改事务的传播参数,主要是修改BankService 类中的事务方法,根据不同的参数来获得该参数带来的传播特性,去解决特定业务下的问题。

    附录

    image.png

    相关文章

      网友评论

          本文标题:Spring事务的传播特性引发的一场血案

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