Java Spring 事务

不用Spring管理事务?

先来看一下不用Spring管理事务时,各种框架是如何管理事务的,使用JDBC来管理事务
Spring事务失效原因分析 - 图2
使用Hibernate来管理事务
Spring事务失效原因分析 - 图3
业务逻辑和事务代码是耦合到一块的,并且和框架的具体api绑定了。当我们换一种框架来实现时,里面对事务控制的代码就要推倒重写,并不一定能保证替换后的api和之前的api有相同的行为。

「统一的事务抽象」

基于这些问题,Spring抽象了一些事务相关的顶层接口。无论是全局事务还是本地事务,JTA,JDBC还是Hibernate,Spring都使用统一的编程模型。使得应用程序可以很容易的在全局事务与本地事务,或者不同事物框架之间进行切换。

「下图为Spring事物抽象的核心类」

Spring事务失效原因分析 - 图4

常用api 接口
PlatformTransactionManager 对事务进行管理
TransactionDefinition 定义事务的相关属性,例如隔离级别,传播行为
TransactionStatus 保存事务状态

针对不同的数据访问技术,使用不用的PlatformTransactionManager类即可

数据访问技术 PlatformTransactionManager实现类
JDBC/Mybatis DataSourceTransactionManager
Hibernate HibernateTransactionManager
Jpa JpaTransactionManager
Jms JmsTransactionManager

编程式事务管理

当我们使用Spring的事务时,可以使用编程式事务或者声明式事务。
当使用编程式事务的时候,可以直接使用事务的顶层接口,也可以使用模版类TransactionTemplate

使用PlatformTransactionManager

Spring事务失效原因分析 - 图5Spring事务失效原因分析 - 图6Spring事务失效原因分析 - 图7

使用TransactionTemplate

当直接使用PlatformTransactionManager来管理事务时,有很多模版代码。例如业务代码正常执行,提交事务,否则回滚事务。可以把这部分模版代码封装成一个模版类,这样使用起来就很方便了,如下所示Spring事务失效原因分析 - 图8如下图所示,TransactionTemplate#execute方法就是一个典型的模版方法Spring事务失效原因分析 - 图9可以传入如下2个接口的实现类来执行业务逻辑,TransactionCallback(需要返回执行结果)或TransactionCallbackWithoutResult(不需要返回结果)

声明式事务管理

为了让使用更加简洁,Spring直接把事务代码的执行放到切面中了,只需要在业务代码方法上加上一个@Transactional注解即可,这种方式我们最常用哈

使用@Transactional注解

此时事务相关的定义就可以通过@Transactional注解来设置了

属性名 类型 描述 默认值
value(和transactionManager互为别名) String 当在配置文件中有多个PlatformTransactionManager,用该属性指定选择哪个事务管理器 空字符串””
propagation 枚举:Propagation 事务的传播行为 REQUIRED
isolation 枚举:Isolation 事务的隔离度 DEFAULT
timeout int 事务的超时时间。如果超过该时间限制但事务还没有完成,则自动回滚事务 -1
readOnly boolean 指定事务是否为只读事务 false
rollbackFor Class[] 需要回滚的异常 空数组{}
rollbackForClassName String[] 需要回滚的异常类名 空数组{}
noRollbackFor Class[] 不需要回滚的异常 空数组{}
noRollbackForClassName String[] 不需要回滚的异常类名 空数组{}

Spring事务失效原因分析 - 图10

源码解析

需要在配置类上加上@EnableTransactionManagement注解,来开启Spring事务管理功能Spring事务失效原因分析 - 图11

TransactionManagementConfigurationSelector#selectImportsSpring事务失效原因分析 - 图12

往容器中注入AutoProxyRegistrarProxyTransactionManagementConfiguration这2个类,那这2个类有啥作用呢?(源码太多了,就不贴代码一步一步分析了,主要是理清思路)
Spring事务失效原因分析 - 图13
AutoProxyRegistrar主要就是往容器中注入一个类InfrastructureAdvisorAutoProxyCreator,这个类有什么作用呢?

