Spring 事务
Spring 事务
1. 手写事务
TransactionStatus tx = null;
try {
// 开启事务:
tx = txManager.getTransaction(new DefaultTransactionDefinition());
// 相关JDBC操作:
jdbcTemplate.update("...");
jdbcTemplate.update("...");
// 提交事务:
txManager.commit(tx);
} catch (RuntimeException e) {
// 回滚事务:
txManager.rollback(tx);
throw e;
}
2. 使用声明式事务
- 开启事务
配置类或者启动类添加 @EnableTransactionManagement
@Configuration
@ComponentScan
@EnableTransactionManagement // 启用声明式
@PropertySource("jdbc.properties")
public class AppConfig {
...
}
- 使用事务
@Component
public class UserService {
// 此public方法自动具有事务支持:
@Transactional
public User register(String email, String password, String name) {
...
}
}
Spring 实现事务的原理本质上还是 AOP 代理来实现,查看源码
public class UserService$$EnhancerBySpringCGLIB extends UserService {
UserService target = ...
PlatformTransactionManager txManager = ...
public User register(String email, String password, String name) {
TransactionStatus tx = null;
try {
tx = txManager.getTransaction(new DefaultTransactionDefinition());
target.register(email, password, name);
txManager.commit(tx);
} catch (RuntimeException e) {
txManager.rollback(tx);
throw e;
}
}
...
}
2.1 回滚事务
通过抛出 RuntimeException 异常来回滚事务,也可以针对性的对指定抛出异常来回滚
// 抛出 RuntimeTime 异常回滚
@Transactional
public buyProducts(long productId, int num) {
...
if (store < num) {
// 库存不够,购买失败:
throw new IllegalArgumentException("No enough products");
}
...
}
// 细化异常回滚
@Transactional(rollbackFor = {RuntimeException.class, IOException.class})
public buyProducts(long productId, int num) throws IOException {
...
}
2.2 事务边界
单个方法的事务边界即是方法的开始与结束,内嵌方法的话需要具体看事务的传播行为。
@Component
public class UserService {
@Transactional
public User register(String email, String password, String name) { // 事务开始
...
} // 事务结束
}
事务传播行为:
SUPPORTS
:表示如果有事务,就加入到当前事务,如果没有,那也不开启事务执行。这种传播级别可用于查询方法,因为SELECT语句既可以在事务内执行,也可以不需要事务;
MANDATORY
:表示必须要存在当前事务并加入执行,否则将抛出异常。这种传播级别可用于核心更新逻辑,比如用户余额变更,它总是被其他事务方法调用,不能直接由非事务方法调用;
REQUIRES_NEW
:表示不管当前有没有事务,都必须开启一个新的事务执行。如果当前已经有事务,那么当前事务会挂起,等新事务完成后,再恢复执行;
NOT_SUPPORTED
:表示不支持事务,如果当前有事务,那么当前事务会挂起,等这个方法执行完成后,再恢复执行;
NEVER
:和NOT_SUPPORTED
相比,它不但不支持事务,而且在监测到当前有事务时,会抛出异常拒绝执行;
NESTED
:表示如果当前有事务,则开启一个嵌套级别事务,如果当前没有事务,则开启一个新事务。
2.3 事务传播原理
方法栈是线程私有的,那么要想让多个方法实现协同事务,就需要线程之间共享 Connection 与 TranscationStatus,通过 ThreadLocal 可以实现。