Spring--@Transactional解析

时间:2025-04-27 11:17:54

一、@Transactional注解简介

@Transactional是Spring框架中基于 AOP 的一个注解,用于在方法级别控制事务。这个注解告诉Spring框架在方法执行过程中,使用事务管理功能。如果该方法正常执行,则事务将被提交;如果方法抛出异常,则事务将被回滚。

二、@Transactional 工作原理

通过Spring AOP动态代理为标注了@Transactional注解的方法织入切面的增强代码逻辑并生成对应的代理对象,而切面的增强代码就是实现事务的相关操作,再通过这个代理对象去调用将目标方法以及切面中的事务处理逻辑。切面中的事务处理逻辑主要做了以下操作:

  1. 获取方法上标注的注解的元数据,包括传播行为、隔离级别、异常配置等信息。
  2. 通过ThreadLocal获取事务上下文,检查是否已经激活事务。
  3. 如果已经激活事务,则根据传播行为,看是否需要新建事务。
  4. 开启事务,先通过数据库连接池获取链接,关闭链接的autocommit,然后在try catch里反射执行真正的数据库操作,通过异常情况来决定是commit还是rollback。

下面使用伪代码简单描述下@Transactional注解标注的方法具体的执行逻辑。

	@Transactional
    public void updateUser() {
        // 执行业务逻辑
        doUpdate();
    }

    // 等价于
    public void updateUser() {
        //事务管理器-开启事务-拿到一个事务
        ;
        //关闭事务自动提交,需要手动提交
        autoCommit = false;
        public void invoke () {
            try {
                // 执行业务逻辑
                doUpdate();
            } cache(Exception e) {
                // 异常回滚
                Rollback();
            }
            //提交
            commit();
        }
    }

三、@Transactional 常用属性

  1. propagation:定义事务的传播行为。
  2. isolation:定义事务的隔离级别。
  3. rollbackFor:用于指定哪些异常需要回滚事务。
  4. noRollbackFor:用于指定哪些异常不需要回滚事务。
  5. timeout:用于设置事务的超时时间,单位为毫秒。

四、事务的传播机制

事务传播行为是Spring框架中事务管理的一部分,它决定了在一个事务中调用另一个带有@Transactional注解的方法时,事务如何传播。下面是常用的几种传播行为:

(1)REQUIRED

如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。在大多数场景下,使用 REQUIRED 传播行为即可满足需求。

  1. 场景一:当前存在事务
@Transactional(propagation = )
public void methodA() {
	// 更新A表
	updateA();
	// 调用methodB方法
	methodB();
}

@Transactional(propagation = )
public void methodB() {
	// 更新B表
	updateB();
}

当调用methodA时,会开启一个事务A。
在执行methodA时调用methodB方法时,methodB 不会开启新的事务,而是加入到事务A中。
这时两个方法在同一个事务A中,无论哪个方法出现异常,两个方法所做的操作都会回滚。
  1. 场景二:当前没有事务
public void methodA() {
	// 更新A表
	updateA();
	// 调用methodB方法
	methodB();
}

@Transactional(propagation = )
public void methodB() {
	// 更新B表
	updateB();
}

当调用methodA时,不会开启事务,在执行methodA时调用methodB方法时,methodB会开启一个新的事务。
如果methodB执行过程中出现异常,事务B会回滚对B表的更新操作。
但是由于methodA 没有起事务,A表的更新操作并不会回滚。
(2)SUPPORTS

如果当前存在事务,则加入该事务;如果当前没有事务,就以非事务方式执行。这种传播行为适用于不强制要求事务,但可以充分利用现有事务的场景。

  1. 场景一:当前存在事务
@Transactional(propagation = )
public void methodA() {
	// 更新A表
	updateA();
	// 调用methodB方法
	methodB();
}

@Transactional(propagation = )
public void methodB() {
	// 更新B表
	updateB();
}

当调用methodA时,会开启一个新的事务A。
在执行methodA时调用methodB方法时,methodB 不会开启新的事务,而是加入到事务A中。
这时两个方法在同一个事务A中,无论哪个方法出现异常,两个方法所做的操作都会回滚。
  1. 场景二:当前没有事务
public void methodA() {
	// 更新A表
	updateA();
	// 调用methodB方法
	methodB();
}

