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 一起使用 要注意注解的执行顺序

  1. 锁放在最外层(范围最大)

    • 锁的范围应尽量小,防止阻塞其他线程。
    • 如果锁范围太大,会影响系统吞吐量,甚至引起死锁。
    • 应尽量只包住必须保护的共享资源读写部分。
  2. 数据库操作在事务内

    • 事务要负责保证数据库操作的原子性,但事务中不能有长时间阻塞操作(如 RPC、IO、消息)。
    • 否则会导致事务持有锁时间过长,阻塞严重。
  3. 消息发送和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();
    }
}

事务不生效

  1. @Transactional 修饰的方法要为public 且不是final (spring要求被代理方法必须是
    public)

错误示例:

@Transactional
void updateData() { ... }

正确示例:

@Transactional
public void updateData() { ... }
  1. 方法被this调用(注解不生效)
    Spring 的事务是通过 AOP 代理实现的,内部方法调用绕过了代理,事务不会生效。

错误示例:

@Transactional
public void methodA() {
    methodB(); // methodB 也是 @Transactional,但事务不会生效
}

@Transactional
public void methodB() { ... }

正确示例:

  • 通过 AopContext.currentProxy() 手动调用代理对象
  • 将 methodB 抽取到别的类中。
((MyService) AopContext.currentProxy()).methodB();
  1. 事务传播行为设置不当
    比如设置了 Propagation.NOT_SUPPORTED、NEVER 等,不参与事务。

  2. 配置错误,导致 AOP 代理未启用

  • 事务管理器未正确配置 (多数据源项目中,如果没有为 @Transactional 指定正确的事务管理器,事务可能不生效。)
  • 没有加上 @EnableTransactionManagement(Spring Boot 默认启用)

其他注意点:

  1. 事务修饰的方法中,又开新线程去select ,新线程会拿不到数据的
  2. 事务中避免调用外部接口(RPC接口、消息发送、IO操作等),导致产生大事务,长时间占用数据库连接
posted @ 2023-11-17 11:28  Eiffelzero  阅读(34)  评论(0)    收藏  举报