Spring之事务原理篇

Spring之事务原理篇

1. Spring事务的实现方式

  • 编程式事务 : 在代码中硬编码(不推荐使用) : 通过 TransactionTemplate或者 TransactionManager 手动管理事务,实际应用中很少使用,但是对于你理解 Spring 事务管理原理有帮助。
  • 声明式事务 : 在 XML 配置文件中配置或者直接基于注解(推荐使用) : 实际是通过 AOP 实现(基于@Transactional 的全注解方式使用最多)

另外,在官方文档中也有关于Spring事务的实现原理的简单概述:

关于Spring框架的声明性事务支持,最重要的概念是这种支持是通过 AOP代理 实现的,而且事务advice是由元数据(目前是基于XML或注解)驱动的。AOP与事务元数据的结合产生了一个AOP代理,它使用 TransactionInterceptor 与适当的 TransactionManager 实现来驱动围绕方法调用的事务。

@Transactional 通常与 PlatformTransactionManager 管理的线程绑定的事务一起工作,将一个事务暴露给当前执行线程内的所有数据访问操作。注意:这不会传播到方法内新启动的线程。

ReactiveTransactionManager 管理的响应式事务使用 Reactor 上下文而不是 thread-local 属性。因此,所有参与的数据访问操作都需要在同一 reactive pipeline 中的同一 Reactor 上下文中执行。

下图显示了在事务型代理上调用方法的概念性视图:

image-20240324180314454

2. Spring事务的事务传播行为有几种

Spring事务传播行为是为了解决业务层方法之间互相调用的事务问题

当事务方法被另一个事务方法调用时,必须指定事务应该如何传播。

Spring中定义了7种传播行为,如表所示:

传播行为 含义
PROPAGATION_REQUIRED(required默认级别) 如果存在一个事务,则支持当前事务。
如果没有事务,则开启新事务
PROPAGATION_REQUIRES_NEW(requires_new) 总是开启一个新的事务。
如果一个事务已经存在,则新建一个事务,新老事务相对独立。
外部事务抛出异常回滚不影响内部事务的正常提交。
PROPAGATION_NESTED(nested) 如果一个活动的事务存在,则运行一个嵌套的事务中;
如果没有活动事务,则新建一个事务。
PROPAGATION_SUPPORTS(supports) 如果存在一个事务,则支持当前事务。
如果没有事务,则以非事务的方法执行
PROPAGATION_NOT_SUPPORTED(not_support) 总是非事务地执行,并挂起任何存在的事务
PROPAGATION_MANDATORY(mandatory) 强制事务执行,如果不存在当前事务,抛出异常
PROPAGATION_NEVER(never) 非事务方式执行,如果存在当前事务,抛出异常

3. @Transactional的本质是什么

@Transactional注解仅仅是一些和事务相关的元数据,在运行时被事务基础设施读取消费,并使用这些元数据来配置Bean的事务行为。一是表明该方法要参与事务,二是配置相关属性来定制事务的参与方式和行为。

声明式事务底层是通过AOP实现的,@Transactional注解使用环绕通知,在进入方法前开启事务,使用try-catch包含目标方法,执行目标方法,如果执行完成后没有出现异常,就提交事务,否则就回滚事务。

@Transactional注解既可以标注在类上,也可以标注在方法上。当在类上时,默认应用到类里的所 有方法。如果此时方法上也标注了,则方法上的优先级高。 另外注意方法一定要是public的。

4. Spring事务失效的场景

Spring框架提供了非常方便的事务管理机制,但是在某些情况下,Spring事务会失效。

(1)方法非public。如果一个方法不是public修饰的,那么Spring无法代理这个方法,Spring官方文档中有明确的说明,@Transactional只有在修饰public方法时才会生效。修改pretected、private或者保内可见的方法均不会生效。

(2)事务方法被final、static关键字修饰。事务方法被final修改会防止子类重写该方法,从而无法进行动态代理,事务方法被static修改会使得该方法属于类而不是对象,因此也无法进行动态代理。

(3)方法自调用。当一个类中的一个方法调用同一个类中的另一个@Transactional方法时,这种情况被称为方法自调用,在这种情况下,事务控制将无法正常工作。因为事务代理对象在外部调用时才会被创建,如果一个方法在同一个类中调用另一个方法,实际上是在当前实例中进行调用,而不是通过代理对象调用。

(4)异常被捕获。Spring默认只对未被捕获的异常进行回滚,如果异常被捕获并处理了,那么Spring无法感知到异常的存在,也就无法进行回滚,因此,如果需要事务回滚,必须确保异常未被捕获并没有被处理。

(5)没有被Spring管理。只有在Spring容器中管理的Bean上的@Transactional注解才会生效,如把@Service注解注释掉,这个类就不会被加载成为一个Bean,在该对象的方法上添加@Transactional注解将不会有任何效果。

(6)数据库不支持事务。Spring事务是通过底层的数据库事务实现的。如果底层数据库不支持事务,那么Spring自然无法实现事务管理,如果需要使用事务,选择支持事务的数据库引擎,如MySQL的InnoDB引擎。

5. @Transactional方法自调用如何解决

@Transactional方法自调用时事务会失效:

@Override
public Integer add() {
return add2();
}
@Transactional(rollbackFor = Exception.class)
public Integer add2() {
int res = userMapper.add();
res = 1 / 0;
return 1;
}

如上代码,即时抛出了异常,事务也不会生效。

(1)更改为外部调用。将更新数据表的操作重新抽取成一个service,并将其注入Spring容器管理。

对于上述代码,我们可以将add2()方法重新抽取成一个TestServiceImpl,然后交给Spring管理,重新注入,如果发生异常事务就会回滚。

(2)使用编程式事务。对更新表的操作的逻辑代码使用编程式事务处理,这样即使加了@Transactional注解的方法是private的也可以被事务管理。

@Override
public Integer add() {
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus status) {
add2();
}
});
return 1;
}

@Autowired
private TransactionTemplate transactionTemplate;

// @Transactional(rollbackFor = Exception.class)
// 这时,即使方法改为private一样事务会生效
public Integer add2() {
int res = userMapper.add();
res = 1 / 0;
return 1;
}

6. Spring事务的隔离级别

Spring本身支持的事务其实本质上还是通过数据库的事务来实现的,所以Spring事务的隔离级别和数据库的隔离级别是一样的。

(1)default。Spring默认的隔离级别,表示使用数据库默认的事务隔离级别。

(2)read_uncommitted(读未提交)。事务最低的隔离级别,允许一个事务可以看到其他事务未提交的数据。这种隔离级别会产生脏读、不可重复读和幻读。

(3)read_committed(读已提交)。这是Sql Server、Oracle默认隔离级别,保证一个事务修改的数据提交后才能被其他事务读取,这种隔离级别可以避免脏读,但是可能会出现不可重复读和幻读。

(4)repeatable_read(可重复读)。这是MySQL-innodb默认隔离级别,可以放置脏读、不可重复读,但是可能出现幻读。

(5)serializable(可串行化)。事务被处理为顺序执行。防止脏读、不可重复读、幻读。