Hibernate JPA持久化后记录未入库,JBehave BDD测试事务同步问题
从你描述的场景来看,核心问题出在异步时序+事务隔离/缓存机制上:MQ触发的数据库操作在JTA事务中执行,而BDD测试的断言线程没等这个事务提交就去查询,再加上Hibernate的一级缓存和RESOURCE_LOCAL事务的特性,导致断言读到的始终是旧数据。下面是几个针对性的解决方案:
1. 清空Hibernate一级缓存,确保查询最新数据
Hibernate的一级缓存(EntityManager级别的缓存)会缓存已经查询过的实体,哪怕数据库里的数据已经被修改,不主动清空的话还是会返回旧数据。你可以修改DbHelper的查询方法,每次查询前强制清空缓存:
public String findTableIdBasedOnAbcId(String Id) throws Exception { // 先清空EntityManager的一级缓存,避免读取旧数据 database.getEm().clear(); String query = "SELECT id FROM TABLE WHERE ABC_ID = ?1"; Query queryResult = database.getEm().createNativeQuery(query); queryResult.setParameter(1, Id); List<Long> list = (List<Long>) queryResult.getResultList(); return !list.isEmpty() ? String.valueOf(list.get(0)) : null; }
另外,在BDD的persistence.xml里明确设置事务隔离级别为READ_COMMITTED(Oracle默认就是这个,但明确配置能避免意外):
<property name="hibernate.connection.isolation" value="2"/>
2. 给MQ异步处理加轮询等待
MQ消息的消费是异步的,@When步骤发送消息后,消息可能还在队列里或者正在被主项目处理,这时候直接断言肯定会失败。你可以把断言改成轮询查询+超时机制,给事务提交留足够时间:
// 改造断言逻辑,加入轮询 public void assertRecordDeleted(String abcId) throws InterruptedException { final int MAX_RETRIES = 12; // 最多重试12次 final long WAIT_INTERVAL = 500; // 每次间隔500ms,总共等待6秒 boolean isDeleted = false; for (int i = 0; i < MAX_RETRIES; i++) { String recordId = findTableIdBasedOnAbcId(abcId); if (recordId == null) { isDeleted = true; break; } Thread.sleep(WAIT_INTERVAL); } Assert.assertNull("超时:记录未被删除", findTableIdBasedOnAbcId(abcId)); }
这样既不会无限等待,又能覆盖异步操作的时序延迟。
3. 调整BDD的事务管理方式
你的DatabaseService在构造方法里直接开启了事务,但RESOURCE_LOCAL事务如果一直处于活跃状态,Hibernate会使用事务内的数据快照,看不到其他事务(比如主项目的JTA事务)提交的修改。可以修改DatabaseService,让查询操作在无事务环境下执行:
public class DatabaseService implements Closeable { private EntityManagerFactory emf = null; private EntityManager em = null; public DatabaseService(Properties configuration) { emf = Persistence.createEntityManagerFactory("project-pu", configuration); em = emf.createEntityManager(); // 移除这里的em.getTransaction().begin(),查询不需要开启事务 } public EntityManager getEm() { // 如果有活跃事务,先提交再返回 if (em.getTransaction().isActive()) { em.getTransaction().commit(); } return em; } }
这样查询时就能读取到数据库中已提交的最新数据,不会被未提交的事务快照限制。
4. 验证主项目的事务提交状态
最后别忘了排查主项目的问题:确保MQ消费方法上有正确的@Transactional注解,事务在处理完成后正常提交,没有因为异常回滚。可以查看主项目的日志,确认事务提交的日志是否存在。
内容的提问来源于stack exchange,提问作者dorcsi

