@Transactional
是 Java 中用于声明式事务管理的关键注解,通常与 Spring 框架一起使用。它允许开发者通过简单的注解方式来管理事务,而无需手动编写事务控制代码(如 beginTransaction()
、commit()
、rollback()
等)。
一、基本概念
事务(Transaction) 是数据库操作的基本单位,它具有以下四个特性,通常称为 ACID 特性:
-
原子性(Atomicity):事务中的所有操作要么全部成功,要么全部失败回滚。
-
一致性(Consistency):事务执行前后,数据库从一个一致状态转变到另一个一致状态。
-
隔离性(Isolation):多个事务并发执行时,一个事务的执行不应影响其他事务。
-
持久性(Durability):一旦事务提交,其结果就是永久性的,即使系统崩溃也不会丢失。
@Transactional
注解的作用就是让方法或类在运行时自动具备事务管理的能力,确保上述 ACID 特性得以实现。
二、@Transactional
的使用场景
@Transactional
主要用于以下场景:
-
数据库的增删改操作(如
INSERT
、UPDATE
、DELETE
),尤其是需要保证数据一致性的业务逻辑。 -
需要在多个数据库操作之间保持原子性的业务方法。
-
需要处理并发访问时的数据一致性问题。
注意 :@Transactional
通常用于服务层(Service Layer),而不是控制层(Controller Layer)或数据访问层(DAO Layer),以实现业务逻辑的事务管理。
三、@Transactional
的基本用法
1. 在方法上使用
在需要事务管理的方法上添加 @Transactional
注解,Spring 会自动为该方法创建和管理事务。
java
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
@Transactional
public void createUser(User user) {
// 业务逻辑
userRepository.save(user);
// 其他数据库操作
}
}
createUser
方法被 @Transactional
注解标记,Spring 会在方法执行前 开启事务,方法执行成功后提交事务,如果方法抛出未捕获的异常,则回滚事务。
2. 在类上使用
将 @Transactional
注解应用于整个类,表示该类中的所有公共方法都默认具有事务管理的能力。
java
@Service
@Transactional
public class UserService {
@Autowired
private UserRepository userRepository;
public void createUser(User user) {
userRepository.save(user);
}
public void updateUser(User user) {
userRepository.update(user);
}
}
注意:在类级别使用 @Transactional时,如果某个方法需要不同的事务配置,可以在方法级别重新声明 @Transactional以覆盖类级别的配置。
四、@Transactional
的配置属性
@Transactional
注解提供了多个属性,允许开发者根据具体需求进行细粒度的事务配置:
属性 | 类型 | 说明 |
---|---|---|
value / transactionManager |
String | 指定事务管理器的 Bean 名称。默认使用 Spring 容器中配置的事务管理器。 |
propagation |
Propagation | 事务的传播行为,定义事务的嵌套规则。 |
isolation |
Isolation | 事务的隔离级别,定义事务之间的可见性。 |
timeout |
int | 事务的超时时间(以秒为单位),超时后事务将被回滚。 |
readOnly |
boolean | 是否为只读事务。只读事务通常用于查询操作,可以提高性能。 |
rollbackFor |
Class<? extends Throwable>[] | 指定哪些异常类型会触发事务回滚。默认情况下,仅对未检查异常(继承自 RuntimeException )和 Error 进行回滚。 |
noRollbackFor |
Class<? extends Throwable>[] | 指定哪些异常类型不会触发事务回滚。 |
1. 传播行为(Propagation)
传播行为定义了事务的嵌套规则,即当一个事务方法调用另一个事务方法时,事务应该如何传播。Spring 提供了以下几种传播行为:
-
Propagation.REQUIRED
(默认):如果当前存在事务,则加入该事务;如果不存在,则新建一个事务。 -
Propagation.SUPPORTS
:如果当前存在事务,则加入该事务;如果不存在,则以非事务方式执行。 -
Propagation.MANDATORY
:如果当前存在事务,则加入该事务;如果不存在,则抛出异常。 -
Propagation.REQUIRES_NEW
:无论当前是否存在事务,都新建一个事务,并挂起当前事务(如果存在)。 -
Propagation.NOT_SUPPORTED
:以非事务方式执行,如果当前存在事务,则挂起当前事务。 -
Propagation.NEVER
:以非事务方式执行,如果当前存在事务,则抛出异常。 -
Propagation.NESTED
:如果当前存在事务,则在嵌套事务内执行;如果不存在,则新建一个事务。嵌套事务可以独立于外部事务进行提交或回滚,但外部事务回滚时,嵌套事务也会回滚。
java
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void performIndependentOperation() {
// 该方法将在一个新的事务中执行,与调用者的事务独立
}
2. 隔离级别(Isolation)
隔离级别定义了一个事务与其他并发事务之间的可见性,防止并发事务之间的干扰。Spring 支持以下几种隔离级别:
-
Isolation.DEFAULT
(默认):使用底层数据库的默认隔离级别。 -
Isolation.READ_UNCOMMITTED
:允许读取未提交的数据变更,可能导致脏读、不可重复读和幻读。 -
Isolation.READ_COMMITTED
:只允许读取已提交的数据,防止脏读,但可能出现不可重复读和幻读。 -
Isolation.REPEATABLE_READ
:确保在同一事务内多次读取同一数据时结果一致,防止脏读和不可重复读,但可能出现幻读。 -
Isolation.SERIALIZABLE
:最高的隔离级别,通过完全锁定相关数据来防止脏读、不可重复读和幻读,但性能较低。
java
@Transactional(isolation = Isolation.READ_COMMITTED)
public void updateUserData() {
// 该方法将在 READ_COMMITTED 隔离级别下执行
}
3. 超时时间(Timeout)
timeout
属性用于设置事务的超时时间(以秒为单位)。如果事务在指定的时间内未完成,则会被自动回滚。
java
@Transactional(timeout = 30)
public void longRunningProcess() {
// 该方法的事务超时时间为 30 秒
}
4. 回滚规则(Rollback For / No Rollback For)
-
rollbackFor
:指定哪些异常类型会触发事务回滚。默认情况下,仅对未检查异常(继承自RuntimeException
)和Error
进行回滚。如果需要对某些已检查异常(继承自Exception
)也进行回滚,可以通过此属性指定。 -
noRollbackFor
:指定哪些异常类型不会触发事务回滚。即使这些异常被抛出,事务也不会回滚。
java
@Transactional(rollbackFor = {CustomException.class}, noRollbackFor = {IllegalArgumentException.class})
public void processWithCustomRules() throws CustomException {
// 该方法在抛出 CustomException 时会回滚事务,而抛出 IllegalArgumentException 时不会回滚
}
五、@Transactional
的工作原理
Spring 的 @Transactional
注解依赖于 AOP(面向切面编程) 来实现事务管理。具体来说,Spring 通过动态代理(JDK 动态代理或 CGLIB)在运行时为目标方法创建代理对象,代理对象在调用目标方法前后插入事务管理的逻辑。
@Transactional
的大致工作流程:
-
代理创建 :Spring 容器在启动时,会扫描带有
@Transactional
注解的类,并为这些类创建代理对象。代理对象会拦截对目标方法的调用。 -
事务拦截 :当调用被
@Transactional
注解标记的方法时,代理对象首先会检查是否存在活动的事务。根据propagation
属性的配置,决定是加入现有事务、新建事务还是以非事务方式执行。 -
事务开启 :如果需要新建事务,代理对象会通过配置的事务管理器(如
DataSourceTransactionManager
)开启一个新的事务。 -
方法执行:代理对象调用目标方法,执行实际的业务逻辑。
-
事务提交或回滚:
-
如果方法执行成功且没有抛出异常(或抛出的异常不在
noRollbackFor
列表中),代理对象会提交事务。 -
如果方法抛出异常(且该异常在
rollbackFor
列表中或属于默认回滚的异常类型),代理对象会回滚事务。
-
-
资源释放:事务提交或回滚后,代理对象会释放相关的事务资源(如数据库连接)。
注意 :由于 @Transactional
基于 AOP 实现,因此它只能拦截 public 方法的事务。对于非 public 方法(如 private、protected 或包级私有方法),@Transactional
注解将不会生效。
六、@Transactional
的注意事项
在使用 @Transactional
时,有一些常见的注意事项和常见问题需要了解,以避免潜在的错误和误解。
1. 自调用问题
由于 @Transactional
基于 AOP 实现,它只能拦截通过代理对象调用的方法 。如果在同一个类中,一个方法调用另一个带有 @Transactional
注解的方法(即自调用),事务注解将不会生效,因为调用是通过 this
关键字直接进行的,而不是通过代理对象。
java
@Service
public class UserService {
@Transactional
public void methodA() {
// 业务逻辑
methodB(); // 自调用,事务注解不生效
}
@Transactional
public void methodB() {
// 业务逻辑
}
}
解决方案:
-
将
methodB
移到另一个服务类中,通过依赖注入调用,以确保通过代理对象调用。 -
使用
AopContext.currentProxy()
获取当前代理对象进行调用(需要开启exposeProxy
配置)。
java
@Service
public class UserService {
@Autowired
private UserService self; // 注入自身代理
@Transactional
public void methodA() {
// 业务逻辑
self.methodB(); // 通过代理对象调用
}
@Transactional
public void methodB() {
// 业务逻辑
}
}
java
@Service
public class UserService {
@Transactional
public void methodA() {
// 业务逻辑
((UserService) AopContext.currentProxy()).methodB(); // 获取当前代理对象并调用
}
@Transactional
public void methodB() {
// 业务逻辑
}
}
注意 :使用 AopContext.currentProxy()
需要在 Spring 配置中开启 exposeProxy
,例如在 XML 配置中添加 <aop:aspectj-autoproxy expose-proxy="true"/>
,或在 Java 配置类中设置 @EnableAspectJAutoProxy(exposeProxy = true),需要引入依赖aspectjweaver
。
java
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.7</version>
</dependency>
2. 异常处理
默认情况下,@Transactional
只对未检查异常(继承自 RuntimeException
)和 Error
进行回滚。如果方法抛出已检查异常(继承自 Exception
),事务不会回滚,除非通过 rollbackFor
属性显式指定。