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帮我们注入的是动态生成的代理对象,所以可以做到事务增强。
踩坑二
为了图方便,在测试事务传播特性的时候,两个事务方法,我都使用了相同的数据库操作方法,如果这个操作恰好又是修改的话,那么数据库就会产生行锁,导致测试又一次失败。
总结
如果想要又快又好的做事务传播配置的测试,那么需要注意:
- 必须把事务方法定义在不同的service类中,这样做本身也是很符合事务传播要解决的业务场景;
- 不同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&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 类中的事务方法,根据不同的参数来获得该参数带来的传播特性,去解决特定业务下的问题。
网友评论