SpringBoot(07):事务管理------@Transactional 你真的用对了吗?

线上出了个事故:用户下单扣了钱,但订单状态没更新。排查半天,发现是 @Transactional 注解加在了 private 方法上,根本没生效。类似的事故我见过不少------同一个类里方法互相调用、异常类型没配对、传播行为选错------每一项都能让事务白加。@Transactional 就一个注解,看着简单,用错了线上就出事。
问题:事务失效的几种常见写法
看几段有问题的代码:
typescript
@Service
public class OrderService {
// 问题1:同一个类里方法互相调用
public void createOrder(Order order) {
orderMapper.insert(order);
this.updateInventory(order); // 事务不生效!
}
@Transactional
public void updateInventory(Order order) {
inventoryMapper.deduct(order.getProductId(), order.getQuantity());
}
}
typescript
@Service
public class PaymentService {
// 问题2:注解加在 private 方法上
@Transactional
private void processPayment(Payment payment) {
accountMapper.deduct(payment.getAmount());
paymentMapper.updateStatus(payment.getId(), "PAID");
}
public void pay(Payment payment) {
this.processPayment(payment);
}
}
kotlin
@Service
public class TransferService {
// 问题3:异常被 catch 了,没抛出去
@Transactional
public void transfer(Long fromId, Long toId, BigDecimal amount) {
try {
accountMapper.deduct(fromId, amount);
accountMapper.add(toId, amount);
} catch (Exception e) {
log.error("转账失败", e);
// 没抛出去,Spring 以为方法正常返回,直接提交事务
}
}
}
这三段代码的共同点: @Transactional 加了,但事务没生效。要理解为什么,得从 Spring 事务管理的底层原理说起。
Spring 事务管理架构

