- Spring声明式事务
- Spring事务注解
- @Transactional注解参数
- Spring事务传播级别
- Spring如何传播事务
- 小结
所有的事物都由PlatformTransactionManager负责管理。事务由TransactionStatus负责表示。之所以抽象出这两个玩意儿,是因为JavaEE除了提供JDBC事务外,还支持分布式事务JTA(Java Transation API)。然而分布式事务非常慢,所以使用率不高。Spring为了同时支持JDBC和JTA两种事物类型,就抽象出了上面的两个玩意儿。
Spring事务注解| 注解 | 翻译 | 作用 |
|---|---|---|
| @EnableTransactionManagement | 启动事务管理 | 用在启动类AppConfig上,启用声明式事物,其原理仍然是AOP代理,即通过自动创建Bean的Proxy实现,因此,启动类上添加了这个注解后,不需要再添加@EnableAspectJAutoProxy |
| @Transactional | 交易的,事务的 | 用在需要事务支持的方法上或者Bean的class处,表示所有public方法都具有事务支持。需要回滚事务,只需要抛出RuntimeException。更多内容本小节**@Transactional注解参数**。 |
-
如果要针对Checked Exception回滚事务,需要在@Transactional注解中写出来,如IOException.class,使用方法形如
@Transactional(rollbackFor = {RuntimeException.class, IOException.class}) public buyProducts(long productId, int num) throws IOException { ... }上述代码表示在抛出RuntimeException或IOException时,事务将回滚。为了简化代码,我们强烈建议业务异常体系从RuntimeException派生,这样就不必声明任何特殊异常即可让Spring的声明式事务正常工作:
public class BusinessException extends RuntimeException { ... } public class LoginException extends BusinessException { ... } public class PaymentException extends BusinessException { ... } -
定义事务的传播级别
@Transactional(propagation = Propagation.REQUIRES_NEW) public Product createProduct() { ... }
| 级别 | 翻译 | 作用 |
|---|---|---|
| REQUIRED | 要求;要求具备;规定 | 默认传播级别**(满足大部分需求)** |
| SUPPORTS | 支持 | 表示如果有事务,就加入到当前事务,如果没有,那也不开启事务执行。这种传播级别可用于查询方法,因为SELECT语句既可以在事务内执行,也可以不需要事务;(少数情况用到) |
| MANDATORY | 强制的;法定的 | 表示必须要存在当前事务并加入执行,否则将抛出异常。这种传播级别可用于核心更新逻辑,比如用户余额变更,它总是被其他事务方法调用,不能直接由非事务方法调用; |
| REQUIRES_NEW | 需要;要求具备_新 | 表示不管当前有没有事务,都必须开启一个新的事务执行。如果当前已经有事务,那么当前事务会挂起,等新事务完成后,再恢复执行;(少数情况用到) |
| NOT_SUPPORTED | 不支持的 | 表示不支持事务,如果当前有事务,那么当前事务会挂起,等这个方法执行完成后,再恢复执行; |
| NEVER | 从不;绝不 | 和NOT_SUPPORTED相比,它不但不支持事务,而且在监测到当前有事务时,会抛出异常拒绝执行; |
| NESTED | 相互套叠 | 表示如果当前有事务,则开启一个嵌套级别事务,如果当前没有事务,则开启一个新事务。 |
上面加黑的部分会用到,其它的基本不会用到,因为把事务搞的越复杂,不仅逻辑跟着复杂,而且速度也会越慢。
Spring如何传播事务Spring使用声明式事务,最终也是通过执行JDBC事务来实现功能的,那么,一个事务方法,如何获知当前是否存在事务?
答案是使用ThreadLocal。Spring总是把JDBC相关的Connection和TransactionStatus实例绑定到ThreadLocal。如果一个事务方法从ThreadLocal未取到事务,那么它会打开一个新的JDBC连接,同时开启一个新的事务,否则,它就直接使用从ThreadLocal获取的JDBC连接以及TransactionStatus。
因此,事务能正确传播的前提是,方法调用是在一个线程内才行。如果像下面这样写:
@Transactional
public User register(String email, String password, String name) { // BEGIN TX-A
User user = jdbcTemplate.insert("...");
new Thread(() -> {
// BEGIN TX-B:
bonusService.addBonus(user.id, 100);
// END TX-B
}).start();
} // END TX-A
在另一个线程中调用BonusService.addBonus(),它根本获取不到当前事务,因此,UserService.register()和BonusService.addBonus()两个方法,将分别开启两个完全独立的事务。
换句话说,事务只能在当前线程传播,无法跨线程传播。
那如果我们想实现跨线程传播事务呢?原理很简单,就是要想办法把当前线程绑定到ThreadLocal的Connection和TransactionStatus实例传递给新线程,但实现起来非常复杂,根据异常回滚更加复杂,不推荐自己去实现。
小结Spring提供的声明式事务极大地方便了在数据库中使用事务,正确使用声明式事务的关键在于确定好事务边界,理解事务传播级别。



