美文网首页
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