Spring事务
@Transactional
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
@Reflective
public @interface Transactional {
@AliasFor("transactionManager")
String value() default "";
@AliasFor("value")
String transactionManager() default "";
String[] label() default {};
Propagation propagation() default Propagation.REQUIRED;
Isolation isolation() default Isolation.DEFAULT;
int timeout() default TransactionDefinition.TIMEOUT_DEFAULT;
String timeoutString() default "";
boolean readOnly() default false;
Class<? extends Throwable>[] rollbackFor() default {};
String[] rollbackForClassName() default {};
Class<? extends Throwable>[] noRollbackFor() default {};
String[] noRollbackForClassName() default {};
}
1. value/transactionManager (事务管理器)
- 指定使用的事务管理器Bean名称
- 适用场景:多数据源配置时需要明确指定
- 示例:@Transactional("accountTransactionManager")
@Transactional("accountTransactionManager")
public void createAccount(Account account) {
accountMapper.insert(account);
}
@Transactional("orderTransactionManager")
public void createOrder(Order order) {
orderMapper.insert(order);
}
// order表跟account表 是不同的数据库,所以需要指定相应事务管理器
2. propagation (传播行为)
- 控制事务边界的传播方式,7种可选值:
┌── REQUIRED(默认): 存在事务则加入,否则新建
├── REQUIRES_NEW : 始终新建事务,暂停当前事务
├── NESTED : 嵌套事务(需要数据库支持)
├── SUPPORTS : 有事务则加入,没有则非事务运行
├── NOT_SUPPORTED : 非事务执行,暂停当前事务
├── NEVER : 强制非事务执行,存在事务则抛异常
└── MANDATORY : 必须存在事务,否则抛异常
3. isolation (隔离级别)
- 设置事务隔离级别,4种可选值:
┌── DEFAULT : 使用数据库默认级别
├── READ_UNCOMMITTED: 可能读到未提交修改
├── READ_COMMITTED : 防止脏读(多数数据库默认)
├── REPEATABLE_READ: 防止不可重复读
└── SERIALIZABLE : 完全串行化(性能最低)
4. timeout/timeoutString (超时时间)
- 事务超时时间(单位:秒/字符串形式)
- 超过时间未完成则自动回滚
- 示例:@Transactional(timeout = 30)
5. readOnly (只读模式)
- @Transactional 不管 readOnly 是 true 还是 false,事务都是会开启的。
- 如果 readOnly 为 true,Spring 会在事务提交前检查是否有写操作,如果有则抛出异常。
例如:
@Transactional(readOnly = true)
public void createAccount(Account account) {
accountMapper.insert(account);
}
- 上面的代码会抛出异常:
org.springframework.dao.TransientDataAccessResourceException:
### Error updating database. Cause: java.sql.SQLException: Connection is read-only. Queries leading to data modification are not allowed
- 这样做的目的是为了防止误操作,因为只读事务不应该包含写操作。
- 还会告诉告诉 JDBC 驱动:将连接设置为 Connection.setReadOnly(true),有些数据库(如 MySQL、Oracle)会基于这个标志做优化
- MyBatis 默认查询不使用事务,可以根据实际情况配置,例如:需要事务内多次查询结果一致(基于隔离级别),可以配置为 true。
6. rollbackFor/rollbackForClassName (回滚规则)
- 示例:@Transactional(rollbackFor = Exception.class)
public boolean rollbackOn(Throwable ex) {
return (ex instanceof RuntimeException || ex instanceof Error);
}
- 默认回滚RuntimeException和Error
7. noRollbackFor/noRollbackForClassName (不回滚规则)
- 指定不触发回滚的异常类型,可以自定义异常类型
- 示例:@Transactional(noRollbackFor = BusinessException.class)
校验事务是否生效
debug 方法: org.springframework.transaction.interceptor.TransactionAspectSupport#invokeWithinTransaction
扩展
锁+事务+消息发送+RPC接口
一般业务来说 都是锁的范围要包含事务的范围,不要等锁释放了,再提交事务;
如果锁是使用注解方式 跟@Transactional 一起使用 要注意注解的执行顺序
-
锁放在最外层(范围最大)
- 锁的范围应尽量小,防止阻塞其他线程。
- 如果锁范围太大,会影响系统吞吐量,甚至引起死锁。
- 应尽量只包住必须保护的共享资源读写部分。
-
数据库操作在事务内
- 事务要负责保证数据库操作的原子性,但事务中不能有长时间阻塞操作(如 RPC、IO、消息)。
- 否则会导致事务持有锁时间过长,阻塞严重。
-
消息发送和RPC 调用
- 这些通常是网络 IO 操作,延迟和失败不可控。
- 如果放在事务内部:RPC 慢 → 阻塞事务
- 更严重的是:RPC 成功了,但事务最终回滚了,导致副作用不可控(幂等、数据错乱)。
// 伪代码结构,逻辑重点展示
public void doBusiness() {
// 获取分布式锁
Lock lock = redissonClient.getLock("lock:key");
lock.lock();
try {
// 开启数据库事务
TransactionStatus tx = transactionManager.getTransaction(...);
try {
// 业务核心逻辑(写DB)
updateDb();
// 提交事务(保证DB状态已确定)
transactionManager.commit(tx);
} catch (Exception e) {
transactionManager.rollback(tx);
throw e;
}
// 事务外执行:RPC 调用 / 消息发送(可用事务消息或最终一致性处理)
mqService.sendMessage();
rpcService.callRemote();
} finally {
// 释放锁
lock.unlock();
}
}
事务不生效
- @Transactional 修饰的方法要为public 且不是final (spring要求被代理方法必须是
public)
错误示例:
@Transactional
void updateData() { ... }
正确示例:
@Transactional
public void updateData() { ... }
- 方法被this调用(注解不生效)
Spring 的事务是通过 AOP 代理实现的,内部方法调用绕过了代理,事务不会生效。
错误示例:
@Transactional
public void methodA() {
methodB(); // methodB 也是 @Transactional,但事务不会生效
}
@Transactional
public void methodB() { ... }
正确示例:
- 通过 AopContext.currentProxy() 手动调用代理对象
- 将 methodB 抽取到别的类中。
((MyService) AopContext.currentProxy()).methodB();
-
事务传播行为设置不当
比如设置了 Propagation.NOT_SUPPORTED、NEVER 等,不参与事务。 -
配置错误,导致 AOP 代理未启用
- 事务管理器未正确配置 (多数据源项目中,如果没有为 @Transactional 指定正确的事务管理器,事务可能不生效。)
- 没有加上 @EnableTransactionManagement(Spring Boot 默认启用)
其他注意点:
- 事务修饰的方法中,又开新线程去
select
,新线程会拿不到数据的 - 事务中避免调用外部接口(RPC接口、消息发送、IO操作等),导致产生大事务,长时间占用数据库连接