Spring 事务管理的核心组件:
- @Transactional --- 注解标记,声明这个方法需要事务
- TransactionInterceptor --- 事务拦截器,AOP 代理在方法调用前后执行它
- PlatformTransactionManager --- 事务管理器接口,负责 begin/commit/rollback
- DataSourceTransactionManager --- JDBC/MyBatis 场景下的实现
- JpaTransactionManager --- JPA 场景下的实现
调用链路:Controller → AOP Proxy → TransactionInterceptor → Target Method。AOP 代理在调用真实方法之前先开事务,方法正常返回就提交,抛异常就回滚。
这也是为什么 self-invocation(同一个类里 this.xxx())事务不生效------绕过了 AOP 代理,直接调用目标方法,TransactionInterceptor 根本没机会执行。
@Transactional 执行流程
- 客户端调用带 @Transactional 的方法
- AOP 代理拦截请求,转入 TransactionInterceptor
- TransactionInterceptor 从 TransactionManager 获取事务状态
- TransactionManager 从 DataSource 获取 Connection,关闭自动提交(setAutoCommit(false))
- 执行目标方法,方法内的所有 SQL 操作共享同一个 Connection
- 方法正常返回 → commit
- 方法抛出异常 → 检查是否需要回滚(根据 rollbackFor 配置)→ rollback
@Transactional 七大属性详解
ini
@Transactional(
propagation = Propagation.REQUIRED, // 传播行为
isolation = Isolation.DEFAULT, // 隔离级别
timeout = -1, // 超时时间(秒)
readOnly = false, // 是否只读
rollbackFor = RuntimeException.class, // 哪些异常回滚
noRollbackFor = BusinessException.class, // 哪些异常不回滚
transactionManager = "transactionManager" // 事务管理器
)
public void someMethod() { }
1. propagation --- 传播行为
传播行为决定:当前方法遇到一个已经存在的事务时,怎么做。一共 7 种,常用的 3 种标了星号:
| 传播行为 | 说明 | 使用场景 |
|---|---|---|
| REQUIRED * | 有事务就加入,没有就新建 | 默认值,90% 的场景用这个 |
| REQUIRES_NEW * | 不管有没有,都新建一个,外面的事务挂起 | 日志记录、审计,不想因为日志失败影响主业务 |
| NESTED * | 有事务就在里面嵌套一个保存点,没有就新建 | 子操作可以单独回滚,但不影响外层事务 |
| SUPPORTS | 有事务就加入,没有就非事务执行 | 查询方法,不强求事务 |
| NOT_SUPPORTED | 有事务就挂起,非事务执行 | 不需要事务的操作 |
| MANDATORY | 必须在事务中调用,否则抛异常 | 强制要求调用方开事务 |
| NEVER | 必须不在事务中调用,否则抛异常 | 不允许在事务中执行的操作 |
实战:REQUIRED vs REQUIRES_NEW vs NESTED
java
@Service
public class OrderService {
@Autowired
private OrderMapper orderMapper;
@Autowired
private InventoryMapper inventoryMapper;
@Autowired
private AuditLogService auditLogService;
// REQUIRED:默认传播行为
@Transactional(propagation = Propagation.REQUIRED)
public void createOrder(Order order) {
orderMapper.insert(order); // SQL 1
inventoryMapper.deduct(order.getProductId(), order.getQuantity()); // SQL 2
// SQL 1 和 SQL 2 在同一个事务里
// 任何一步失败,全部回滚
}
}
java
@Service
public class AuditLogService {
@Autowired
private AuditLogMapper auditLogMapper;
// REQUIRES_NEW:不管外层有没有事务,自己开一个新的
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void log(String action, Long orderId) {
AuditLog log = new AuditLog();
log.setAction(action);
log.setOrderId(orderId);
auditLogMapper.insert(log);
// 即使外层事务回滚,这条日志也会提交
}
}
java
@Service
public class OrderService {
@Autowired
private OrderMapper orderMapper;
@Autowired
private InventoryMapper inventoryMapper;
@Autowired
private GiftService giftService;
// NESTED:嵌套事务,内部失败不影响外层
@Transactional(propagation = Propagation.REQUIRED)
public void createOrder(Order order) {
orderMapper.insert(order);
inventoryMapper.deduct(order.getProductId(), order.getQuantity());
try {
// 赠品逻辑独立出来,赠品失败不影响主订单
giftService.sendGift(order);
} catch (Exception e) {
log.warn("赠品发放失败,不影响主订单", e);
}
}
}
@Service
public class GiftService {
@Transactional(propagation = Propagation.NESTED)
public void sendGift(Order order) {
giftMapper.insert(new Gift(order.getUserId(), order.getId()));
// 如果这里抛异常,只回滚到保存点
// 外层的 orderMapper.insert 和 inventoryMapper.deduct 不受影响
}
}
三种传播行为的区别:
| 场景 | REQUIRED | REQUIRES_NEW | NESTED |
|---|---|---|---|
| 外层有事务 | 加入外层事务 | 新开独立事务 | 嵌套保存点 |
| 内层回滚 | 整个事务回滚 | 只回滚内层事务 | 只回滚到保存点 |
| 外层回滚 | 内层也回滚 | 内层不受影响 | 内层也回滚 |
| 连接数 | 共享一个 Connection | 需要两个 Connection | 共享一个 Connection |
2. isolation --- 隔离级别
MySQL 默认隔离级别是 REPEATABLE_READ(可重复读),Spring Boot 直接沿用数据库的设置(Isolation.DEFAULT)。一般不需要改。
| 隔离级别 | 脏读 | 不可重复读 | 幻读 | 说明 |
|---|---|---|---|---|
| READ_UNCOMMITTED | 可能 | 可能 | 可能 | 最低级别,基本不用 |
| READ_COMMITTED | 不会 | 可能 | 可能 | Oracle 默认级别 |
| REPEATABLE_READ | 不会 | 不会 | 可能 | MySQL 默认级别 |
| SERIALIZABLE | 不会 | 不会 | 不会 | 最高级别,性能差 |
typescript
// 大多数情况用 DEFAULT 就行,让数据库自己决定
@Transactional(isolation = Isolation.DEFAULT)
public void createOrder(Order order) {
orderMapper.insert(order);
}
// 特殊场景:报表查询,允许脏读换取性能
@Transactional(isolation = Isolation.READ_UNCOMMITTED, readOnly = true)
public List<Order> reportOrders() {
return orderMapper.selectAll();
}
3. readOnly --- 只读事务
ini
@Transactional(readOnly = true)
public OrderDTO getOrder(Long orderId) {
Order order = orderMapper.selectById(orderId);
User user = userMapper.selectById(order.getUserId());
return new OrderDTO(order, user);
}
readOnly = true 的效果:
- JPA/Hibernate 不会做脏检查(flush),性能有提升
- MySQL 执行
START TRANSACTION READ ONLY,InnoDB 可以做优化 - JDBC 的 Connection.setReadOnly(true) 会路由到从库(如果配了读写分离)
查询方法加上 readOnly = true 是个好习惯。
4. rollbackFor --- 回滚条件
这是踩坑最多的属性。
java
// Spring 默认行为:只回滚 RuntimeException 和 Error
// 不回滚 checked Exception(比如 IOException)
// 问题代码:
@Transactional
public void transfer(Long fromId, Long toId, BigDecimal amount)
throws InsufficientBalanceException { // 自定义 checked Exception
accountMapper.deduct(fromId, amount);
accountMapper.add(toId, amount);
// 如果 deduct 之后抛 InsufficientBalanceException
// Spring 不会回滚!钱扣了但没加过去
}
// 正确写法:指定回滚的异常类型
@Transactional(rollbackFor = Exception.class)
public void transfer(Long fromId, Long toId, BigDecimal amount)
throws InsufficientBalanceException {
accountMapper.deduct(fromId, amount);
accountMapper.add(toId, amount);
}
一条规则:永远加 rollbackFor = Exception.class。 让所有异常都回滚,不要依赖 Spring 的默认行为。
5. noRollbackFor --- 不回滚条件
arduino
// 业务异常不想回滚(比如余额不足,但前面的操作要保留)
@Transactional(rollbackFor = Exception.class, noRollbackFor = BusinessException.class)
public void process(Order order) {
orderMapper.insert(order);
if (order.getAmount().compareTo(BigDecimal.ZERO) < 0) {
throw new BusinessException("金额不能为负");
// 抛 BusinessException 不回滚,orderMapper.insert 的记录保留
}
}
这个属性用得少,但在某些业务场景下有用:前面的操作要保留,只有特定异常不触发回滚。
6. timeout --- 超时时间
java
@Transactional(timeout = 5) // 5 秒超时
public void batchInsert(List<Order> orders) {
for (Order order : orders) {
orderMapper.insert(order); // 如果 5 秒内没执行完,自动回滚
}
}
默认 -1,不超时。批量操作、耗时计算等场景可以加上,防止长事务锁表。
7. transactionManager --- 指定事务管理器
多数据源环境下必须指定:
typescript
@Transactional(transactionManager = "orderTransactionManager")
public void createOrder(Order order) {
orderMapper.insert(order);
}
@Transactional(transactionManager = "userTransactionManager")
public void updateUser(User user) {
userMapper.update(user);
}
不指定的话,Spring 用 @Primary 标记的那个事务管理器。
事务失效的 7 种场景

