事务
约 995 字大约 3 分钟
2025-01-12
要么全部执行,要么都不执行 于 Spring 中,能够使用编程式管理与声明式管理两种方式 编程式
@Autowired
private TransactionTemplate transactionTemplate;
public void testTransaction() {
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {
try {
// .... 业务代码
} catch (Exception e){
//回滚
transactionStatus.setRollbackOnly();
}
}
});
}
或是更简单的声明式
@Transactional
public void aMethod {
//do something
B b = new B();
C c = new C();
b.bMethod();
c.cMethod();
}
工作原理
如果使用 Mybatis 这个 ORM 框架并打开日志,你会看到以下输出
Creating a new SqlSession
Registering transaction synchronization for SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@6bb71265]
JDBC Connection [HikariProxyConnection@940454178 wrapping com.mysql.cj.jdbc.ConnectionImpl@576323ff] will be managed by Spring
在日志中看到managed by Spring
,即创建的 SqlSession 将被 Spring 管理,这就是 Spring 事务管理的原因。默认下,@Transactional
将会捕获代码范围内的运行时异常及错误,完成回滚后再抛出
@Transactional
void delete() throws IOException {
animalMapper.deleteByPrimaryKey(1);
if (true) {
throw new IOException();
}
animalMapper.deleteByPrimaryKey(2);
}
在默认情况下,编译时异常(如IOException
)不会被捕获,在以上代码中就会出现 sql 没有被回滚,因此需要使用rollbackFor
参数指定需要回滚的异常(不会影响默认的运行时异常下的回滚)
@Transactional(rollbackFor = IOException.class)
void delete() throws IOException {
animalMapper.deleteByPrimaryKey(1);
if (true) {
throw new IOException();
}
animalMapper.deleteByPrimaryKey(2);
}
By the way
事务基于 AOP 实现,目标类实现了接口,则使用 JDK 实现,否则使用 CGLIB 此外,AOP 的实现在类内自调用下事务不会生效。正常调用 AOP 方法时调用的是代理类而非目标类,而在同一类中产生了直接调用,因此@Transactional
注解失效
@Service
public class MyService {
private void method1() {
method2(); // ❌ 自调用:事务不会生效!
//......
}
@Transactional
public void method2() {
// 数据库操作...
}
}
事务传播
传播行为
当 A 事务调用 B 事务时,由 B 决定是否在父事务中运行或是新开一个事务进行运行
- REQUIRED(使用当前或新建)(默认):若没有当前事务,则新建一个事务;若存在当前事务,则支持该事务(使用该事务),例如 A 方法存在事务,A 调用 B,而 B 的传播行为为
REQUIRED
时,则会使用 A 传入的事务,类似[[锁#ReentrantLock|可重入锁]] - SUPPORTS(使用当前或不使用):若存在当前事务,则使用该事务,否则以非事务执行
- REQUIRES_NEW(总是新建):开启一个新事务,若存在当前事务则将其挂起
- MANDATORY(当前无则报错):若存在当前事务,则对其支持,否则抛出异常
- NOT_SUPPORTED(总是非事务):总是非事务执行,若存在当前事务则挂起
- NEVER(不允许存在事务):总是非事务执行,若存在当前事务则抛出异常
- NESTED(当前有事务则嵌套):若存在当前事务,则在当前事务中创建一个嵌套事务,否则按照
REQUIRED
执行
分类记忆?
依赖事务
必须在事务上运行
- REQUIRED:有则加入,无则新建
- MANDATORY:有则加入,无则报错
不依赖事务
可以在非事务上运行
- SUPPORTS:有则加入,无则非事务
- NOT_SUPPORTED:存在当前事务则将其挂起,自身总是非事务运行
- NEVER:有事务就抛出异常
独立事务
总是新建自己的事务运行
- REQUIRES_NEW:挂起旧事务,运行在新事务
- NESTED:在当前事务内嵌套创建一个新事务运行
NESTED 与 REQUIRED 有何区别
NESTED
中创建的嵌套事务发生异常回滚时不会影响当前事务,即 B 抛出异常只需要 B 回滚即可,而REQUIRED
中与当前事务共用一个事务,一旦 B 抛出异常则 A 也需要回滚
REQUIRES_NEW 与 NESTED 有何区别
REQUIRES_NEW 产生的新事务与旧事务相独立,提交回滚互补影响 NESTED 产生的新事务内嵌于旧事务中,内层回滚不影响外层,而外层回滚内层一定会回滚