第一次在线上环境遇到@Transactional失效问题时,我盯着日志里那条本该回滚却成功提交的订单记录愣了半天。作为Spring框架最常用的注解之一,@Transactional看似简单,实则暗藏玄机。它的失效往往发生在深夜紧急上线时,或是业务高峰期的生产环境里——这正是最要命的时候。
事务失效的本质是AOP代理机制被打破。Spring通过动态代理实现事务管理,当调用带有@Transactional的方法时,实际调用的是代理对象的方法。如果调用链中某个环节跳过了代理,事务控制就会失效。这就像多米诺骨牌,中间任何一块牌倒下,整个链条就会中断。
java复制@Service
public class OrderService {
public void createOrder(OrderDTO dto) {
this.validateStock(dto); // 这里直接调用了同类方法
}
@Transactional
public void validateStock(OrderDTO dto) {
// 库存校验逻辑
}
}
这个案例中,createOrder方法直接调用同类中的validateStock方法,跳过了Spring代理,导致事务失效。我曾在电商项目中因此损失了价值23万的库存数据。
解决方案:将事务方法拆分到不同类,或通过ApplicationContext.getBean()获取代理对象
java复制@Transactional
public void processPayment() throws BusinessException {
try {
paymentGateway.charge();
} catch (GatewayTimeoutException e) {
throw new BusinessException("支付超时"); // 非RuntimeException
}
}
Spring默认只对RuntimeException回滚。某次支付系统故障时,我们封装的自定义异常导致本该回滚的交易被提交,引发大量用户投诉。
修正方案:添加rollbackFor参数
java复制@Transactional(rollbackFor = Exception.class)
java复制@Transactional
private void updateInventory() {
// 库存更新逻辑
}
私有方法无法被代理,这是AOP的基本限制。曾有个团队花了三天排查库存不同步问题,最终发现是这个低级错误。
java复制@Transactional
public void batchProcess(List<Item> items) {
items.parallelStream().forEach(item -> {
repository.save(item); // 每个线程独立连接
});
}
并行流中每个线程使用不同数据库连接,导致事务碎片化。我们在处理10万级订单时,因此产生了数百条部分成功记录。
java复制@Transactional(propagation = Propagation.REQUIRES_NEW)
public void logOperation() {
auditLogRepository.save(log);
}
public void businessMethod() {
// 业务逻辑
logOperation(); // 开启新事务
if(systemError) {
throw new RuntimeException(); // 主事务回滚不影响日志记录
}
}
REQUIRES_NEW会使日志记录独立提交,这在审计场景是优点,但在需要原子性的场景就是灾难。
java复制@Transactional
@Async
public void asyncProcess() {
// 异步处理逻辑
}
@Async会创建新的代理链,与@Transactional代理链断裂。我们在对账系统中因此丢失了关键流水记录。
在application.yml中开启调试:
yaml复制logging:
level:
org.springframework.transaction.interceptor: TRACE
org.springframework.jdbc.datasource.DataSourceTransactionManager: DEBUG
关键日志线索:
java复制// 在测试用例中加入
assertThat(AopUtils.isAopProxy(service)).isTrue();
assertThat(AopUtils.isCglibProxy(service)
|| AopUtils.isJdkDynamicProxy(service)).isTrue();
sql复制-- MySQL查看当前会话事务状态
SELECT * FROM information_schema.innodb_trx
WHERE trx_mysql_thread_id = CONNECTION_ID();
java复制@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Transactional(
isolation = Isolation.READ_COMMITTED,
propagation = Propagation.REQUIRED,
rollbackFor = Throwable.class,
timeout = 30
)
public @interface StrictTransactional {}
java复制@SpringBootTest
class TransactionalValidationTest {
@Autowired
private PlatformTransactionManager transactionManager;
@Test
void testMethodTransactional() {
TransactionStatus status = transactionManager.getTransaction(
new DefaultTransactionDefinition());
try {
testService.targetMethod();
fail("Expected exception");
} catch (Exception e) {
assertThat(transactionManager.getTransaction(status).isRollbackOnly())
.isTrue();
}
}
}
事务超时设置需要根据业务特点调整:
连接池配置建议:
yaml复制spring:
datasource:
hikari:
maximum-pool-size: 20 # 根据TPS测算
connection-timeout: 30000
max-lifetime: 1800000
在金融级系统中,我们采用以下监控指标:
某银行夜间批处理中,由于嵌套事务配置错误,导致10万笔转账重复执行。根本原因是:
java复制@Transactional
public void batchTransfer() {
list.forEach(item -> {
transferService.singleTransfer(item); // 内部也有@Transactional
});
}
修正方案:
java复制@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void batchTransfer() {
list.forEach(item -> {
transactionTemplate.execute(status -> {
return transferService.singleTransfer(item);
});
});
}
电商大促时,以下代码导致库存扣减失效:
java复制@Transactional
public void deductStock(Long sku, int num) {
// 先查后改
Item item = repository.findById(sku);
if(item.getStock() >= num) {
item.setStock(item.getStock() - num);
}
}
优化方案:
java复制@Transactional
@Lock(LockModeType.PESSIMISTIC_WRITE)
public void deductStock(Long sku, int num) {
// 使用SELECT FOR UPDATE
Item item = repository.findWithLockingById(sku);
// 或使用乐观锁
// @Version + repository.save()
}
java复制@Component
class TransactionListener {
@TransactionalEventListener(phase = TransactionPhase.AFTER_ROLLBACK)
public void handleRollback(OrderFailedEvent event) {
compensationService.compensate(event);
}
}
java复制// 使用Seata的全局事务
@GlobalTransactional
public void crossServiceOperation() {
orderService.create();
inventoryService.deduct();
accountService.debit();
}
java复制@Transactional
@CircuitBreaker(
failThreshold = 3,
resetTimeout = 30000
)
public void riskyOperation() {
// 高风险操作
}
在微服务架构下,我们最终形成的黄金法则: