javax.persistence.EntityManager回滚时事务外插入被回滚问题
Hibernate手动回滚事务时,前置非事务插入语句被连带回滚问题
问题场景
代码逻辑预期为:方法启动后先向table1插入一条数据,插入完成后再正式开启事务执行table2的批量插入;若执行流程走到transaction.rollback()分支,仅回滚table2的插入操作,table1的插入正常提交持久化。
实际运行时两次插入操作均被回滚。
预期结果
- 第一条插入(table1)语句正常提交持久化
- 第二条插入(table2)语句被事务回滚
实际运行结果
- 第一条插入(table1)语句被回滚
- 第二条插入(table2)语句被回滚
问题复现代码
//Relevant imports import java.sql.PreparedStatement; import javax.persistence.EntityManager; import javax.persistence.PersistenceContext; import org.hibernate.Session; import org.hibernate.Transaction; import org.hibernate.jdbc.Work; public boolean myMethod(String createdBy) { final Long messageId = ((BigInteger) myEntityManager.createNativeQuery("insert into table1(created_by) values (:createdBy) returning id") .setParameter("createdBy", createdBy) .getSingleResult()).longValue(); Session session = myEntityManager.unwrap(Session.class); Transaction tx = session.beginTransaction(); session.doWork(new Work() { @Override public void execute(Connection conn) throws SQLException { String sqlInsert = "insert into table2(messageId) values (?)"; try ( PreparedStatement pstmt = conn.prepareStatement(sqlInsert)) { for (Object[] row : unsentRecords) { pstmt.setLong(1, messageId); pstmt.addBatch(); } //Execute batch int[] sqlStatmentsStatuses = pstmt.executeBatch(); //check if some statement failed //Let's assume it always fails,just to be explicit boolean somethingWentWrong = atLeastOneFailed(sqlStatmentsStatuses); somethingWentWrong = true; if(somethingWentWrong){ tx.rollback(); } } } } }
问题根因
该现象由JPA/Hibernate的事务绑定机制导致,核心认知偏差点如下:
- 容器注入的
myEntityManager是和事务上下文绑定的,执行table1插入的原生SQL时,EntityManager会自动从连接池获取数据库连接,并隐式开启事务承载该操作,这条插入语句从未被提交,只是暂存在当前连接的事务上下文中。 - 后续调用
session.beginTransaction()并未创建全新的独立事务,只是获取了当前EntityManager绑定连接上已存在的事务上下文。 - 调用
tx.rollback()时,回滚范围覆盖该连接上事务开启后执行的所有操作,因此前置的table1插入会被连带回滚。 - 代码中混用JPA EntityManager API与原生Hibernate Session/Transaction API的写法,会进一步放大事务边界不可控的风险。
修复方案
要实现table1插入不受后续操作回滚影响的效果,必须保证两个操作使用独立数据库连接、运行在完全隔离的事务中:
- 执行
table1插入时,单独获取独立数据库连接,开启独立事务,插入完成后立刻手动提交事务并关闭连接,确保数据真正持久化。 - 执行
table2批量插入时,重新获取新的数据库连接,开启独立事务承载批量操作,失败时仅回滚该独立事务即可。 - 禁止在容器托管的EntityManager上直接执行预期为"事务外自动提交"的写操作,容器托管EntityManager的所有写操作默认都会纳入事务管理,不会自动提交。
内容的提问来源于stack exchange,提问作者Pero122
相关产品推荐
相关产品推荐