@Transactional(propagation = )
public void methodB() {
	// 更新B表
	updateB();
}

当调用methodA时,不会开启事务,在执行methodA时调用methodB方法时,methodB也不会开启新的事务。
相当于methodB以普通方法执行,执行过程中出现任何异常都不会回滚。
(3)NOT_SUPPORTED

以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。这种传播行为适用于不需要事务支持的场景。

  1. 场景一:当前存在事务
@Transactional(propagation = )
public void methodA() {
	// 更新A表
	updateA();
	// 调用methodB方法
	methodB();
}

@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void methodB() {
	// 更新B表
	updateB();
}

当调用methodA时,会开启一个事务A,在执行methodA时调用methodB方法,
不会开启新的事务也不会加入事务A。此时事务A会被挂起,直到methodB方法执行完成。
如果methodA执行出现异常,事务A会将methodA执行的操作回滚,methodB方法的执行不受影响。
  1. 场景二:当前没有事务
public void methodA() {
	// 更新A表
	updateA();
	// 调用methodB方法
	methodB();
}

@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void methodB() {
	// 更新B表
	updateB();
}

当调用methodA时,不会开启事务,在执行methodA时调用methodB方法时,methodB也不会开启新的事务。
相当于methodA和methodB都以普通方法执行,执行过程中出现任何异常都不会回滚。
(4)REQUIRES_NEW

创建一个新事务,如果当前存在事务,把当前事务挂起,直到新事务完成。这种传播行为适用于需要独立于当前事务的场景。

@Transactional(propagation = )
public void methodA() {
	// 更新A表
	updateA();
	// 调用methodB方法
	methodB();
}

@Transactional(propagation = Propagation.REQUIRES_NEW)
public void methodB() {
	// 更新B表
	updateB();
}

当调用methodA时,会开启一个事务A,在执行methodA时调用methodB方法,会开启一个新的事务B。
此时事务A会被挂起,直到事务B执行完成。由于methodA和methodB分别在两个不同的事务中执行,
所以任何一个方法中出现异常只会触发当前方法所在事务的回滚操作。
不会影响另一个方法所在事务的回滚操作。
(5)MANDATORY

如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。这种传播行为适用于必须在事务中执行的场景。

  1. 场景一:当前存在事务
@Transactional(propagation = )
public void methodA() {
	// 更新A表
	updateA();
	// 调用methodB方法
	methodB();
}

@Transactional(propagation = )
public void methodB() {
	// 更新B表
	updateB();
}

当调用methodA时,会开启一个事务A,
在执行methodA时调用methodB方法,不会开启新的事务,而是加入事务A。
这时两个方法在同一个事务A中,无论哪个方法出现异常,两个方法所做的操作都会回滚。
  1. 场景二:当前没有事务
public void methodA() {
	// 更新A表
	updateA();
	// 调用methodB方法
	methodB();
}

@Transactional(propagation = )
public void methodB() {
	// 更新B表
	updateB();
}

当调用methodA时,不会开启事务,在执行methodA时调用methodB方法时,methodA会抛出异常,
methodB方法不会执行。也就是说methodB方法必须在存在事务的方法中调用。
(6)NEVER

必须以非事务方式执行,如果当前存在事务,则抛出异常。这种传播行为适用于不能在事务中执行的场景。

  1. 场景一:当前存在事务
@Transactional(propagation = )
public void methodA() {
	// 更新A表
	updateA();
	// 调用methodB方法
	methodB();
}

@Transactional(propagation = )
public void methodB() {
	// 更新B表
	updateB();
}

当调用methodA时,会开启一个事务A,在执行methodA时调用methodB方法,methodA会抛异常,methodB不会执行。也就是说methodB方法不能在存在事务的方法中调用。
  1. 场景二:当前没有事务
public void methodA() {
	// 更新A表
	updateA();
	// 调用methodB方法
	methodB();
}

@Transactional(propagation = )
public void methodB() {
	// 更新B表
	updateB();
}

当调用methodA时,不会开启事务,在执行methodA时调用methodB方法时,
methodB也不会开启事务,methodB以普通方法执行。
(7)NESTED

