Spring支持的事务本质上是数据库支持的事务,所以前提也是数据库支持事务
Spring 不直接实现事务 ,而是通过 事务管理器(PlatformTransactionManager) 来对接不同底层技术:
| 底层技术 | 对应的事务管理器 |
|---|---|
| JDBC / MyBatis | DataSourceTransactionManager |
| JPA | JpaTransactionManager |
| Hibernate | HibernateTransactionManager |
所以 Spring 的事务是 抽象 + 适配 的设计典范。
Spring支持两种方式的事务管理:编程式事务管理(使用硬编码的方式)和声明式事务管理(使用注解@Transactional的方式)
编程式事务:
@Autowired
private PlatformTransactionManager transactionManager;
public void save() {
//1.开启事务
TransactionStatus status = transactionManager.getTransaction(new DefaultTransactionDefinition());
try {
// 业务代码
personDao.save(person);
detailDao.save(detail);
//2.提交
transactionManager.commit(status);
} catch (Exception e) {
//3.回滚
transactionManager.rollback(status);
}
}
//这个是最原始的编程式事务:手动控制事务边界
//其实和直接用 JDBC 的 connection.setAutoCommit(false) + commit()/rollback() 是类似的,只是 Spring 把它抽象成了统一接口。
//TransactionTemplate 对 PlatformTransactionManager 的模板方法封装
@Autowired
private TransactionTemplate transactionTemplate;
public void save() {
transactionTemplate.execute(status -> {
// 业务代码
personDao.save(person);
detailDao.save(detail);
});
}
声明式事务:
@Service
public class AccountService {
@Transactional
public void transfer(int from, int to, int amount) {
accountDao.reduce(from, amount);
// int i = 1/0; // 如果这里出异常,整个方法会回滚!
accountDao.add(to, amount);
}
}
这种通过注解的方式底层原理实际是通过AOP 动态代理, 在方法执行前后插入事务管理逻辑,底层调用 PlatformTransactionManager 实现编程式事务控制。
AOP通过给这个对象创建一个代理的方式拦截这个加了注解的方法,实际走的是代理的invoke方法。这个代理对象内部仍然持有真实对象的引用。
PlatformTransactionManager的底层原理
@Transactional
public void transfer(...) {
accountDao.reduce(...); // 第1次 DB 操作
accountDao.add(...); // 第2次 DB 操作
}
要保证原子性,同一个事务中的多次数据库操作必须使用同一个数据库连接,并且该连接处于手动提交模式
而默认情况下springboot中的datasource会自动配置一个最大连接为10的连接池
spring通过把connection绑定当当前线程(ThreadLocal)来实现使用同一个连接来保证事务操作:
在开启事务的时候,使用一个叫TransactionSynchronizationManager的工具类,内部通过ThreadLocal存储事务相关资源
@Override
protected Object doGetTransaction() {
DataSourceTransactionObject txObject = new DataSourceTransactionObject();
// 尝试从 ThreadLocal 中获取已存在的 Connection
ConnectionHolder conHolder =
(ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);
if (conHolder != null) {
// 已有事务 → 复用 Connection(用于传播行为 REQUIRED)
txObject.setConnectionHolder(conHolder, false);
} else {
// 新事务 → 从 DataSource 获取新连接
Connection con = dataSource.getConnection();
con.setAutoCommit(false); // 关键!关闭自动提交
// 绑定到当前线程的 ThreadLocal
ConnectionHolder newConHolder = new ConnectionHolder(con);
TransactionSynchronizationManager.bindResource(dataSource, newConHolder);
txObject.setConnectionHolder(newConHolder, true);
}
return txObject;
}
但是需要注意的是,ThreadLocal绑定的资源不会自动传递到子线程,
所以在异步任务、线程池等中,事务会失效,除非手动传递连接。
(这也是一次面试中面试官问到的,如果事务中开了一个子线程的话,事务还能生效吗)