/**
* 在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;
}
}