如果当前存在事务,则以嵌套事务的方式执行;如果不存在事务,则创建一个新的事务。嵌套事务是一种特殊的事务,它可以独立于外部事务进行回滚,但提交时仍依赖于外部事务,并且。这种传播行为适用于需要在单个事务内执行多个独立操作的场景。

  1. 场景一:当前存在事务
@Transactional(propagation = )
public void methodA() {
	// 更新A表
	updateA();
	
	try {
		// 调用methodB方法
		methodB();
	} catch {
	}
}

@Transactional(propagation = )
public void methodB() {
	// 更新B表
	updateB();
}

当调用methodA时,会开启一个事务A。
在执行methodA时调用methodB方法时,methodB 会开启新的嵌套事务A_B。
如果methodA执行过程出现异常,则methodA和methodB都会回滚。
如果methodB执行过程出现异常,methodB方法会回滚,
如果methodA通过try捕获了methodB的异常,则methodA能正常执行完,不会回滚。
如果methodA没有捕获了methodB的异常,methodA也会回滚。
  1. 场景二:当前没有事务
public void methodA() {
	// 更新A表
	updateA();
	// 调用methodB方法
	methodB();
}

@Transactional(propagation = )
public void methodB() {
	// 更新B表
	updateB();
}

当调用methodA时,不会开启事务,在执行methodA时调用methodB方法时,methodB会开启一个新的事务。
如果methodB执行过程中出现异常,事务B会回滚对B表的更新操作。
但是由于methodA 没有起事务,A表的更新操作并不会回滚。

五、事务的隔离级别

  1. ISOLATION_DEFAULT:使用底层数据库的默认隔离级别。是spring默认设置。
  2. ISOLATION_READ_UNCOMMITTED:可以读取其他事务未提交的数据。这是最低的隔离级别,可能会导致脏数据问题。
  3. ISOLATION_READ_COMMITTED:只能读取其他事务已提交的数据,可以解决脏读;但也能读取到在当前事务开始之后提交的数据,可能会出现不可重复读的问题。
  4. ISOLATION_REPEATABLE_READ:可以重复读取当前事务开始之前其他事务已提交的数据,之后其他事务提交的数据读取不到,可以避免脏读以及不可重复读的问题,但是可能会出现幻读的问题。
  5. ISOLATION_SERIALIZABLE:完全序列化。这是最高的隔离级别,能确保在同一时刻,只能有一个事务访问数据库。

六、@Transactional失效场景

  1. 方法的可见性:
    • @Transactional注解的方法必须是public的,如果方法是private、protected或package-private,事务将不会生效。其原因主要与 Spring AOP(面向切面编程)的代理机制有关,Spring AOP 默认使用 JDK 动态代理或 CGLIB 代理来创建代理对象,而这两种代理机制对方法的可见性有不同的要求:
      • JDK 动态代理:这种代理机制要求被代理的方法必须是接口中定义的方法。因此,只有 public 方法才能被代理,因为接口中的方法默认是 public 的。
      • CGLIB 代理:这种代理机制通过生成目标类的子类来实现代理,因此理论上可以代理非 public 方法。然而,Spring 的事务管理默认只对 public 方法进行事务增强。这是因为事务管理通常用于服务层,而服务层的方法通常是 public 的,以便被其他层调用。
  2. 自调用:
    • 当一个类中的方法调用同一个类中的另一个@Transactional方法时,事务不会生效。这是因为Spring的事务管理是通过AOP代理实现的,自调用不会经过代理。
  3. 非Spring管理的对象:
    • 如果@Transactional注解应用在非Spring管理的对象上,事务将不会生效。只有Spring容器管理的Bean才能支持事务。
  4. 异常处理:
    • 默认情况下,只有未捕获的运行时异常(RuntimeException)或错误(Error)会导致事务回滚。检查异常(Checked Exception)不会触发回滚,除非在@Transactional注解中明确指定rollbackFor属性。
  5. 事务传播行为:
    • 如果事务传播行为设置不当,可能导致事务失效。例如,使用Propagation.REQUIRES_NEW会导致当前事务被挂起,开启一个新的事务。
  6. 数据库不支持事务:
    • 如果底层数据库不支持事务(如某些NoSQL数据库),则@Transactional将不起作用。
  7. 多线程环境:
    • @Transactional不适用于多线程环境,因为事务上下文是线程绑定的。