主要是今天遇见使用TransactionSynchronizationManager出现的事务问题
TransactionSynchronizationManager
是一个事务管理的核心类,通过TransactionSynchronizationManager
我们可以管理当前线程的事务。而很多时候我们使用这个类是为了方便我们在事务结束或者开始之前实现一些自己的逻辑。
类似下面的逻辑我们希望在事务结束后再执行某些业务。所以可以使用TransactionSynchronizationManager.registerSynchronization
通过实现TransactionSynchronization
接口的不同功能来实现在事务不同阶段执行不同逻辑。
@Service
@Transactional
@Slf4j
public class UserService {
@Autowired
private UserRepository userRepository;
@Autowired
private OrderService orderService;
public User addUser(User user) {
log.info("任务开始!");
userRepository.save(user);
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
@Override
public void beforeCommit(boolean readOnly) {
log.info("事务开始提交");
}
@Override
public void afterCommit() {
log.info("事务提交结束");
orderService.addOrder();
}
});
log.info("任务已经结束!");
return user;
}
}
一般情况这样执行时可以的。但是当我们在afterCommit
方法中执行的任务也是包含事务的,比如下面情况orderService.addOrder()
自身是一个使用JPA进行保存数据的事务方法。
@Service
@Transactional
@Slf4j
public class OrderService {
@Autowired
private OrderRepository orderRepository;
@Transactional
public Order addOrder() {
log.info("开始插入order数据");
Order order = new Order();
order.setMoney(100D);
orderRepository.save(order);
log.info("插入order数据结束");
return order;
}
}
此时会发现orderRepository.save(order)虽然尝试保存了数据,但是最终数据没有被保存到数据库中。
可能的原因
下面的内容并非是通过源码的分析得出的结果。而是个人经验总结,所以最后虽然解决自己遇见的问题但是可能并非可靠的。
在addUser
和addOrder
方法中添加下面代码,具体来打印当前事务。
String currentTransactionName = TransactionSynchronizationManager.getCurrentTransactionName();
log.info("currentTransactionName is {}",currentTransactionName);
最终可以得到下面结果
2021-01-05 23:28:28.669 INFO 12908 --- [ main] dai.samples.jpa2.service.UserService : currentTransactionName is dai.samples.jpa2.service.UserService.addUser
2021-01-05 23:28:28.673 INFO 12908 --- [ main] dai.samples.jpa2.service.OrderService : currentTransactionName is dai.samples.jpa2.service.UserService.addUser
两个方法使用了相同的事务,但是需要注意的是addOrder
方法是在afterCommit
事务提交之后执行的,此时会导致addOrder
中的JPA数据保存最终无法提交。所以我们需要使addOrder
进入一个新的事务中。
解决办法
在@Transactional
注解中propagation
参数用来控制事务的传播。其默认被设置为Propagation.REQUIRED
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Transactional {
.....
/**
*/
Propagation propagation() default Propagation.REQUIRED;
......
}
Propagation.REQUIRED
其逻辑是,如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。而上面的业务中我们并不希望其加入已有的事务中,所以单介绍上面的逻辑,假如希望JPA的数据保存到数据库中,需要在事务注解修改为@Transactional(propagation = Propagation.REQUIRES_NEW)
参数
@Service
@Transactional
@Slf4j
public class OrderService {
@Autowired
private OrderRepository orderRepository;
@Transactional(propagation = Propagation.REQUIRES_NEW)
public Order addOrder() {
log.info("开始插入order数据");
Order order = new Order();
order.setMoney(100D);
orderRepository.save(order);
log.info("插入order数据结束");
return order;
}
}
PS.然而在很多时候我们希望新加入的方法能够被同一个事务所管理,而使用Propagation.REQUIRES_NEW
会导致当前操作脱离上一级事务的控制。所以在使用@Transactional(propagation = Propagation.REQUIRES_NEW)
的时候一定要慎重,并且严格控制其被滥用。