1. 使用场景
@Transactional
注解主要用于需要确保数据一致性和完整性的场景,特别是在涉及多个数据库操作的场景中,如:
- 读写数据库:通常用于增、删、改等涉及数据修改的操作,确保这些操作要么全部成功,要么全部失败。
- 批量操作:如批量插入或更新记录,一旦其中一条记录操作失败,全部操作都会回滚。
- 复杂业务逻辑:当多个数据库表之间存在关联时,比如涉及订单、支付等业务场景时,需要保持数据一致性。
2. 使用限制
@Transactional
注解在使用时,有一些限制或注意事项:
- 适用于公有方法:Spring 的事务管理默认是基于 AOP 代理机制,因此
@Transactional
只能应用在public
方法上。如果在private
或protected
方法上使用,则不会生效。 - 同类方法调用:如果在同一个类中调用带有
@Transactional
注解的方法,事务将不会生效,因为代理无法在类内部调用时生效。 - 只支持单个数据库:在使用
DataSourceTransactionManager
时,事务管理默认只支持单个数据源。如果涉及到多个数据库操作,建议使用分布式事务管理框架(如 Spring Cloud 的 Seata 或 JTA)。
3. 属性详解
@Transactional
注解提供了一些常用属性,用于更细粒度地控制事务行为:
- propagation:
REQUIRED
(默认值):如果当前有事务存在,则加入当前事务;如果没有事务,则创建新事务。
REQUIRES_NEW
:无论当前是否有事务,都会创建新事务,并且暂停当前事务。
NESTED
:嵌套事务,内部事务可以独立回滚,但依赖于外部事务的提交。
SUPPORTS
:如果当前有事务,则加入事务;如果没有事务,则以非事务方式执行。
NOT_SUPPORTED
:以非事务方式执行,并暂停当前事务。
NEVER
:以非事务方式执行,如果当前有事务,则抛出异常。
MANDATORY
:必须在一个现有事务中执行,如果没有事务,则抛出异常。 - isolation:定义了数据库操作的隔离性,避免并发问题。常见的隔离级别:
DEFAULT
:使用底层数据库的默认隔离级别。
READ_UNCOMMITTED
:允许读取未提交的数据,可能会导致脏读。
READ_COMMITTED
:只能读取已提交的数据,避免脏读。
REPEATABLE_READ
:在事务期间多次读取相同数据时,结果是一致的,避免不可重复读。
SERIALIZABLE
:最高级别的隔离,完全避免并发问题,但可能导致性能下降。 - timeout:设置事务超时时间,超过此时间后,事务会被强制回滚。单位为秒。
- readOnly:如果设置为
true
,则表示该事务为只读事务,用于优化性能。常用于只进行查询的操作。 - rollbackFor:指定抛出哪些异常时,事务应该回滚。默认情况下,Spring 只在抛出
RuntimeException
或Error
时回滚。 - noRollbackFor:指定抛出哪些异常时,事务不应该回滚。
4. 使用方式
通常有三种方式使用 @Transactional
注解:
4.1. 在类或方法上
在类上声明 @Transactional
表示该类中的所有方法都将参与事务管理。如果只希望特定方法参与事务管理,可以只在方法上添加。
@Service
@Transactional
public class UserService {
public void saveUser(User user) {
// 数据库操作
}
@Transactional(readOnly = true)
public User findUserById(Long id) {
// 只读操作
}
}
4.2. 通过 XML 配置事务
可以通过 XML 来定义事务配置(较少使用)。
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="*" propagation="REQUIRED" />
<tx:method name="find*" read-only="true" />
</tx:attributes>
</tx:advice>
4.3. 通过编程式事务管理
除了使用注解和 XML 配置,还可以通过 Spring 提供的编程式 API 手动控制事务。在某些复杂场景中,例如需要动态地控制事务的行为时,编程式事务管理可能更加灵活。
开发者可以使用 TransactionTemplate
来手动控制事务的开始、提交和回滚。
@Service
public class UserService {
@Autowired
private TransactionTemplate transactionTemplate;
public void saveUser(User user) {
transactionTemplate.execute(status -> {
try {
// 数据库保存操作
return true;
} catch (Exception e) {
status.setRollbackOnly(); // 手动触发回滚
throw e;
}
});
}
}
5. 注意事项
- 异常类型影响回滚:默认情况下,Spring 只会对
RuntimeException
和Error
类型的异常回滚。对于Checked Exception
(受检异常),事务不会自动回滚,除非你显式配置rollbackFor
属性。 - 手动管理事务时避免混用:如果你手动管理事务(如使用
TransactionTemplate
),那么在这些方法中不要混用@Transactional
,否则会导致不一致行为。 - 只读事务的性能优化:如果明确某些方法仅用于查询,可以设置
@Transactional(readOnly = true)
,这会帮助底层数据库优化性能(如避免加锁)。
6. 常见异常
-
TransactionRequiredException
:当一个需要事务支持的方法在没有事务的环境下执行时,抛出此异常。通常是由于事务传播行为不正确或事务管理器未正确配置。 UnexpectedRollbackException
:当一个事务在提交时检测到已经回滚时抛出此异常。可能是因为嵌套事务或其他操作导致事务回滚。-
OptimisticLockingFailureException
:在乐观锁的场景下,如果两个事务并发更新同一条记录,并且其中一个事务提交了数据,另一个事务则会抛出该异常。
7. 最佳实践
- 区分读写操作:使用
readOnly = true
来优化只读查询操作,避免不必要的事务开销。
@Service
public class UserService {
@Transactional(readOnly = true)
public User findUserById(Long id) {
// 查询操作,标记为只读事务
return userRepository.findById(id);
}
@Transactional
public void saveUser(User user) {
// 写操作,进行插入或更新
userRepository.save(user);
}
}
findUserById
方法被标记为 readOnly = true
,Spring 将优化事务管理,避免不必要的加锁和其他资源开销。
- 合适的传播行为:在编写多个事务嵌套调用的方法时,仔细选择传播行为,尤其是在需要多个事务独立回滚或提交的场景下。
当你在一个方法中调用另一个有事务控制的方法时,如果子方法中的事务失败不应该影响到父方法,可以使用REQUIRES_NEW
。
@Service
public class OrderService {
@Transactional
public void processOrder(Order order) {
// 处理订单逻辑
saveOrder(order);
try {
// 尝试发送通知,即使通知失败,订单也应该提交
sendNotification(order);
} catch (Exception e) {
// 处理通知发送失败的情况
}
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void sendNotification(Order order) {
// 发送通知的逻辑,可能会抛出异常
notificationService.send(order);
}
}
processOrder
方法会处理订单逻辑并提交订单,同时调用 sendNotification
。即使 sendNotification
方法抛出异常,因为它使用 REQUIRES_NEW
传播级别,不会影响 processOrder
的事务提交。
- 合理使用隔离级别:根据业务场景选择适合的隔离级别,避免性能瓶颈或不必要的锁。
@Service
public class BankService {
@Transactional(isolation = Isolation.REPEATABLE_READ)
public Account getAccountBalance(Long accountId) {
// 获取账户余额,防止不可重复读
return accountRepository.findById(accountId);
}
}
getAccountBalance
方法使用 REPEATABLE_READ
隔离级别,确保在事务中多次读取同一账户余额时,返回的数据一致,防止不可重复读的问题。
- 捕获异常处理:尽量不要在事务方法内部捕获并吞掉
RuntimeException
,否则可能会导致事务未回滚。
@Service
public class PaymentService {
@Transactional
public void processPayment(Payment payment) {
try {
// 处理支付
paymentRepository.save(payment);
} catch (RuntimeException e) {
// 不要吞掉异常,否则事务不会回滚
throw e;
}
}
}
processPayment
方法中,如果捕获 RuntimeException
,要继续抛出异常,否则事务不会回滚,从而可能导致数据库数据不一致。
- 注解事务粒度:尽量将事务注解放置在最顶层的方法上,避免内嵌事务带来的复杂性。
@Service
public class PurchaseService {
@Transactional
public void completePurchase(Purchase purchase) {
// 调用多个子方法,保持事务的一致性
validatePurchase(purchase);
processPayment(purchase.getPayment());
saveOrder(purchase.getOrder());
}
}
completePurchase
是整个购买流程的顶层方法,所有的子操作都在这个事务中执行,这样可以确保整个操作要么全部成功,要么全部回滚。