(/≧▽≦)/~┴┴ 嗨~我叫小奥 ✨✨✨
👀👀👀 个人博客:小奥的博客
👍👍👍:个人CSDN
⭐️⭐️⭐️:Github传送门
🍹 本人24应届生一枚,技术和水平有限,如果文章中有不正确的内容,欢迎多多指正!
📜 欢迎点赞收藏关注哟! ❤️
文章目录
- Spring之事务原理篇
- 1. Spring事务的实现方式
- 2. Spring事务的事务传播行为有几种
- 3. @Transactional的本质是什么
- 4. Spring事务失效的场景
- 5. @Transactional方法自调用如何解决
- 6. 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 上下文中执行。
下图显示了在事务型代理上调用方法的概念性视图:
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(可串行化)。事务被处理为顺序执行。防止脏读、不可重复读、幻读。