场景 1:同一个类里方法调用(self-invocation)
typescript
@Service
public class OrderService {
public void createOrder(Order order) {
orderMapper.insert(order);
this.updateInventory(order); // ← 绕过了 AOP 代理
}
@Transactional
public void updateInventory(Order order) {
inventoryMapper.deduct(order.getProductId(), order.getQuantity());
}
}
this.updateInventory() 直接调用目标方法,不经过 AOP 代理,TransactionInterceptor 没机会拦截。
解决办法:
scss
// 方法1:把事务方法拆到另一个 Service
@Service
public class InventoryService {
@Transactional
public void updateInventory(Order order) {
inventoryMapper.deduct(order.getProductId(), order.getQuantity());
}
}
@Service
public class OrderService {
@Autowired
private InventoryService inventoryService;
public void createOrder(Order order) {
orderMapper.insert(order);
inventoryService.updateInventory(order); // 通过代理调用
}
}
// 方法2:注入自己(不太优雅)
@Service
public class OrderService {
@Autowired
private OrderService self;
public void createOrder(Order order) {
orderMapper.insert(order);
self.updateInventory(order); // 通过代理调用
}
@Transactional
public void updateInventory(Order order) {
inventoryMapper.deduct(order.getProductId(), order.getQuantity());
}
}
// 方法3:用 AopContext 获取代理对象(需要开启 exposeProxy)
@EnableAspectJAutoProxy(exposeProxy = true)
@SpringBootApplication
public class Application { }
@Service
public class OrderService {
public void createOrder(Order order) {
orderMapper.insert(order);
((OrderService) AopContext.currentProxy()).updateInventory(order);
}
@Transactional
public void updateInventory(Order order) {
inventoryMapper.deduct(order.getProductId(), order.getQuantity());
}
}
三种方案,推荐方案 1:把事务逻辑拆到独立的 Service,干净且好测试。
场景 2:方法不是 public
typescript
@Service
public class OrderService {
@Transactional
private void process(Order order) { // private!Spring AOP 不拦截非 public 方法
orderMapper.insert(order);
inventoryMapper.deduct(order.getProductId(), order.getQuantity());
}
@Transactional
protected void handle(Order order) { // protected 也不行
orderMapper.insert(order);
}
}
Spring AOP 默认只拦截 public 方法。非 public 方法上的 @Transactional 相当于白加。
解决办法:把方法改成 public。
场景 3:异常被吞了
kotlin
@Service
public class TransferService {
@Transactional(rollbackFor = Exception.class)
public void transfer(Long fromId, Long toId, BigDecimal amount) {
try {
accountMapper.deduct(fromId, amount);
accountMapper.add(toId, amount);
} catch (Exception e) {
log.error("转账失败", e);
// 异常没抛出去,Spring 以为正常返回,提交事务
}
}
}
解决办法:catch 完了要 throw,或者手动标记回滚。
csharp
// 方法1:抛出去
@Transactional(rollbackFor = Exception.class)
public void transfer(Long fromId, Long toId, BigDecimal amount) {
try {
accountMapper.deduct(fromId, amount);
accountMapper.add(toId, amount);
} catch (Exception e) {
log.error("转账失败", e);
throw e;
}
}
// 方法2:手动标记回滚
@Transactional(rollbackFor = Exception.class)
public void transfer(Long fromId, Long toId, BigDecimal amount) {
try {
accountMapper.deduct(fromId, amount);
accountMapper.add(toId, amount);
} catch (Exception e) {
log.error("转账失败", e);
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
}
}
场景 4:异常类型不对
java
@Service
public class OrderService {
@Transactional // 默认只回滚 RuntimeException
public void createOrder(Order order) throws IOException {
orderMapper.insert(order);
fileService.exportOrder(order); // 抛 IOException(checked Exception)
// 事务不会回滚!orderMapper.insert 的记录保留
}
}
解决办法 :永远加 rollbackFor = Exception.class。
java
@Transactional(rollbackFor = Exception.class)
public void createOrder(Order order) throws IOException {
orderMapper.insert(order);
fileService.exportOrder(order);
}
场景 5:数据库引擎不支持事务
MySQL 的 MyISAM 引擎不支持事务。如果表用的是 MyISAM,@Transactional 加了也白加。
sql
-- 检查表引擎
SHOW TABLE STATUS LIKE 'orders';
-- 如果 Engine 是 MyISAM,改成 InnoDB
ALTER TABLE orders ENGINE = InnoDB;
Spring Boot 2.x + MySQL 5.7+ 默认用 InnoDB,一般不会有这个问题。但老项目迁移的时候要注意。
场景 6:传播行为用错了
scss
@Service
public class OrderService {
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void createOrder(Order order) {
orderMapper.insert(order);
inventoryMapper.deduct(order.getProductId(), order.getQuantity());
// NOT_SUPPORTED:以非事务方式执行,出错了不回滚
}
@Transactional(propagation = Propagation.SUPPORTS)
public void createOrder2(Order order) {
orderMapper.insert(order);
inventoryMapper.deduct(order.getProductId(), order.getQuantity());
// SUPPORTS:如果没有外层事务,也是非事务执行
}
}
写操作必须用 REQUIRED(默认值)或 REQUIRES_NEW。NOT_SUPPORTED 和 SUPPORTS 用在查询方法上。
场景 7:Bean 没被 Spring 管理
typescript
// 没加 @Service 或 @Component
public class OrderService {
@Transactional
public void createOrder(Order order) {
orderMapper.insert(order);
// 不是 Spring Bean,@Transactional 完全无效
}
}
或者通过 new OrderService() 创建的对象,也不归 Spring 管。
解决办法 :确保类上有 @Service / @Component,且通过 @Autowired 注入使用。
源码分析:@Transactional 的底层实现
1. 事务拦截器:TransactionInterceptor
scala
// org.springframework.transaction.interceptor.TransactionInterceptor
public class TransactionInterceptor extends TransactionAspectSupport
implements MethodInterceptor, Serializable {
@Override
@Nullable
public Object invoke(MethodInvocation invocation) throws Throwable {
// 获取目标类
Class<?> targetClass = (invocation.getThis() != null ?
AopUtils.getTargetClass(invocation.getThis()) : null);
// 调用父类的事务处理逻辑
return invokeWithinTransaction(invocation.getMethod(), targetClass, () -> {
return invocation.proceed(); // 执行目标方法
});
}
}
TransactionInterceptor 实现了 MethodInterceptor 接口,AOP 代理在调用目标方法时,会先执行 invoke()。invoke() 里调用父类 TransactionAspectSupport 的 invokeWithinTransaction()。
2. 核心逻辑:TransactionAspectSupport
java
// org.springframework.transaction.interceptor.TransactionAspectSupport
public abstract class TransactionAspectSupport {
@Nullable
protected Object invokeWithinTransaction(Method method, @Nullable Class<?> targetClass,
InvocationCallback invocation) throws Throwable {
// 1. 获取事务属性(@Transactional 的配置)
TransactionAttributeSource tas = getTransactionAttributeSource();
final TransactionAttribute txAttr = (tas != null ?
tas.getTransactionAttribute(method, targetClass) : null);
// 2. 获取事务管理器
final PlatformTransactionManager tm = determineTransactionManager(txAttr);
// 3. 创建事务
final TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, methodIdentification);
Object retVal;
try {
// 4. 执行目标方法
retVal = invocation.proceedWithInvocation();
} catch (Throwable ex) {
// 5. 异常处理:根据 rollbackFor 判断回滚还是提交
completeTransactionAfterThrowing(txInfo, ex);
throw ex;
} finally {
cleanupTransactionInfo(txInfo);
}
// 6. 正常返回:提交事务
commitTransactionAfterReturning(txInfo);
return retVal;
}
private void completeTransactionAfterThrowing(TransactionInfo txInfo, Throwable ex) {
if (txInfo.transactionAttribute.rollbackOn(ex)) {
// 回滚
txInfo.getTransactionManager().rollback(txInfo.getTransactionStatus());
} else {
// 不回滚,提交
txInfo.getTransactionManager().commit(txInfo.getTransactionStatus());
}
}
}
这段源码把事务的完整生命周期写得很清楚:
- 解析 @Transactional 注解的属性
- 获取对应的事务管理器
- 开启事务(createTransactionIfNecessary)
- 执行目标方法
- 异常时判断是否回滚(rollbackOn)
- 正常时提交(commitTransactionAfterReturning)
3. 事务管理器:AbstractPlatformTransactionManager
java
// org.springframework.transaction.support.AbstractPlatformTransactionManager
public abstract class AbstractPlatformTransactionManager implements PlatformTransactionManager {
@Override
public final TransactionStatus getTransaction(@Nullable TransactionDefinition definition)
throws TransactionException {
// 获取当前事务对象(DataSource 场景下就是 Connection)
Object transaction = doGetTransaction();
// 检查是否已存在事务(处理传播行为)
if (isExistingTransaction(transaction)) {
// 已有事务,根据传播行为决定:加入、挂起、抛异常
return handleExistingTransaction(definition, transaction, debugEnabled);
}
// 没有事务,根据传播行为决定是否新建
if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_MANDATORY) {
throw new IllegalTransactionStateException("当前没有事务,但传播行为是 MANDATORY");
} else if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRED ||
definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW ||
definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) {
// 挂起(如果需要),开新事务
SuspendedResourcesHolder suspendedResources = suspend(null);
try {
return startTransaction(definition, transaction, debugEnabled, suspendedResources);
} catch (RuntimeException ex) {
resume(null, suspendedResources);
throw ex;
}
} else {
// 不需要事务的传播行为
return prepareTransactionStatus(definition, null, true, debugEnabled, null);
}
}
private TransactionStatus startTransaction(TransactionDefinition definition, Object transaction,
boolean debugEnabled, @Nullable SuspendedResourcesHolder suspendedResources) {
boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);
DefaultTransactionStatus status = newTransactionStatus(
definition, transaction, true, newSynchronization, debugEnabled, suspendedResources);
// 子类实现:DataSourceTransactionManager 里就是 connection.setAutoCommit(false)
doBegin(transaction, definition);
prepareSynchronization(status, definition);
return status;
}
}
doBegin() 是关键。DataSourceTransactionManager 的实现:
scss
// org.springframework.jdbc.datasource.DataSourceTransactionManager
public class DataSourceTransactionManager extends AbstractPlatformTransactionManager {
@Override
protected void doBegin(Object transaction, TransactionDefinition definition) {
DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;
Connection con = null;
try {
if (!txObject.hasConnectionHolder() ||
txObject.getConnectionHolder().isSynchronizedWithTransaction()) {
// 从 DataSource 获取 Connection
Connection newCon = obtainDataSource().getConnection();
txObject.setConnectionHolder(new ConnectionHolder(newCon), true);
}
con = txObject.getConnectionHolder().getConnection();
// 设置隔离级别和只读
Integer previousIsolationLevel = DataSourceUtils.prepareConnectionForTransaction(con, definition);
txObject.setPreviousIsolationLevel(previousIsolationLevel);
txObject.setReadOnly(definition.isReadOnly());
// 关键:关闭自动提交
if (con.getAutoCommit()) {
txObject.setMustRestoreAutoCommit(true);
con.setAutoCommit(false); // ← 事务从这里开始
}
// 如果是只读事务,执行 SET TRANSACTION READ ONLY
prepareTransactionalConnection(con, definition);
txObject.getConnectionHolder().setTransactionActive(true);
} catch (Throwable ex) {
DataSourceUtils.releaseConnection(con, obtainDataSource());
throw new CannotCreateTransactionException("无法打开 JDBC Connection 进行事务", ex);
}
}
@Override
protected void doCommit(DefaultTransactionStatus status) {
DataSourceTransactionObject txObject = (DataSourceTransactionObject) status.getTransaction();
Connection con = txObject.getConnectionHolder().getConnection();
try {
con.commit(); // ← 提交
} catch (SQLException ex) {
throw new TransactionSystemException("提交 JDBC 事务失败", ex);
}
}
@Override
protected void doRollback(DefaultTransactionStatus status) {
DataSourceTransactionObject txObject = (DataSourceTransactionObject) status.getTransaction();
Connection con = txObject.getConnectionHolder().getConnection();
try {
con.rollback(); // ← 回滚
} catch (SQLException ex) {
throw new TransactionSystemException("回滚 JDBC 事务失败", ex);
}
}
}
源码看下来,Spring 事务管理的本质就三步:
con.setAutoCommit(false)--- 开事务- 执行业务方法
con.commit()或con.rollback()--- 提交或回滚
Spring 帮你把这三步用 AOP 封装好了,你只需要加个注解。但如果你绕过了 AOP 代理(self-invocation)、或者异常没传到 TransactionInterceptor(catch 吞了),这三步就执行不对。
编程式事务 vs 声明式事务
声明式事务就是前面讲的 @Transactional,通过 AOP 实现。编程式事务是直接在代码里控制事务边界。
编程式事务:TransactionTemplate
scss
@Service
public class OrderService {
@Autowired
private TransactionTemplate transactionTemplate;
@Autowired
private OrderMapper orderMapper;
@Autowired
private InventoryMapper inventoryMapper;
public void createOrder(Order order) {
transactionTemplate.execute(status -> {
try {
orderMapper.insert(order);
inventoryMapper.deduct(order.getProductId(), order.getQuantity());
} catch (Exception e) {
status.setRollbackOnly();
throw e;
}
return null;
});
}
// 有返回值的写法
public Order createAndReturn(Order order) {
return transactionTemplate.execute(status -> {
orderMapper.insert(order);
inventoryMapper.deduct(order.getProductId(), order.getQuantity());
return order;
});
}
}
编程式事务:PlatformTransactionManager
scss
@Service
public class OrderService {
@Autowired
private PlatformTransactionManager transactionManager;
public void createOrder(Order order) {
DefaultTransactionDefinition def = new DefaultTransactionDefinition();
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
def.setIsolationLevel(TransactionDefinition.ISOLATION_DEFAULT);
def.setTimeout(10);
TransactionStatus status = transactionManager.getTransaction(def);
try {
orderMapper.insert(order);
inventoryMapper.deduct(order.getProductId(), order.getQuantity());
transactionManager.commit(status);
} catch (Exception e) {
transactionManager.rollback(status);
throw e;
}
}
}
两种方式对比
| 对比项 | 声明式(@Transactional) | 编程式(TransactionTemplate) |
|---|---|---|
| 代码侵入 | 低,一个注解 | 高,需要手动写事务逻辑 |
| 灵活性 | 方法级别控制 | 代码块级别控制 |
| 可读性 | 好,一眼就看到 | 差,事务逻辑和业务逻辑混在一起 |
| self-invocation 问题 | 有 | 没有(不走 AOP) |
| 适用场景 | 大部分 CRUD 操作 | 需要精确控制事务边界的场景 |
一般用声明式就够了。编程式事务适合这种场景:一个方法里,只有部分代码需要在事务里执行。
scss
public void processOrder(Order order) {
// 1. 校验(不需要事务)
validateOrder(order);
// 2. 入库(需要事务)
transactionTemplate.execute(status -> {
orderMapper.insert(order);
inventoryMapper.deduct(order.getProductId(), order.getQuantity());
return null;
});
// 3. 发通知(不需要事务,失败也不回滚)
notificationService.sendOrderCreated(order);
}
最佳实践
1. @Transactional 加在 Service 层
less
// Controller 层不加事务
@RestController
@RequestMapping("/order")
public class OrderController {
@Autowired
private OrderService orderService;
@PostMapping("/create")
public Result create(@RequestBody OrderRequest request) {
return Result.success(orderService.createOrder(request.toOrder()));
}
}
// Service 层加事务
@Service
public class OrderService {
@Transactional(rollbackFor = Exception.class)
public void createOrder(Order order) {
orderMapper.insert(order);
inventoryMapper.deduct(order.getProductId(), order.getQuantity());
}
}
事务加在 Service 层,Controller 层只做参数校验和结果封装。这样不管有多少个 Controller 调用同一个 Service 方法,事务都能生效。
2. 永远加 rollbackFor = Exception.class
less
// 不好
@Transactional
public void transfer() { }
// 好
@Transactional(rollbackFor = Exception.class)
public void transfer() { }
3. 查询方法加 readOnly = true
typescript
@Transactional(readOnly = true)
public OrderDTO getOrder(Long orderId) {
return new OrderDTO(orderMapper.selectById(orderId));
}
4. 事务方法尽量短
scss
// 不好:事务里做了太多非数据库操作
@Transactional(rollbackFor = Exception.class)
public void createOrder(Order order) {
orderMapper.insert(order); // DB 操作
inventoryMapper.deduct(...); // DB 操作
emailService.sendConfirm(order); // 发邮件,可能耗时几十秒
smsService.sendNotify(order); // 发短信,可能耗时几秒
// 整个方法执行期间事务都开着,锁住数据库连接
}
// 好:事务只包数据库操作
public void createOrder(Order order) {
doCreateOrder(order); // 事务内
emailService.sendConfirm(order); // 事务外
smsService.sendNotify(order); // 事务外
}
@Transactional(rollbackFor = Exception.class)
public void doCreateOrder(Order order) {
orderMapper.insert(order);
inventoryMapper.deduct(order.getProductId(), order.getQuantity());
}
长事务会长时间持有数据库连接和行锁,影响并发性能。事务方法里只放数据库操作,RPC 调用、消息发送、文件操作放到事务外面。
5. 避免嵌套事务陷阱
java
@Service
public class OrderService {
@Transactional(rollbackFor = Exception.class)
public void createOrder(Order order) {
orderMapper.insert(order);
try {
inventoryService.deduct(order);
} catch (Exception e) {
log.warn("扣库存失败", e);
// 这里 catch 了,但 inventoryService.deduct 的事务可能已经标记了 rollbackOnly
// 外层事务 commit 时会抛 UnexpectedRollbackException
}
}
}
@Service
public class InventoryService {
@Transactional(rollbackFor = Exception.class) // 默认 REQUIRED,加入外层事务
public void deduct(Order order) {
inventoryMapper.deduct(order.getProductId(), order.getQuantity());
// 如果这里抛异常,事务状态被标记为 rollbackOnly
}
}
REQUIRED 传播行为下,内层方法加入外层事务。内层抛异常时,事务状态被标记为 rollbackOnly。外层即使 catch 了异常,commit 时也会抛 UnexpectedRollbackException。
解决办法:内层方法用 NESTED 传播行为,或者把内层逻辑改成非事务方式。
6. 多数据源要指定 transactionManager
typescript
@Transactional(transactionManager = "orderTransactionManager", rollbackFor = Exception.class)
public void createOrder(Order order) {
orderMapper.insert(order);
}
总结
| 知识点 | 要点 |
|---|---|
| 核心原理 | AOP 代理 + TransactionInterceptor + PlatformTransactionManager |
| 执行流程 | 拦截方法 → setAutoCommit(false) → 执行方法 → commit/rollback |
| 传播行为 | REQUIRED(默认)、REQUIRES_NEW(独立事务)、NESTED(嵌套保存点) |
| 隔离级别 | DEFAULT 即可,让数据库决定 |
| readOnly | 查询方法加上,有性能优化和读写分离路由效果 |
| rollbackFor | 永远加 Exception.class |
| 事务失效 | self-invocation、非 public、异常被吞、异常类型不对、引擎不支持、传播行为错误、非 Spring Bean |
| 编程式事务 | TransactionTemplate,适合精确控制事务边界 |
| 最佳实践 | 加在 Service 层、方法尽量短、非 DB 操作放事务外 |
@Transactional 就一个注解,背后是 AOP 代理、事务管理器、Connection 管理一整套机制。搞清楚原理,线上事务不生效的问题才好定位。