"Spring Boot的事务管理在项目中用得非常多。主要使用声明式事务(@Transactional注解),它基于Spring AOP实现。我会从基本使用、核心原理、失效场景、实战优化四个方面来分享我的经验。"
一、基本使用(日常开发)
1.1 最简单的用法
@Service
public class OrderService {
@Autowired
private OrderMapper orderMapper;
@Autowired
private ProductMapper productMapper;
/**
* 创建订单 - 最基础的事务使用
*/
@Transactional
public void createOrder(Order order) {
// 1. 插入订单
orderMapper.insert(order);
// 2. 扣减库存
productMapper.decreaseStock(order.getProductId(), order.getQuantity());
// 如果任何一步出错,整个事务回滚
}
}
说明:
@Transactional标注在方法上,该方法内的所有数据库操作在一个事务中- 如果方法正常执行完成 → 提交事务
- 如果抛出RuntimeException或Error → 回滚事务
- 如果抛出受检异常(Checked Exception)→ 不会回滚(这是个坑)
1.2 事务属性配置
@Transactional(
// 1. 传播行为:当前方法被另一个事务方法调用时的行为
propagation = Propagation.REQUIRED,
// 2. 隔离级别:解决并发问题
isolation = Isolation.READ_COMMITTED,
// 3. 超时时间:防止长事务(秒)
timeout = 30,
// 4. 只读事务:优化性能
readOnly = false,
// 5. 回滚规则:哪些异常触发回滚
rollbackFor = Exception.class,
// 6. 不回滚规则:哪些异常不回滚
noRollbackFor = IllegalArgumentException.class
)
public void complexTransaction() {
// 业务逻辑
}
1.3 常用场景示例
场景1:订单创建(多表操作)
@Transactional(rollbackFor = Exception.class)
public Long createOrder(OrderDTO orderDTO) {
// 1. 创建订单主表
Order order = new Order();
order.setUserId(orderDTO.getUserId());
order.setTotalAmount(orderDTO.getTotalAmount());
order.setStatus(OrderStatus.UNPAID);
orderMapper.insert(order);
// 2. 创建订单明细
for (OrderItemDTO item : orderDTO.getItems()) {
OrderItem orderItem = new OrderItem();
orderItem.setOrderId(order.getId());
orderItem.setProductId(item.getProductId());
orderItem.setQuantity(item.getQuantity());
orderItemMapper.insert(orderItem);
// 3. 扣减库存
int rows = productMapper.decreaseStock(
item.getProductId(),
item.getQuantity()
);
if (rows == 0) {
throw new BusinessException("库存不足");
}
}
// 4. 记录操作日志
orderLogMapper.insert(new OrderLog(order.getId(), "订单创建"));
return order.getId();
}
场景2:用户注册(关联数据创建)
@Transactional(rollbackFor = Exception.class)
public void registerUser(UserRegisterDTO dto) {
// 1. 创建用户
User user = new User();
user.setUsername(dto.getUsername());
user.setPassword(passwordEncoder.encode(dto.getPassword()));
userMapper.insert(user);
// 2. 创建用户扩展信息
UserProfile profile = new UserProfile();
profile.setUserId(user.getId());
profile.setNickname(dto.getNickname());
userProfileMapper.insert(profile);
// 3. 赠送新人优惠券
CouponUser coupon = new CouponUser();
coupon.setUserId(user.getId());
coupon.setCouponId(NEW_USER_COUPON_ID);
couponUserMapper.insert(coupon);
// 4. 初始化钱包
Wallet wallet = new Wallet();
wallet.setUserId(user.getId());
wallet.setBalance(BigDecimal.ZERO);
walletMapper.insert(wallet);
}
二、核心原理(必须理解)
2.1 事务的本质
// 事务就是把多个数据库操作包装在一起
// Spring帮我们自动管理了以下流程:
try {
// 开启事务
connection.setAutoCommit(false);
// 执行业务逻辑
orderMapper.insert(order);
productMapper.decreaseStock(productId, quantity);
// 提交事务
connection.commit();
} catch (Exception e) {
// 回滚事务
connection.rollback();
throw e;
} finally {
connection.close();
}
2.2 Spring事务实现原理(AOP代理)
// 原始类
@Service
public class OrderService {
@Transactional
public void createOrder() {
// 业务逻辑
}
}
// Spring生成的代理类(伪代码)
public class OrderService$$Proxy extends OrderService {
private TransactionManager txManager;
private OrderService target; // 原始对象
@Override
public void createOrder() {
TransactionInfo txInfo = null;
try {
// 1. 开启事务(前置增强)
txInfo = txManager.getTransaction(txDefinition);
// 2. 调用原始方法
target.createOrder();
// 3. 提交事务(后置增强)
txManager.commit(txInfo);
} catch (RuntimeException | Error e) {
// 4. 回滚事务(异常增强)
txManager.rollback(txInfo);
throw e;
}
}
}
关键点:
- Spring通过AOP创建代理对象
- 代理对象拦截@Transactional方法
- 在方法执行前后增加事务管理逻辑
- 必须通过代理对象调用,事务才生效
2.3 事务传播行为详解
最重要的3种传播行为:
REQUIRED(默认,最常用)
@Transactional(propagation = Propagation.REQUIRED)
public void methodA() {
// 业务逻辑A
methodB(); // 调用B
}
@Transactional(propagation = Propagation.REQUIRED)
public void methodB() {
// 业务逻辑B
}
// 执行结果:
// methodA有事务 → methodB加入methodA的事务(同一个事务)
// methodA无事务 → methodB创建新事务
REQUIRES_NEW(最容易出问题)
@Transactional
public void methodA() {
orderMapper.insert(order); // 操作1
methodB(); // 调用B
// 如果这里抛异常,操作1回滚,但methodB已提交,不会回滚
throw new RuntimeException("error");
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void methodB() {
logMapper.insert(log); // 操作2
}
// 执行结果:
// methodB创建新事务,立即提交
// methodA回滚时,methodB的操作不受影响
真实案例:记录操作日志
@Service
public class OrderService {
@Autowired
private LogService logService;
@Transactional(rollbackFor = Exception.class)
public void createOrder(Order order) {
orderMapper.insert(order);
// 无论订单创建成功还是失败,都要记录日志
// 所以日志使用REQUIRES_NEW,独立事务
logService.saveLog("创建订单", order.getId());
// 如果后面业务失败,订单回滚,但日志已经保存
productMapper.decreaseStock(order.getProductId(), order.getQuantity());
}
}
@Service
public class LogService {
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void saveLog(String action, Long orderId) {
operationLogMapper.insert(new OperationLog(action, orderId));
}
}
NESTED(嵌套事务,用savepoint实现)
@Transactional
public void methodA() {
orderMapper.insert(order); // 操作1
try {
methodB(); // 操作2
} catch (Exception e) {
// methodB回滚,但不影响methodA
}
// 操作1继续,可以提交
}
@Transactional(propagation = Propagation.NESTED)
public void methodB() {
productMapper.decreaseStock(productId, quantity);
}
// 执行结果:
// methodB使用savepoint(保存点)
// methodB异常 → 回滚到savepoint,methodA可以继续
// methodA异常 → methodB也会回滚
2.4 事务隔离级别
| 隔离级别 | 脏读 | 不可重复读 | 幻读 | 说明 |
|---|---|---|---|---|
| READ_UNCOMMITTED | ✅ | ✅ | ✅ | 读到未提交数据 |
| READ_COMMITTED | ❌ | ✅ | ✅ | Oracle默认 |
| REPEATABLE_READ | ❌ | ❌ | ✅ | MySQL默认(InnoDB通过MVCC解决幻读) |
| SERIALIZABLE | ❌ | ❌ | ❌ | 串行执行,性能最差 |
实际项目中的使用:
// 场景1:财务对账(要求数据强一致)
@Transactional(isolation = Isolation.SERIALIZABLE)
public void reconciliation() {
// 对账逻辑,避免并发问题
}
// 场景2:普通订单查询(用MySQL默认即可)
@Transactional(
readOnly = true,
isolation = Isolation.REPEATABLE_READ
)
public Order getOrderById(Long id) {
return orderMapper.selectById(id);
}
// 场景3:秒杀扣库存(需要避免幻读)
@Transactional(isolation = Isolation.REPEATABLE_READ)
public void decreaseStock(Long productId, Integer quantity) {
// 使用乐观锁或悲观锁
Product product = productMapper.selectById(productId);
if (product.getStock() >= quantity) {
productMapper.decreaseStock(productId, quantity);
}
}
三、事务失效场景(重点,容易踩坑)
3.1 坑1:类内部方法调用
@Service
public class OrderService {
// ❌ 错误示例
public void methodA() {
// 直接调用本类的methodB
this.methodB(); // 事务不生效!
}
@Transactional
public void methodB() {
orderMapper.insert(order);
}
}
// 原因:this.methodB()没有通过代理对象调用,直接调用了原始对象
// 解决方案:
解决方案1:注入自己(推荐)
@Service
public class OrderService {
@Autowired
private OrderService self; // 注入自己(代理对象)
public void methodA() {
self.methodB(); // 通过代理对象调用,事务生效
}
@Transactional
public void methodB() {
orderMapper.insert(order);
}
}
解决方案2:使用AopContext(不推荐,需要开启expose-proxy)
public void methodA() {
OrderService proxy = (OrderService) AopContext.currentProxy();
proxy.methodB();
}
解决方案3:拆分到不同类(最佳)
@Service
public class OrderService {
@Autowired
private OrderTransactionService orderTransactionService;
public void methodA() {
orderTransactionService.methodB(); // 跨类调用,事务生效
}
}
@Service
public class OrderTransactionService {
@Transactional
public void methodB() {
orderMapper.insert(order);
}
}
3.2 坑2:方法不是public
// ❌ 错误示例
@Transactional
protected void createOrder() { // protected、private都不行
orderMapper.insert(order);
}
// 原因:Spring AOP默认只代理public方法
// 解决方案:改成public
3.3 坑3:异常被捕获
// ❌ 错误示例
@Transactional
public void createOrder() {
try {
orderMapper.insert(order);
productMapper.decreaseStock(productId, quantity);
} catch (Exception e) {
log.error("创建订单失败", e);
// 异常被吃掉,事务不会回滚!
}
}
// ✅ 正确示例1:重新抛出异常
@Transactional(rollbackFor = Exception.class)
public void createOrder() {
try {
orderMapper.insert(order);
productMapper.decreaseStock(productId, quantity);
} catch (Exception e) {
log.error("创建订单失败", e);
throw e; // 重新抛出,触发回滚
}
}
// ✅ 正确示例2:手动回滚
@Transactional(rollbackFor = Exception.class)
public void createOrder() {
try {
orderMapper.insert(order);
productMapper.decreaseStock(productId, quantity);
} catch (Exception e) {
log.error("创建订单失败", e);
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
return; // 手动标记回滚
}
}
3.4 坑4:抛出的是受检异常
// ❌ 错误示例
@Transactional
public void createOrder() throws Exception {
orderMapper.insert(order);
throw new Exception("业务异常"); // 受检异常,不会回滚!
}
// ✅ 正确示例:指定rollbackFor
@Transactional(rollbackFor = Exception.class)
public void createOrder() throws Exception {
orderMapper.insert(order);
throw new Exception("业务异常"); // 现在会回滚
}
3.5 坑5:数据库不支持事务
// MySQL的MyISAM引擎不支持事务
// 即使加了@Transactional,也不会生效
// 解决方案:使用InnoDB引擎
CREATE TABLE orders (
id BIGINT PRIMARY KEY,
...
) ENGINE=InnoDB; -- 必须是InnoDB
3.6 坑6:事务传播行为设置错误
// ❌ 错误示例
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void createOrder() {
orderMapper.insert(order);
// NOT_SUPPORTED会挂起当前事务,以非事务方式执行
}
// 常见错误:误用REQUIRES_NEW导致数据不一致
@Transactional
public void methodA() {
orderMapper.insert(order); // 操作1
methodB(); // 操作2
throw new RuntimeException(); // 操作1回滚,但操作2已提交
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void methodB() {
logMapper.insert(log);
}
3.7 坑7:多线程场景
// ❌ 错误示例
@Transactional
public void createOrder() {
orderMapper.insert(order);
// 新开线程,事务不会传播到子线程
new Thread(() -> {
productMapper.decreaseStock(productId, quantity);
}).start();
}
// 原因:事务是ThreadLocal的,每个线程独立
// 解决方案:不要在事务方法中开启新线程,或者用消息队列异步处理
四、实战优化经验
4.1 避免大事务
问题:大事务会导致锁持有时间长,影响并发性能
// ❌ 错误示例:大事务
@Transactional
public void processOrder(Long orderId) {
// 1. 查询订单(可能很慢)
Order order = orderService.getOrderDetail(orderId);
// 2. 调用外部接口(网络IO,可能超时)
PayResult result = paymentClient.pay(order);
// 3. 发送短信(外部调用)
smsService.sendSms(order.getUserPhone(), "订单已支付");
// 4. 更新订单状态
orderMapper.updateStatus(orderId, OrderStatus.PAID);
}
// ✅ 正确示例:缩小事务范围
public void processOrder(Long orderId) {
// 1. 查询订单(无需事务)
Order order = orderService.getOrderDetail(orderId);
// 2. 调用外部接口(无需事务)
PayResult result = paymentClient.pay(order);
// 3. 只在更新数据库时开启事务
updateOrderStatus(orderId, OrderStatus.PAID);
// 4. 发送短信(无需事务,异步处理)
smsService.sendSmsAsync(order.getUserPhone(), "订单已支付");
}
@Transactional(rollbackFor = Exception.class)
public void updateOrderStatus(Long orderId, OrderStatus status) {
orderMapper.updateStatus(orderId, status);
}
4.2 使用编程式事务(灵活控制)
@Service
public class OrderService {
@Autowired
private TransactionTemplate transactionTemplate;
/**
* 场景:部分逻辑需要事务,部分不需要
*/
public void createOrder(Order order) {
// 1. 参数校验(不需要事务)
validateOrder(order);
// 2. 调用外部接口(不需要事务)
PayResult payResult = paymentClient.prePay(order);
// 3. 只在保存数据时使用事务
Long orderId = transactionTemplate.execute(status -> {
orderMapper.insert(order);
orderItemMapper.batchInsert(order.getItems());
return order.getId();
});
// 4. 发送通知(不需要事务)
notificationService.sendOrderNotify(orderId);
}
/**
* 场景:需要手动控制回滚
*/
public void complexBusiness() {
transactionTemplate.execute(status -> {
try {
orderMapper.insert(order);
productMapper.decreaseStock(productId, quantity);
return order.getId();
} catch (StockNotEnoughException e) {
// 库存不足,手动回滚
status.setRollbackOnly();
return null;
}
});
}
}
4.3 只读事务优化
/**
* 查询场景:使用只读事务
* 好处:
* 1. MySQL会优化查询(不加锁)
* 2. 提示Spring这是只读操作(可以做优化)
*/
@Transactional(readOnly = true)
public List<Order> getOrderList(Long userId) {
return orderMapper.selectByUserId(userId);
}
/**
* 复杂查询:关联多张表
*/
@Transactional(
readOnly = true,
timeout = 10 // 设置超时,避免慢查询
)
public OrderDetailVO getOrderDetail(Long orderId) {
Order order = orderMapper.selectById(orderId);
List<OrderItem> items = orderItemMapper.selectByOrderId(orderId);
User user = userMapper.selectById(order.getUserId());
return buildOrderDetailVO(order, items, user);
}
4.4 事务超时设置
/**
* 防止长事务:设置超时时间
*/
@Transactional(
rollbackFor = Exception.class,
timeout = 30 // 30秒超时
)
public void importOrders(List<Order> orders) {
for (Order order : orders) {
orderMapper.insert(order);
}
}
/**
* 批量操作:分批提交
*/
public void batchImport(List<Order> orders) {
// 分批处理,每批500条
List<List<Order>> batches = Lists.partition(orders, 500);
for (List<Order> batch : batches) {
importBatch(batch); // 每批一个事务
}
}
@Transactional(rollbackFor = Exception.class, timeout = 10)
private void importBatch(List<Order> batch) {
orderMapper.batchInsert(batch);
}
4.5 分布式事务方案
场景:跨数据库、跨服务的事务
/**
* 方案1:本地消息表(最常用)
*/
@Transactional(rollbackFor = Exception.class)
public void createOrder(Order order) {
// 1. 创建订单(本地数据库)
orderMapper.insert(order);
// 2. 插入消息表(本地数据库,同一个事务)
LocalMessage message = new LocalMessage();
message.setTopic("order_created");
message.setContent(JSON.toJSONString(order));
message.setStatus(MessageStatus.PENDING);
localMessageMapper.insert(message);
// 提交事务后,定时任务扫描消息表,发送MQ
}
/**
* 方案2:Seata分布式事务(复杂场景)
*/
@GlobalTransactional(timeoutMills = 300000)
public void placeOrder(Order order) {
// 1. 订单服务:创建订单
orderService.createOrder(order);
// 2. 库存服务:扣减库存(远程调用)
stockService.decreaseStock(order.getProductId(), order.getQuantity());
// 3. 积分服务:增加积分(远程调用)
pointService.addPoints(order.getUserId(), order.getAmount());
// 任何一个服务失败,都会全部回滚
}
/**
* 方案3:最终一致性(推荐)
*/
public void createOrder(Order order) {
// 1. 创建订单(本地事务)
Long orderId = createOrderInDb(order);
// 2. 发送MQ消息(异步)
rocketMQTemplate.asyncSend("order_created_topic",
new OrderCreatedEvent(orderId),
new SendCallback() {
@Override
public void onSuccess(SendResult sendResult) {
log.info("订单创建事件发送成功");
}
@Override
public void onException(Throwable e) {
log.error("订单创建事件发送失败,需要补偿", e);
// 记录失败,定时重试
}
});
}
@Transactional(rollbackFor = Exception.class)
private Long createOrderInDb(Order order) {
orderMapper.insert(order);
return order.getId();
}
五、监控与排查
5.1 事务监控
/**
* 自定义事务监控切面
*/
@Aspect
@Component
public class TransactionMonitorAspect {
@Around("@annotation(transactional)")
public Object monitorTransaction(ProceedingJoinPoint pjp,
Transactional transactional) throws Throwable {
String methodName = pjp.getSignature().getName();
long startTime = System.currentTimeMillis();
try {
Object result = pjp.proceed();
long duration = System.currentTimeMillis() - startTime;
// 记录事务执行时间
if (duration > 1000) {
log.warn("长事务告警: {}耗时{}ms", methodName, duration);
}
return result;
} catch (Exception e) {
log.error("事务执行失败: {}", methodName, e);
throw e;
}
}
}
5.2 常见问题排查
-- 1. 查看当前事务
SELECT * FROM information_schema.INNODB_TRX;
-- 2. 查看锁等待
SELECT * FROM information_schema.INNODB_LOCKS;
SELECT * FROM information_schema.INNODB_LOCK_WAITS;
-- 3. 查看长事务
SELECT
trx_id,
trx_state,
trx_started,
TIMESTAMPDIFF(SECOND, trx_started, NOW()) AS duration,
trx_mysql_thread_id,
trx_query
FROM information_schema.INNODB_TRX
WHERE TIMESTAMPDIFF(SECOND, trx_started, NOW()) > 10
ORDER BY trx_started;
六、面试高频追问
Q1: "@Transactional注解的原理是什么?"
标准答案:
"基于Spring AOP实现。Spring在启动时扫描@Transactional注解,通过JDK动态代理或CGLIB为目标类生成代理对象。代理对象在方法执行前开启事务,执行后提交事务,异常时回滚事务。"
详细解释:
// 1. 启动时,Spring扫描@Transactional注解
// 2. 为带注解的类创建代理(BeanPostProcessor)
// 3. 代理对象拦截方法调用,执行事务逻辑
// 伪代码:
Object proxy = Proxy.newProxyInstance(..., new InvocationHandler() {
public Object invoke(Object proxy, Method method, Object[] args) {
// 获取事务属性
TransactionAttribute txAttr = getTransactionAttribute(method);
// 开启事务
TransactionInfo txInfo = createTransactionIfNecessary(txAttr);
try {
// 执行原方法
Object result = method.invoke(target, args);
// 提交事务
commitTransactionAfterReturning(txInfo);
return result;
} catch (Throwable ex) {
// 回滚事务
completeTransactionAfterThrowing(txInfo, ex);
throw ex;
}
}
});
Q2: "事务什么时候会失效?"
标准答案:
"主要有7种情况:1)类内部方法调用;2)方法不是public;3)异常被捕获;4)抛出受检异常但未指定rollbackFor;5)数据库不支持事务;6)传播行为设置错误;7)多线程场景。"
Q3: "REQUIRED和REQUIRES_NEW的区别?"
标准答案:
"REQUIRED:如果当前有事务就加入,没有就新建。REQUIRES_NEW:无论当前有没有事务,都会新建一个事务,并挂起当前事务。主要区别是REQUIRES_NEW会创建独立的事务,不受外部事务影响。"
实战场景:
// 记录操作日志:无论业务成功失败,日志都要保存
// 所以日志使用REQUIRES_NEW
@Transactional
public void createOrder() {
orderMapper.insert(order);
// 日志独立事务,无论订单创建成功还是失败,日志都会保存
logService.saveLog("创建订单");
// 如果这里抛异常,订单回滚,但日志已保存
productMapper.decreaseStock(productId, quantity);
}
Q4: "如何避免大事务?"
标准答案:
"1) 缩小事务范围,只在必要时开启事务;2) 使用编程式事务,精确控制事务边界;3) 异步处理非核心逻辑;4) 批量操作分批提交;5) 避免在事务中调用RPC、HTTP等外部接口。"
Q5: "分布式事务怎么处理?"
标准答案:
"实际项目中,我们优先选择最终一致性方案,通过MQ保证数据最终一致。具体有:1)本地消息表;2)事务消息(RocketMQ);3)Saga模式。强一致性场景才考虑Seata等分布式事务框架,但要评估性能影响。"
七、实战案例(真实经历)
案例1:订单创建事务回滚,但积分已扣除
问题背景: 用户下单时,订单创建失败,但积分已经被扣除了。
问题代码:
@Service
public class OrderService {
@Autowired
private PointService pointService;
@Transactional(rollbackFor = Exception.class)
public void createOrder(Order order) {
// 1. 扣积分(调用另一个服务)
pointService.deductPoints(order.getUserId(), order.getPoints());
// 2. 创建订单
orderMapper.insert(order);
// 3. 扣库存
int rows = productMapper.decreaseStock(
order.getProductId(),
order.getQuantity()
);
if (rows == 0) {
throw new BusinessException("库存不足"); // 抛异常回滚
}
}
}
@Service
public class PointService {
// ❌ 问题:使用了REQUIRES_NEW
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void deductPoints(Long userId, Integer points) {
pointMapper.deduct(userId, points);
}
}
问题原因:
PointService.deductPoints使用了REQUIRES_NEW- 它创建了独立的事务,立即提交
- 当订单创建失败回滚时,积分扣除不会回滚
解决方案:
// 方案1:改用REQUIRED(推荐)
@Transactional(propagation = Propagation.REQUIRED)
public void deductPoints(Long userId, Integer points) {
pointMapper.deduct(userId, points);
}
// 方案2:调整顺序,先创建订单,成功后再扣积分
@Transactional(rollbackFor = Exception.class)
public void createOrder(Order order) {
// 1. 先创建订单
orderMapper.insert(order);
// 2. 扣库存
productMapper.decreaseStock(order.getProductId(), order.getQuantity());
// 3. 最后扣积分(成功才扣)
pointService.deductPoints(order.getUserId(), order.getPoints());
}
// 方案3:使用消息队列(最佳)
@Transactional(rollbackFor = Exception.class)
public void createOrder(Order order) {
// 1. 创建订单
orderMapper.insert(order);
// 2. 扣库存
productMapper.decreaseStock(order.getProductId(), order.getQuantity());
// 3. 发送MQ消息,异步扣积分
rocketMQTemplate.sendMessageInTransaction(
"point_deduct_topic",
new PointDeductEvent(order.getUserId(), order.getPoints()),
order
);
}
效果:
- 方案1:订单和积分在同一个事务,要么都成功,要么都失败
- 方案3:订单创建成功后,通过MQ异步扣积分,支持重试
案例2:类内部方法调用导致事务失效
问题背景: 用户注册时,明明加了@Transactional,但数据还是插入了一半。
问题代码:
@Service
public class UserService {
public void register(UserRegisterDTO dto) {
// 调用本类的事务方法
this.saveUser(dto); // ❌ 事务失效
}
@Transactional(rollbackFor = Exception.class)
public void saveUser(UserRegisterDTO dto) {
// 1. 创建用户
userMapper.insert(new User(dto));
// 2. 创建用户详情
userDetailMapper.insert(new UserDetail(dto));
// 3. 初始化账户
accountMapper.insert(new Account(dto));
// 如果这里抛异常,前面的数据不会回滚!
throw new RuntimeException("test");
}
}
问题原因:
this.saveUser()是直接调用,没有通过代理对象- 事务切面无法拦截,事务不生效
解决方案:
// 方案1:拆分到不同类(推荐)
@Service
public class UserService {
@Autowired
private UserTransactionService userTransactionService;
public void register(UserRegisterDTO dto) {
// 跨类调用,事务生效
userTransactionService.saveUser(dto);
}
}
@Service
public class UserTransactionService {
@Transactional(rollbackFor = Exception.class)
public void saveUser(UserRegisterDTO dto) {
userMapper.insert(new User(dto));
userDetailMapper.insert(new UserDetail(dto));
accountMapper.insert(new Account(dto));
}
}
// 方案2:注入自己
@Service
public class UserService {
@Autowired
private UserService self; // 注入代理对象
public void register(UserRegisterDTO dto) {
self.saveUser(dto); // 通过代理调用,事务生效
}
@Transactional(rollbackFor = Exception.class)
public void saveUser(UserRegisterDTO dto) {
userMapper.insert(new User(dto));
userDetailMapper.insert(new UserDetail(dto));
accountMapper.insert(new Account(dto));
}
}
效果:
- 事务生效,要么全部成功,要么全部回滚
八、最佳实践总结
✅ DO(应该做的)
-
明确指定rollbackFor
@Transactional(rollbackFor = Exception.class) -
缩小事务范围
// 只在必要时开启事务 public void process() { // 查询、校验(无需事务) validate(); // 只在更新数据时用事务 updateData(); // 发送通知(无需事务) notify(); } -
设置超时时间
@Transactional(timeout = 30) -
只读事务优化
@Transactional(readOnly = true) public List<Order> query() { ... } -
监控长事务
// 通过日志、监控工具发现长事务
❌ DON'T(不应该做的)
-
不要在事务中调用RPC、HTTP
// ❌ 错误 @Transactional public void process() { orderMapper.insert(order); paymentService.pay(); // 外部调用,可能很慢 } -
不要在事务中执行大量循环
// ❌ 错误 @Transactional public void batchProcess(List<Order> orders) { for (Order order : orders) { // 可能几万条 orderMapper.insert(order); } } // ✅ 正确:分批 -
不要在事务方法中开启新线程
// ❌ 错误 @Transactional public void process() { new Thread(() -> { orderMapper.insert(order); // 事务不会传播到子线程 }).start(); } -
不要滥用事务
// ❌ 错误:只读方法不需要写事务 @Transactional public Order getById(Long id) { return orderMapper.selectById(id); } // ✅ 正确 @Transactional(readOnly = true) public Order getById(Long id) { return orderMapper.selectById(id); }
九、面试回答模板
第一步:表明态度(10秒)
"我在项目中用得比较多,主要使用@Transactional声明式事务。"
第二步:基本使用(30秒)
"最常用的是在Service方法上加@Transactional注解,保证多表操作的原子性。比如创建订单时,需要插入订单表、扣减库存、更新账户,这些操作要么全成功,要么全失败。"
第三步:关键配置(30秒)
"我会特别注意几个属性:rollbackFor设置为Exception.class(因为默认只回滚RuntimeException),timeout设置超时时间避免长事务,readOnly用于查询方法优化性能。"
第四步:实战经验(1-2分钟)
"我踩过几个坑:1)类内部方法调用事务失效,后来拆分到不同类解决;2)异常被捕获导致不回滚,改成重新抛出异常;3)大事务影响性能,优化成只在必要时开启事务。"
第五步:加分项(可选)
"分布式场景下,我们用RocketMQ的事务消息保证最终一致性,比如订单创建成功后,通过MQ异步扣积分、扣库存。"
十、参考资料
- Spring官方文档:docs.spring.io/spring-fram…
- 《Spring实战》第9章:事务管理
- MySQL InnoDB存储引擎事务机制
祝你面试顺利!记住:结合实际项目经验,比单纯背理论更有说服力。