JPA和Mybatis混用事务管理器出现事务问题原理剖析

/**
 * 在spring事务中,默认是使用的DataSourceTransactionManager作为事务管理器
 * 在DataSourceTransactionManager直接是获取JDBC连接就能开启了事务,并且只会将数据源绑定到当前线程中
 * 而在JPA中,事务是通过EntityManager来开启的(虽然最终也是通过JDBC连接开启,但是逻辑大不一样)
 * 因为我们的SpringDataJpa是使用的Hibernate厂商对JPA的支持,它来处理事务管理逻辑
 * 并且它会将当前创建的EntityManager和当前获取的连接都绑定到当前线程
 *
 * <pre>
 *     @Configuration
 *     @RequiredArgsConstructor
 *     public static class AllService {
 *         private final AddressRepository addressRepository;
 *         private final RoleRepository roleRepository;
 *
 *         // 当使用DataSourceTransactionManager,抛出javax.persistence.TransactionRequiredException: no transaction is in progress
 *         // 而JpaTransactionManager正常执行
 *         @Transactional
 *         public void save() {
 *             // 情况一: 调用saveAndFlush,内部会调用entityManager.persist方法和entityManager.flush()方法
 *             // 在调用flush方法的时候,会到org.hibernate.internal.SessionImpl#doFlush()方法,该方法有一个校验操作checkTransactionNeededForUpdateOperation()
 *             // 又会调用org.hibernate.internal.AbstractSharedSessionContract#isTransactionInProgress(),这个是出问题的核心方法
 *             // 内部会用到一个transactionCoordinator事务协调器,会判断事务协调器持有的TransactionDriver事务驱动有没有初始化,这里只是将重点和大概,具体要看源码
 *             // 而DataSourceTransactionManager数据源事务管理器从头到尾就和Hibernate一点关系没有,所以自然也不会初始化TransactionDriver
 *             // 所以,最终判断事务是否有效的时候自然而然就判断不通过,导致事务不可用抛出javax.persistence.TransactionRequiredException: no transaction is in progress
 *             roleRepository.saveAndFlush(new Role("管理员", "admin"));
 *             addressRepository.saveAndFlush(new Address("address"));
 *
 *              // 情况二,和saveAndFlush类似,在保存之前,会调用org.hibernate.internal.AbstractSharedSessionContract#isTransactionInProgress(),这个是出问题的核心方法
 *              // 内部会用到一个transactionCoordinator事务协调器,会判断事务协调器持有的TransactionDriver事务驱动有没有初始化,这里只是将重点和大概,具体要看源码
 *              // 而DataSourceTransactionManager数据源事务管理器从头到尾就和Hibernate一点关系没有,所以自然也不会初始化TransactionDriver
 *              // 但是,它不会和flush方法一样,判断不通过抛出异常就抛出异常
 *              // 个人理解原因有二:
 *                  一: JPA协议规定,获取事务需要通过EntityManager.getTransaction(),开启提交都是都通过EntityTransaction对象提交,而在DataSourceTransactionManager中
 *                      直接操作的是JDBC连接来提交事务,从头到尾没有获取过EntityTransaction对象
 *                  二: 通过roleRepository.save保存,底层是操作EntityManager.persist方法,它实际上是与持久话上下文打交道,并没有立即提交到数据库
 *                      而是提交事务的时候,才会将数据从持久化上下文中的实体刷新到数据库,也就是调用flush方法,但是DataSourceTransactionManager并没有操作entityManager.flush()
 *                      而是直接使用JDBC连接提交事务,所以数据库中没有记录这也是情理之中
 *                      tip: 在这种情况当然,它自己或者你敢调用flush它就敢抛异常,这就又回到了saveAndFlush的问题,如果你不调用flush(),它就敢把你的数据吃掉,而且还不给你报错
 *              roleRepository.save(new Role("管理员", "admin"));
 *              addressRepository.save(new Address("address"));
 *         }
 *
 *         // 解决方案就是设置到JPA的地方,要重点关注事务管理器的处理逻辑,使用JpaTransactionManager作为事务管理器问题就能迎刃而解,因为他对有特殊处理,其他事务管理器暂时没有测试
 *     }
 * </pre>
 */
class Trans {
    @Primary
    @Bean
    public PlatformTransactionManager transactionManager(EntityManagerFactory entityManagerFactory) {
        JpaTransactionManager txManager = new JpaTransactionManager();
        txManager.setEntityManagerFactory(entityManagerFactory);
        return txManager;
    }

    @Bean
    public PlatformTransactionManager transactionManager() {
        DataSourceTransactionManager txManager = new DataSourceTransactionManager();
        txManager.setDataSource(dataSource());
        return txManager;
    }
}

  • 5
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值