「看一下继承关系,原来是继承自AbstractAutoProxyCreator,用来实现自动代理没跑了!」

Spring事务失效原因分析 - 图14BeanFactoryTransactionAttributeSourceAdvisor主要就是往容器中注入了一个Advisor类,用来保存PointcutAdvice
Spring事务失效原因分析 - 图15对应的PointcutTransactionAttributeSourcePointcut的实现类,是一个匿名内部类,即筛选的逻辑是通过TransactionAttributeSourcePointcut类来实现的
BeanFactoryTransactionAttributeSourceAdvisorSpring事务失效原因分析 - 图16对应的Advice的实现类为TransactionInterceptor,即针对事务增强的逻辑都在这个类中。
筛选的逻辑就先不分析了,后面会再简单提一下
来看针对事务增强的逻辑,当执行被@Transactional标记的方法时,会调用到如下方法(TransactionInterceptor#invoke有点类似@Around
TransactionInterceptor#invokeSpring事务失效原因分析 - 图17TransactionAspectSupport#invokeWithinTransactionSpring事务失效原因分析 - 图18挑出这个方法比较重要的几个部分来分析(上图圈出来的部分)

  1. 如果需要的话开启事务(和传播属性相关,我们后面会提到)
  2. 执行业务逻辑
  3. 如果发生异常则会滚事务
  4. 如果正常执行则提交事务

    「所以当发生异常需要会滚的时候,一定不要自己把异常try catch掉,不然事务会正常提交」

    TransactionAspectSupport#createTransactionIfNecessarySpring事务失效原因分析 - 图19当开启事务的时候,可以看到各种传播属性的行为(即@Transactional方法调用@Transactional方法会发生什么?)
    AbstractPlatformTransactionManager#getTransactionSpring事务失效原因分析 - 图20
    Spring事务的传播行为在Propagation枚举类中定义了如下几种选择

    「支持当前事务」

  • REQUIRED:如果当前存在事务,则加入该事务。如果当前没有事务,则创建一个新的事务
  • SUPPORTS:如果当前存在事务,则加入该事务 。如果当前没有事务, 则以非事务的方式继续运行
  • MANDATORY:如果当前存在事务,则加入该事务 。如果当前没有事务,则抛出异常

    「不支持当前事务」

  • REQUIRES_NEW:如果当前存在事务,则把当前事务挂起,创建一个新事务

  • NOT_SUPPORTED:如果当前存在事务,则把当前事务挂起,以非事务方式运行,
  • NEVER:如果当前存在事务,则抛出异常

    「其他情况」

  • NESTED:如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来执行 。如果当前没有事务,则该取值等价于REQUIRED

NESTED启动的事务内嵌于外部事务中 (如果存在外部事务的话),此时内嵌事务并不是一个独立的事务,它依赖于外部事务。只有通过外部事务的提交,才能引起内部事务的提交,嵌套的子事务不能单独提交

事务失效的场景有哪些?

因为经常使用声明式事务,如果一步消息就会导致事务失效,所以就从源码角度来盘一下事务为什么失效

异常被你try catch了

首先就是上面刚提到的,「异常被try catch了」。因为声明式事物是通过目标方法是否抛出异常来决定是提交事物还是会滚事物的

自调用

当自调用时,方法执行不会经过代理对象,所以会导致事务失效

  1. // 事务失效
  2. @Service
  3. public class UserServiceV2Impl implements UserService {
  4. @Autowired
  5. private JdbcTemplate jdbcTemplate;
  6. @Override
  7. public void addUser(String name, String location) {
  8. doAdd(name);
  9. }
  10. @Transactional
  11. public void doAdd(String name) {
  12. String sql = "insert into user (`name`) values (?)";
  13. jdbcTemplate.update(sql, new Object[]{name});
  14. throw new RuntimeException("保存用户失败");
  15. }
  16. }

可以通过如下三种方式来解决自调用失效的场景

「1.@Autowired注入代理对象,然后调用方法」

  1. // @Service
  2. public class UserServiceV3Impl implements UserService {
  3. @Autowired
  4. private JdbcTemplate jdbcTemplate;
  5. @Autowired
  6. private UserService userService;
  7. @Override
  8. public void addUser(String name, String location) {
  9. userService.doAdd(name);
  10. }
  11. @Override
  12. @Transactional
  13. public void doAdd(String name) {
  14. String sql = "insert into user (`name`) values (?)";
  15. jdbcTemplate.update(sql, new Object[]{name});
  16. throw new RuntimeException("保存用户失败");
  17. }
  18. }

「2.从ApplicationContext获取代理对象,然后调用方法」

  1. @Service
  2. public class UserServiceV4Impl implements UserService {
  3. @Autowired
  4. private JdbcTemplate jdbcTemplate;
  5. @Autowired
  6. private ApplicationContext applicationContext;
  7. @Override
  8. public void addUser(String name, String location) {
  9. UserService userService = applicationContext.getBean(UserService.class);
  10. userService.doAdd(name);
  11. }
  12. @Override
  13. @Transactional
  14. public void doAdd(String name) {
  15. String sql = "insert into user (`name`) values (?)";
  16. jdbcTemplate.update(sql, new Object[]{name});
  17. throw new RuntimeException("保存用户失败");
  18. }
  19. }

「3.进行如下设置@EnableAspectJAutoProxy(exposeProxy = true),从AopContext中获取代理对象,然后调用方法」

  1. @Service
  2. public class UserServiceV5Impl implements UserService {
  3. @Autowired
  4. private JdbcTemplate jdbcTemplate;
  5. @Override
  6. public void addUser(String name, String location) {
  7. UserService userService = (UserService) AopContext.currentProxy();
  8. userService.doAdd(name);
  9. }
  10. @Override
  11. @Transactional
  12. public void doAdd(String name) {
  13. String sql = "insert into user (`name`) values (?)";
  14. jdbcTemplate.update(sql, new Object[]{name});
  15. throw new RuntimeException("保存用户失败");
  16. }
  17. }

public方法导致事务失效

先来猜一下为什么非public方法会导致事务失效?

「难道是因为非public方法不会生成代理对象?」

给一个非public方法加上@Transactional,debug到如下代码看一下是否会生成代理对象
AbstractAutoProxyCreator#wrapIfNecessarySpring事务失效原因分析 - 图21

「结论是不会生成代理对象,那为什么不会生成代理对象呢?」

应该就是不符合Pointcut的要求了呗,在前面已经提到了事务对应的PointcutTransactionAttributeSourcePointcut
TransactionAttributeSourcePointcut#matchesSpring事务失效原因分析 - 图22matches方法返回false,为什么会返回false呢?
一直debug发现是如下代码导致的
AbstractFallbackTransactionAttributeSource#computeTransactionAttribute
Spring事务失效原因分析 - 图23public方法能正常生成代理对象,而非public方法因为不符合Pointcut的要求,根本就不会生成代理对象

异常类型不正确,默认只支持RuntimeExceptionError,不支持检查异常

Spring事务失效原因分析 - 图24

「为什么不支持检查异常呢?」

拿出上面分析过的代码Spring事务失效原因分析 - 图25当执行业务逻辑发生异常的时候,会调用到TransactionAspectSupport#completeTransactionAfterThrowing方法
Spring事务失效原因分析 - 图26可以看到对异常类型做了判断,根据返回的结果来决定是否会滚事务,会调用到如下方法进行判断
RuleBasedTransactionAttribute#rollbackOnSpring事务失效原因分析 - 图27如果用户指定了回滚的异常类型,则根据用户指定的规则来判断,否则用默认的规则
DefaultTransactionAttributeSpring事务失效原因分析 - 图28默认的规则为只支持RuntimeExceptionError
可以通过@Transactional属性指定回滚的类型,一般为Exception即可

  1. @Transactional(rollbackFor = Exception.class)

Spring事务失效原因分析 - 图29