SpringBoot事务:从“一键开关”到“踩坑大全”的生存指南 🎢⚡
各位Java侠士,有没有经历过这种绝望瞬间:你信心满满地在方法上加了@Transactional,数据却没回滚;或者明明只是查个数据,却抛出一堆锁超时异常。你盯着代码陷入沉思——这破注解到底生不生效? 😤
别慌!今天咱们就把SpringBoot里这个最常用、也最“玄学”的@Transactional扒个底朝天。保证你看完以后,不仅能优雅地控制事务,还能在同事面前淡定地解释:“哦,这个事务传播行为嘛,是这么回事……” 🧐
第一章:SpringBoot事务——你的“御用翻译官” 🧑💼
首先理解一个核心问题:SpringBoot自己不会管理事务,它只是个“翻译官” 。
当你写:
@Transactional
public void transferMoney(Long fromId, Long toId, BigDecimal amount) {
// 扣钱
accountDao.deduct(fromId, amount);
// 加钱
accountDao.add(toId, amount);
}
SpringBoot帮你“翻译”成类似这样的JDBC代码:
Connection conn = dataSource.getConnection();
try {
conn.setAutoCommit(false); // 1. 关闭自动提交
// 2. 执行你的业务代码
accountDao.deduct(fromId, amount);
accountDao.add(toId, amount);
conn.commit(); // 3. 成功,提交
} catch (Exception e) {
conn.rollback(); // 4. 失败,回滚
throw e;
} finally {
conn.setAutoCommit(true);
}
SpringBoot事务的本质:帮你在业务代码前后,自动加上“事务开关、提交、回滚”的逻辑。没有它,你得自己写一堆样板代码,想想都头大!🤯
第二章:@Transactional的“七宗罪”——那些年我们踩过的坑 🕳️
坑1:事务不生效之“自调用陷阱”
@Service
public class OrderService {
public void placeOrder(Order order) {
// 做一些校验...
this.createOrder(order); // ❌ 直接调内部方法,事务不生效!
}
@Transactional
public void createOrder(Order order) {
orderDao.insert(order);
// 如果这里抛异常,数据不会回滚!
}
}
原因:Spring事务基于AOP代理,this.方法()调用的是原对象,不是代理对象。
解决方案:
@Service
public class OrderService {
@Autowired
private OrderService self; // 注入自己
public void placeOrder(Order order) {
self.createOrder(order); // ✅ 通过代理对象调用
}
@Transactional
public void createOrder(Order order) {
// 现在事务生效了!
}
}
坑2:异常被吞之“抓了不抛”
@Transactional
public void updateUser(User user) {
try {
userDao.update(user);
int i = 1 / 0; // 肯定会抛ArithmeticException
} catch (Exception e) {
log.error("出错了", e);
// ❌ 抓了异常但不抛,Spring以为一切正常,提交事务!
}
}
解决方案:
@Transactional(rollbackFor = Exception.class) // 指定回滚异常
public void updateUser(User user) {
try {
userDao.update(user);
int i = 1 / 0;
} catch (Exception e) {
log.error("出错了", e);
throw new RuntimeException(e); // ✅ 重新抛出
}
}
坑3:错误异常类型之“默认只回滚RuntimeException”
@Transactional // ❌ 默认只回滚RuntimeException和Error
public void saveData() throws IOException {
// 抛IOException?事务不会回滚!
throw new IOException("文件错误");
}
解决方案:
@Transactional(rollbackFor = Exception.class) // ✅ 回滚所有异常
public void saveData() throws IOException {
// 现在抛IOException也会回滚了
}
坑4:非public方法之“私有事务”
@Service
public class UserService {
@Transactional
private void saveUser(User user) { // ❌ 私有方法,事务不生效!
userDao.insert(user);
}
}
记住:@Transactional只能用在public方法上!Spring官方文档写的明明白白。
坑5:多数据源之“切错片儿”
@Transactional
public void crossDatabaseOperation() {
userDao.insert(user); // 操作数据库A
orderDao.insert(order); // 操作数据库B
// ❌ 这俩不在一个事务里!出问题不能一起回滚
}
解决方案:用JTA/XA分布式事务,或者用Seata这类分布式事务框架。
第三章:传播行为——事务的“套娃艺术” 🪆
传播行为(Propagation)是Spring事务最骚的操作,定义了“方法B调用方法A时,事务怎么玩”。
7种传播行为,其实常用的就3种:
1. REQUIRED(默认):有就用,没有就新建
@Transactional(propagation = Propagation.REQUIRED)
public void methodA() {
// 如果调用方有事务,就用它的
// 如果没有,就新建一个
methodB(); // B会用A的事务
}
@Transactional(propagation = Propagation.REQUIRED)
public void methodB() {
// 和methodA在同一个事务里
}
适用场景:90%的情况都用这个,简单省心。
2. REQUIRES_NEW:甭管有没有,我自建新房
@Transactional(propagation = Propagation.REQUIRED)
public void methodA() {
// 主逻辑
logService.saveLog(); // 记录日志,必须成功
// 即使这里抛异常,日志也得记
}
@Service
public class LogService {
@Transactional(propagation = Propagation.REQUIRES_NEW) // 新事务
public void saveLog() {
// 独立事务,methodA回滚不影响我
}
}
适用场景:日志记录、消息发送等“必须成功”的旁路操作。
3. NESTED:嵌套事务,子随父动
@Transactional
public void batchProcess(List<Item> items) {
for (Item item : items) {
try {
processItem(item); // 单个失败不影响其他
} catch (Exception e) {
log.error("处理失败: {}", item, e);
}
}
}
@Transactional(propagation = Propagation.NESTED)
public void processItem(Item item) {
// 嵌套事务,失败只回滚自己
// 但需要数据库支持(MySQL的InnoDB支持)
}
适用场景:批量处理,局部失败不影响整体。
其他几种(了解即可):
- SUPPORTS:有就用,没有拉倒(非事务执行)
- NOT_SUPPORTED:不支持事务,挂起当前事务
- MANDATORY:必须有事务,没有就报错
- NEVER:绝不能有事务,有就报错
第四章:隔离级别——与MySQL的“爱恨情仇” 💑
Spring事务隔离级别,底层就是MySQL的隔离级别,但多了一层封装:
@Transactional(isolation = Isolation.REPEATABLE_READ) // MySQL默认
public void business() {
// 可重复读级别
}
Spring的4个级别 vs MySQL的4个级别:
| Spring级别 | MySQL对应 | 性能 | 脏读 | 不可重复读 | 幻读 |
|---|---|---|---|---|---|
| READ_UNCOMMITTED | READ UNCOMMITTED | 🚀最快 | 可能 | 可能 | 可能 |
| READ_COMMITTED | READ COMMITTED | 🏃较快 | 防止 | 可能 | 可能 |
| REPEATABLE_READ | REPEATABLE READ | 🚶一般 | 防止 | 防止 | 可能 |
| SERIALIZABLE | SERIALIZABLE | 🐌最慢 | 防止 | 防止 | 防止 |
注意坑点:
@Transactional(isolation = Isolation.SERIALIZABLE)
public void criticalOperation() {
// 串行化级别,性能最差!
// 除非是“秒杀扣库存”这种强一致性场景,否则慎用
}
实际开发建议:
- 大部分情况:用默认(不指定),让Spring用数据库默认级别
- 高并发读:可尝试
READ_COMMITTED,减少锁竞争 - 财务系统:用
REPEATABLE_READ,代码里处理幻读
第五章:声明式 vs 编程式——两种武器的抉择 ⚔️
声明式事务(@Transactional):优雅但“不透明”
@Service
@Transactional // 类级别生效
public class UserService {
public User getUser(Long id) {
return userDao.selectById(id);
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void updateUser(User user) {
// 这个方法单独开事务
}
}
优点:代码干净,无侵入
缺点:配置复杂时,不容易理解事务边界
编程式事务(TransactionTemplate):灵活但“啰嗦”
@Service
public class OrderService {
@Autowired
private TransactionTemplate transactionTemplate;
public void createOrder(Order order) {
transactionTemplate.execute(status -> {
try {
// 业务逻辑
orderDao.insert(order);
inventoryDao.deduct(order.getProductId(), order.getQuantity());
if (order.getAmount() > 10000) {
// 手动回滚
status.setRollbackOnly();
return false;
}
return true;
} catch (Exception e) {
// 异常时自动回滚
throw e;
}
});
}
}
优点:完全控制,可编程化回滚
缺点:代码啰嗦,事务逻辑和业务逻辑混在一起
怎么选?
- 80%场景:用
@Transactional,简单省事 - 复杂事务逻辑:用
TransactionTemplate,精细控制 - 两者混合:用
@Transactional打底,特殊场景用编程式
第六章:SpringBoot事务最佳实践 🏆
实践1:明确指定回滚异常
// ✅ 推荐
@Transactional(rollbackFor = Exception.class)
public void saveData() {
// ...
}
// ❌ 不推荐(默认只回滚RuntimeException)
@Transactional
public void saveData() throws Exception {
// ...
}
实践2:事务方法尽量短小
@Transactional
public void processOrder(Long orderId) {
// ✅ 快速查询
Order order = orderDao.findById(orderId);
// ✅ 快速更新
order.setStatus(OrderStatus.PAID);
orderDao.update(order);
// ❌ 不要在这里发HTTP请求、调外部接口
// ❌ 不要有长时间循环
// ❌ 不要等用户输入
// 如果需要复杂操作,拆分方法
sendNotificationAsync(order); // 异步发送通知
}
实践3:只读查询用@Transactional(readOnly = true)
@Transactional(readOnly = true) // ✅ 优化性能
public List<Order> getUserOrders(Long userId) {
return orderDao.findByUserId(userId);
}
好处:
- 数据库优化(MySQL可启用只读模式)
- 可路由到从库(如果用了读写分离)
- 防止误操作
实践4:事务与锁的配合
@Transactional
@Lock(LockModeType.PESSIMISTIC_WRITE) // 悲观锁
public Order lockAndUpdate(Long orderId) {
Order order = orderDao.findById(orderId);
// 这里order被锁定,其他事务无法修改
order.setStatus(OrderStatus.PROCESSING);
return orderDao.save(order);
}
实践5:监控事务执行
# application.yml
logging:
level:
org.springframework.orm.jpa: DEBUG
org.springframework.transaction: DEBUG
org.springframework.jdbc.datasource.DataSourceTransactionManager: DEBUG
// 或用Micrometer监控
@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource) {
DataSourceTransactionManager manager = new DataSourceTransactionManager(dataSource);
manager.setTransactionSynchronizationName(
DataSourceTransactionManager.SYNCHRONIZATION_NEVER
);
return new TransactionExecutorMetrics(manager); // 包装监控
}
第七章:高级玩法——多数据源与分布式事务 🚀
多数据源事务配置
@Configuration
@EnableTransactionManagement
public class DataSourceConfig {
@Primary
@Bean(name = "masterTransactionManager")
public PlatformTransactionManager masterTransactionManager(
@Qualifier("masterDataSource") DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
@Bean(name = "slaveTransactionManager")
public PlatformTransactionManager slaveTransactionManager(
@Qualifier("slaveDataSource") DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
}
// 使用指定事务管理器
@Service
public class UserService {
@Transactional(transactionManager = "masterTransactionManager")
public void updateUser(User user) {
// 用主库事务
}
@Transactional(transactionManager = "slaveTransactionManager",
readOnly = true)
public User getUser(Long id) {
// 用从库事务,只读
}
}
分布式事务方案(二阶段提交)
// 1. 用Spring的JTA(需要JTA实现,如Atomikos)
@Bean
public JtaTransactionManager transactionManager() {
return new JtaTransactionManager();
}
@Transactional
public void distributedOperation() {
// 操作多个数据库
userDao.insert(user); // 数据库A
orderDao.insert(order); // 数据库B
// 要么都成功,要么都回滚
}
// 2. 或用Seata(推荐,阿里开源)
@GlobalTransactional // Seata的全局事务注解
public void purchase(Long userId, Long productId) {
// 扣库存(服务A)
inventoryService.deduct(productId);
// 创建订单(服务B)
orderService.create(userId, productId);
// 扣余额(服务C)
accountService.deduct(userId, product.getPrice());
}
最后的“心法”总结 🧘♂️
- 默认不一定最好:
@Transactional默认设置适合80%场景,但另外20%需要你显式配置 - 理解传播行为:别只会用REQUIRED,REQUIRES_NEW和NESTED关键时刻能救命
- 监控与测试:事务问题难调试,一定要有事务监控和单元测试
- 简单即美:能不用事务就不用,必须用时尽量简单
- 异步解耦:长事务是大忌,用异步消息或工作流拆分
记住SpringBoot事务的终极哲学:它让你的代码更简洁,但绝不是你逃避思考事务边界、异常处理的借口。
当你真正掌握事务传播、隔离级别、回滚规则时,你就从“会用@Transactional的程序员”升级为“理解事务本质的架构师”。下次面试被问事务,你可以优雅地喝口水,从ACID讲到分布式事务,从传播行为聊到最终一致性…… 😎
事务如人生,有始有终,要么全得,要么全舍。代码如此,生活亦如此。 ✨
(好了,现在去检查你的代码,把那些裸奔的数据库操作,用合适的事务包装起来吧!🔧)