spring事务 @Transactional
spring事务 @Transactional
文章目录
1. 简介
1.1 什么是事务
在计算机科学中,事务通常指的是一系列操作,这些操作作为一个整体一起执行,要么全部成功,要么全部失败。事务是保证数据一致性的一种机制,它有四个基本特性,通常被称为ACID属性:
- 原子性(Atomicity):事务是一个原子操作单元,其对数据的修改要么全都执行,要么全都不执行。
- 一致性(Consistency):事务必须使数据库从一个一致性状态变换到另一个一致性状态。
- 隔离性(Isolation):在并发环境中,一个事务的执行不应该被其他事务干扰。即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰。
- 持久性(Durability):一旦事务完成(即成功提交),其结果就必须能够永久保存在数据库中。
1.2 什么是Spring事务管理
Spring事务管理是Spring框架中的一个重要组成部分,它提供了一种抽象机制来管理事务。Spring事务管理可以处理编程式和声明式的两种事务管理方式。
编程式事务管理:这种方式需要在代码中明确地进行事务管理,包括开始事务、提交事务、回滚事务等。
声明式事务管理:这种方式只需要通过注解或XML配置来管理事务,无需在代码中进行事务管理。这种方式的主要优点是不需要编写大量的事务管理代码,使业务代码更加简洁、清晰。
在Spring中,最常用的声明式事务管理方式就是使用@Transactional
注解。
1.3 @Transactional注解的作用
在Spring框架中,@Transactional
是用于管理事务的关键注解。它可以应用于接口定义、类定义、公开方法上。最常见的使用场景是在服务层的方法上使用@Transactional
注解来控制事务。
当在方法上使用@Transactional
注解时,Spring会为这个方法创建一个代理,当这个方法被调用时,代理会首先启动一个新的事务,然后调用原始的方法。如果这个方法成功完成(即没有抛出异常),代理会提交事务;如果这个方法抛出了异常,代理会回滚事务。
通过使用@Transactional
注解,我们可以很方便地进行事务控制,无需在业务代码中添加大量的事务管理代码,使代码更加简洁、清晰。
2. @Transactional注解的使用
2.1 如何在Spring中使用@Transactional
在Spring框架中,我们可以通过在方法上使用@Transactional
注解来声明该方法是事务性的。当这个方法被调用时,Spring就会为这个方法创建一个事务。
例如,我们可以在一个服务类中的方法上使用@Transactional
注解:
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
@Transactional
public void createUser(User user) {
userRepository.save(user);
}
}
在上述代码中,createUser
方法被@Transactional
注解,所以当调用createUser
方法时,Spring会为这个方法创建一个事务。
2.2 @Transactional的属性配置
@Transactional
注解包含一些属性,我们可以通过设置这些属性来改变事务的行为。
- propagation:事务的传播行为。默认值是
Propagation.REQUIRED
,表示如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。 - isolation:事务的隔离级别。默认值是
Isolation.DEFAULT
,表示使用底层数据库的默认隔离级别。 - timeout:事务的超时时间。默认值是-1,表示永不超时。
- readOnly:是否为只读事务。默认值是false,表示这是一个读写事务。
- rollbackFor:需要进行回滚的异常类数组。默认值是空数组,表示所有的
RuntimeException
及其子类都会导致事务回滚。 - noRollbackFor:不需要进行回滚的异常类数组。默认值是空数组。
例如,我们可以这样配置@Transactional
注解:
@Transactional(propagation = Propagation.REQUIRES_NEW,
isolation = Isolation.READ_COMMITTED,
timeout = 3600,
readOnly = false,
rollbackFor = {CustomException.class},
noRollbackFor = {AnotherCustomException.class})
public void someMethod() {
// ...
}
在上述代码中,我们设置了@Transactional
注解的各种属性,包括传播行为、隔离级别、超时时间、是否只读以及回滚规则。
3. @Transactional的工作原理
3.1 Spring如何管理事务
Spring的事务管理分为编程式事务管理和声明式事务管理两种。编程式事务管理需要在代码中显式地进行事务管理,而声明式事务管理则通过注解或XML配置来管理事务,大大简化了事务管理的复杂性。
Spring的事务管理是通过AOP(面向切面编程)来实现的。当你在一个方法上使用@Transactional
注解时,Spring会在运行时为这个方法创建一个代理对象,这个代理对象会包含事务管理的代码。当你调用这个方法时,实际上是调用了代理对象的方法。
在代理对象的方法中,Spring会首先检查当前是否存在一个事务。如果不存在,Spring会创建一个新的事务。然后,Spring会执行你的方法。如果你的方法执行成功,Spring会提交事务;如果你的方法执行失败,并抛出了未被捕获的异常,Spring会回滚事务。
3.2 @Transactional的底层实现
@Transactional
注解的实现主要依赖于Spring的AOP和事务管理器。
当Spring启动时,它会创建一个AnnotationTransactionAttributeSource
的实例,这个实例会找到所有使用了@Transactional
注解的方法,并为这些方法生成相应的事务属性定义。
然后,Spring会创建一个TransactionInterceptor
的实例,这个拦截器会在运行时拦截到所有的事务方法调用,并根据事务属性定义来决定如何处理事务。
最后,Spring会创建一个ProxyFactoryBean
的实例,这个实例会为所有的事务方法生成代理对象。这些代理对象会在调用事务方法时,首先调用`Transaction
4. @Transactional的传播行为
4.1 传播行为的种类
在Spring的@Transactional
注解中,传播行为(propagation behavior)定义了事务的边界。Spring定义了7种传播行为:
Propagation.REQUIRED
:支持当前事务,如果当前没有事务,就新建一个事务。这是最常见的选择。Propagation.SUPPORTS
:支持当前事务,如果当前没有事务,就以非事务方式执行。Propagation.MANDATORY
:支持当前事务,如果当前没有事务,就抛出异常。Propagation.REQUIRES_NEW
:新建事务,如果当前存在事务,把当前事务挂起。Propagation.NOT_SUPPORTED
:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。Propagation.NEVER
:以非事务方式执行,如果当前存在事务,则抛出异常。Propagation.NESTED
:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与Propagation.REQUIRED
类似的操作。
4.2 每种传播行为的具体作用和使用场景
-
Propagation.REQUIRED
:这是最常用的传播行为。在大多数情况下,我们希望如果一个事务已经存在,那么就在这个事务中执行;否则,就创建一个新的事务。 -
Propagation.SUPPORTS
:这种传播行为在调用者并不一定需要事务,但是如果有现成的事务可以加入,那么就使用它。如果没有事务,那么就按非事务方式执行。 -
Propagation.MANDATORY
:这种传播行为适用于如果没有一个已经存在的事务,那么就抛出异常的情况。也就是说,这个方法必须在一个已经存在的事务中执行。 -
Propagation.REQUIRES_NEW
:这种传播行为在每次都需要创建一个新的事务,并且也会将当前的事务(如果存在)挂起,直到新的事务完成。这种传播行为适用于需要在自己的新事务中执行的情况。 -
Propagation.NOT_SUPPORTED
:这种传播行为适用于不需要事务的情况。如果有一个已经存在的事务,那么这个事务会被挂起,直到这个方法执行完成。 -
Propagation.NEVER
:这种传播行为适用于如果存在一个事务,那么就抛出异常的情况。也就是说,这个方法必须在没有事务的情况下执行。 -
Propagation.NESTED
:这种传播行为适用于需要在一个嵌套事务中执行的情况。如果当前的事务存在,那么就在这个事务的嵌套事务中执行。如果当前的事务不存在,那么就创建一个新的事务。这种传播行为需要特定的事务管理器支持。
5. @Transactional的隔离级别
事务的隔离级别是用来解决事务并发执行时可能出现的问题,如脏读、不可重复读、幻读等。不同的隔离级别提供了不同程度的隔离。在Spring的@Transactional
注解中,可以通过设置隔离级别来控制事务的隔离程度。
5.1 事务的隔离级别有哪些
事务的隔离级别主要有以下四种:
ISOLATION_READ_UNCOMMITTED
(读未提交):这是最低的隔离级别。允许一个事务读取另一个事务未提交的数据。可能导致脏读、不可重复读和幻读。ISOLATION_READ_COMMITTED
(读已提交):这个隔离级别允许一个事务读取另一个事务已提交的数据。可以避免脏读,但仍可能导致不可重复读和幻读。ISOLATION_REPEATABLE_READ
(可重复读):这个隔离级别确保一个事务在整个过程中多次读取同一行数据时,每次读取的结果都是相同的。可以避免脏读和不可重复读,但仍可能导致幻读。ISOLATION_SERIALIZABLE
(串行化):这是最高的隔离级别。它要求事务序列化执行,即一个事务只能在另一个事务完成后才能开始。可以避免脏读、不可重复读和幻读,但执行效率较低。
5.2 如何在@Transactional中设置隔离级别
在@Transactional
注解中,可以通过设置isolation
属性来指定事务的隔离级别。例如:
@Transactional(isolation = Isolation.READ_COMMITTED)
public void someMethod() {
// 业务逻辑
}
在这个例子中,someMethod
方法的事务隔离级别被设置为READ_COMMITTED
。当然,你可以根据实际需求选择其他的隔离级别。需要注意的是,不同的数据库可能支持不同的隔离级别,因此在设置隔离级别时,需要考虑数据库的实际支持情况。
6. @Transactional的只读设置
在Spring的@Transactional
注解中,可以通过设置readOnly
属性来指定事务是否为只读。只读事务可以帮助数据库引擎优化事务,提高查询速度。
6.1 什么是只读事务
只读事务是指该事务只进行数据的读取操作,不进行任何的数据修改操作(包括:INSERT、UPDATE、DELETE等)。在某些场景下,我们知道事务只需要读取数据,不需要修改数据,这时,我们可以将事务设置为只读,以帮助数据库做一些优化,比如避免一些不必要的锁操作,从而提高查询效率。
6.2 如何在@Transactional中设置只读
在@Transactional
注解中,可以通过设置readOnly
属性来指定事务是否为只读。例如:
@Transactional(readOnly = true)
public void someMethod() {
// 业务逻辑
}
在这个例子中,someMethod
方法的事务被设置为只读。这意味着,在这个事务中,我们只能进行数据的查询操作,不能进行数据的修改操作。
需要注意的是,这个只读设置只是一个提示,具体的行为可能会根据实际的事务管理器的实现有所不同。例如,某些事务管理器可能会在只读事务中允许进行数据的修改操作,但这通常不是一个好的做法,因为它可能会导致数据的不一致。所以,当我们将一个事务设置为只读时,我们应该确保在事务中不进行任何数据的修改操作。
7. @Transactional的回滚规则
在Spring的@Transactional
注解中,可以通过设置rollbackFor
和noRollbackFor
属性来自定义事务的回滚规则。回滚规则决定了在哪些异常情况下事务需要回滚,哪些异常情况下事务可以继续提交。
7.1 默认的回滚规则是什么
默认情况下,@Transactional
注解的回滚规则是:当事务中发生运行时异常(RuntimeException
)或者错误(Error
)时,事务会回滚;当发生受检异常(Checked Exception
)时,事务不会回滚。
7.2 如何自定义回滚规则
在@Transactional
注解中,可以通过设置rollbackFor
和noRollbackFor
属性来自定义事务的回滚规则。例如:
@Transactional(rollbackFor = {CustomException.class}, noRollbackFor = {AnotherCustomException.class})
public void someMethod() {
// 业务逻辑
}
在这个例子中,我们为someMethod
方法自定义了回滚规则:
- 当方法抛出
CustomException
异常时,事务会回滚。这里的CustomException
可以是任何继承自Throwable
的自定义异常类。 - 当方法抛出
AnotherCustomException
异常时,事务不会回滚。同样,AnotherCustomException
可以是任何继承自Throwable
的自定义异常类。
通过这种方式,我们可以根据实际需求灵活地自定义事务的回滚规则。需要注意的是,如果同时设置了rollbackFor
和noRollbackFor
,那么noRollbackFor
的优先级更高,即如果一个异常同时满足rollbackFor
和noRollbackFor
的条件,那么事务不会回滚。
8. @Transactional的常见问题
在使用@Transactional
注解时,可能会遇到一些常见的问题。本节将介绍@Transactional
不生效的原因及解决办法,以及使用@Transactional
的注意事项。
8.1 @Transactional不生效的原因及解决办法
8.1.1 原因1:事务管理器配置错误或缺失
如果事务管理器没有正确配置或者缺失,@Transactional
注解将无法正常工作。
解决办法:确保已经正确配置了事务管理器。对于Spring Boot项目,通常会自动配置事务管理器。对于非Spring Boot项目,需要手动配置事务管理器,例如:
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
8.1.2 原因2:使用@Transactional注解的方法在同一个类中调用
当一个没有使用@Transactional
注解的方法调用同一个类中的使用@Transactional
注解的方法时,事务注解不会生效。
解决办法1:将使用@Transactional
注解的方法移到另一个类中,然后在原类中通过依赖注入的方式调用这个方法。例如:
@Service
public class AService {
@Autowired
private BService bService;
public void methodA() {
bService.methodB();
}
}
@Service
public class BService {
@Transactional
public void methodB() {
// 业务逻辑
}
}
解决办法2:将使用 SpringContextUtil.getBean(this.getClass()).methodB()
从spring中再获取一边当前bean的代理对象并调用。例如:
@Service
public class BService {
public void methodA() {
SpringContextUtil.getBean(this.getClass()).methodB();
}
@Transactional
public void methodB() {
// 业务逻辑
}
}
在Spring中,当一个类中的一个方法调用另一个带有@Transactional
注解的方法时,通常情况下,@Transactional
注解不会生效。这是因为Spring事务管理是基于代理的,当一个方法内部调用另一个方法时,实际上是在同一个对象实例上进行的调用,而不是通过代理对象。因此,事务管理功能不会被触发。
然而,通过使用SpringContextUtil.getBean(this.getClass()).methodB();
这种写法,我们可以绕过这个限制。这是因为SpringContextUtil.getBean(this.getClass())
会从Spring容器中获取当前类的代理对象,然后通过这个代理对象调用methodB()
方法。由于调用是通过代理对象进行的,因此@Transactional
注解会生效,触发事务管理功能。
请注意,这种写法虽然可以解决问题,但并不是最佳实践。在实际项目中,我们应该尽量避免在一个类的方法中直接调用另一个带有@Transactional
注解的方法。更好的做法是将这两个方法分别放在不同的服务类中,然后通过依赖注入的方式来调用这些服务类的方法。这样可以更好地遵循单一职责原则,使代码更加清晰和可维护。
8.1.3 原因3:事务传播行为配置不正确
如果事务传播行为配置不正确,可能会导致@Transactional
注解不生效。
解决办法:检查@Transactional
注解的propagation
属性设置,确保它符合实际需求。根据不同的业务场景,选择合适的事务传播行为。
8.2 @Transactional的使用注意事项
-
确保事务管理器配置正确。对于Spring Boot项目,通常会自动配置事务管理器。对于非Spring Boot项目,需要手动配置事务管理器。
-
避免在同一个类中调用使用
@Transactional
注解的方法。将使用@Transactional
注解的方法移到另一个类中,然后在原类中通过依赖注入的方式调用这个方法。 -
根据实际需求,合理设置事务的传播行为、隔离级别、超时时间、只读属性等。
-
不要在
private
、protected
、final
方法上使用@Transactional
注解,因为这些方法无法被代理,导致@Transactional
注解无法生效。 -
如果需要自定义事务的回滚规则,可以通过设置
rollbackFor
和noRollbackFor
属性来实现。注意noRollbackFor
的优先级更高。 -
在使用
@Transactional
注解时,尽量遵循最小化事务范围的原则,即只在需要进行事务控制的方法上使用@Transactional
注解。这样可以降低事务的开销,提高系统性能。
假设我们正在开发一个电商系统,系统中有一个订单服务(OrderService)和库存服务(InventoryService)。当用户下单时,需要同时更新订单和库存信息。为了保证数据的一致性,我们可以使用@Transactional
注解来确保这两个操作在同一个事务中完成。如果其中一个操作失败,整个事务将回滚,从而避免数据不一致的问题。
首先,我们创建订单服务(OrderService)和库存服务(InventoryService):
@Service
public class OrderService {
public void createOrder() {
// 创建订单的逻辑
}
}
@Service
public class InventoryService {
public void updateInventory() {
// 更新库存的逻辑
}
}
9.实例
接下来,我们创建一个业务服务(BusinessService),该服务将负责调用订单服务和库存服务。在该服务中,我们使用@Transactional
注解来确保这两个操作在同一个事务中完成:
@Service
public class BusinessService {
@Autowired
private OrderService orderService;
@Autowired
private InventoryService inventoryService;
@Transactional
public void placeOrder() {
try {
orderService.createOrder();
inventoryService.updateInventory();
} catch (Exception e) {
// 处理异常,例如记录日志等
throw e;
}
}
}
现在,当我们调用BusinessService
的placeOrder
方法时,订单创建和库存更新操作将在同一个事务中进行。如果其中一个操作失败,整个事务将回滚,从而确保数据的一致性。
这个例子展示了如何使用@Transactional
注解解决实际问题。在实际项目中,我们可以根据业务需求灵活地使用@Transactional
注解来控制事务,确保数据的一致性和完整性。
更多推荐
所有评论(0)