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

文章讨论了Spring中使用DataSourceTransactionManager和JpaTransactionManager进行事务管理的区别,尤其是在JPA场景下,JpaTransactionManager能避免`TransactionRequiredException`,因为其与Hibernate有更好的集成并遵循JPA协议。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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

<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>3.5.3</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.example</groupId> <artifactId>exam</artifactId> <version>0.0.1-SNAPSHOT</version> <name>exam</name> <description>exam</description> <url/> <licenses> <license/> </licenses> <developers> <developer/> </developers> <scm> <connection/> <developerConnection/> <tag/> <url/> </scm> <properties> <java.version>17</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.thymeleaf.extras</groupId> <artifactId>thymeleaf-extras-springsecurity6</artifactId> </dependency> <dependency> <groupId>com.mysql</groupId> <artifactId>mysql-connector-j</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <scope>provided</scope> </dependency> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-annotation</artifactId> <version>3.5.7</version> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <scope>provided</scope> </dependency> <dependency> <groupId>org.apache.velocity</groupId> <artifactId>velocity-tools</artifactId> <version>2.0</version> </dependency> <dependency> <groupId>org.springframework.data</groupId> <artifactId>spring-data-commons</artifactId> <version>3.4.4</version> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <scope>provided</scope> </dependency> <dependency> <groupId>org.hibernate.orm</groupId> <artifactId>hibernate-core</artifactId> <version>6.6.11.Final</version> </dependency> <dependency> <groupId>com.networknt</groupId> <artifactId>json-schema-validator</artifactId> <version>1.5.1</version> </dependency> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.16</version> </dependency> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-core</artifactId> <version>3.5.7</version> </dependency> <dependency> <groupId>org.thymeleaf</groupId> <artifactId>thymeleaf</artifactId> <version>3.1.3.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>6.2.5</version> </dependency> <!-- Bean Validation API --> <dependency> <groupId>jakarta.validation</groupId> <artifactId>jakarta.validation-api</artifactId> <version>3.0.2</version> </dependency> <!-- Hibernate Validator (实现) --> <dependency> <groupId>org.hibernate.validator</groupId> <artifactId>hibernate-validator</artifactId> <version>8.0.1.Final</version> </dependency> <!-- Spring Boot 验证支持 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-validation</artifactId> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <configuration> <annotationProcessorPaths> <path> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </path> </annotationProcessorPaths> </configuration> </plugin> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <configuration> <excludes> <exclude> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </exclude> </excludes> </configuration> </plugin> </plugins> </build> </project> 检查我的Pom.xml
最新发布
06-21
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值