日常工作——TransactionSynchronizationManager.registerSynchronization使用中事务传播产生的问题

主要是今天遇见使用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)虽然尝试保存了数据,但是最终数据没有被保存到数据库中

可能的原因

下面的内容并非是通过源码的分析得出的结果。而是个人经验总结,所以最后虽然解决自己遇见的问题但是可能并非可靠的。

addUseraddOrder方法中添加下面代码,具体来打印当前事务。

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)的时候一定要慎重,并且严格控制其被滥用。

上一篇:一文搞懂什么是事务


下一篇:Spring框架(五)--Spring事务管理和Spring事务传播行为