Skip to content

事务

约 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 产生的新事务内嵌于旧事务中,内层回滚不影响外层,而外层回滚内层一定会回滚