Trasactional在java中作为事务的注解被广泛使用,本文将简单介绍它的原理以及注意事项,某些情况下会失效,以更合理的使用该注解。
1. 原理
1.1 事务的基本概念与 ACID 特性
事务是数据库操作的最小逻辑单元,确保一组操作要么全部成功,要么全部失败。其核心特性 ACID 包括:
-
原子性(Atomicity):事务中的操作不可分割,要么全做,要么全不做。
-
一致性(Consistency):事务执行前后,数据从一个合法状态转换到另一个合法状态。
-
隔离性(Isolation):多个事务并发执行时,彼此互不干扰。
-
持久性(Durability):事务提交后,数据修改永久保存到数据库。
在 Java 开发中,Spring 框架通过
@Transactional
注解简化了事务管理,其底层依赖数据库提供的事务机制(如 MySQL 的 InnoDB 引擎支持事务)和 Spring AOP 实现逻辑增强。1.2 @Transactional 的实现机制
1.2.1 代理模式与 AOP
Spring 通过 AOP(面向切面编程)实现事务增强,核心流程如下:
-
代理创建:当 Spring 容器scan到
@Transactional
注解时,会为目标 Bean 生成代理对象(JDK 动态代理或 CGLIB 代理)。 -
切面逻辑:在代理对象的方法调用前后,织入事务开启、提交、回滚等逻辑。
-
调用链:客户端通过代理对象调用目标方法时,实际执行的是被事务逻辑包装的增强方法。
1.2.2 关键类与接口
-
PlatformTransactionManager:事务管理器接口,定义了事务操作的核心方法(
getTransaction
、commit
、rollback
)。-
DataSourceTransactionManager:基于数据源的事务管理器,适用于 JDBC 或 MyBatis 操作。
-
JpaTransactionManager:适用于 JPA 或 Hibernate 等 ORM 框架。
-
-
TransactionAttribute:事务属性接口,封装了事务的传播行为、隔离级别、超时时间等配置。
-
TransactionStatus:事务状态接口,用于记录事务的执行状态(是否新事务、是否已提交等)。
2.失效场景
2.1 spring未管理到所在类
目标类未使用
@Component
、@Service
等注解,未纳入 Spring 容器。解决方法:对目标类正确的添加相应的Spring容器注解
2.2 方法非public
Transactional
的生效要求是public方法解决方法:对该事务方法添加public;
2.3 作用在私有方法上
@Transactional
修饰的方法不能是 private
,因为Spring无法对私有方法生成动态代理;解决方法:修改方法类型为类的public方法。
2.4 作用在static/final 方法上
CGLIB是通过生成目标类子类的方式生成代理类的,被final、static修饰后,无法继承父类与父类的方法。
解决方法:对需要事务的逻辑抽成类的方法,不使用final关键字。
2.5 方法被所在类其它方法引用
事务的管理是通过代理执行的方式生效的,如果是方法内部调用,将不会走代理逻辑,也就调用不到了。
解决方法:
-
将事务方法单独抽到新的类中,在原来的class里进行引用,可以被代理;
-
在原类中注入自身,使用该事务方法的地方,不是直接声明方法名,而是调用自身的该方法,以此完成代理。
2.6 抛错异常不符合约定
默认情况下事务仅回滚RuntimeException和Error,不回滚受检异常(例如IOException),因此如果抛出业务自定义的异常,可能无法事务回滚。
解决方法:配置rollback参数,将需要回滚的异常声明在注解中。
2.7 异常被内部捕获未抛出
同前一情况相反,出错而不抛出异常,则对注解来说是正常执行的逻辑,不会进行事务回滚。
解决方法:重新梳理业务逻辑,将需要事务回滚的异常抛出,并声明为rollback的类型;
2.8 逻辑中使用了多线程
因为Transactional是和线程绑定的,因此逻辑内部如果新开了线程,则原子性失效。
解决方法:梳理事务与多线程之间的关系,尽量规避在多线程中处理事务。