事务方法持久化实体后,后续方法偶尔无法查询到该实体的问题
问题背景
使用Hibernate时遇到偶发现象:通过persist()持久化实体后,在@Transactional修饰的create方法内可通过find()查询到实体并返回ID,但后续调用getById()方法时,约1/2000的概率无法找到该实体。已排查过EntityManager实例一致性(记录哈希码)、手动执行flush(),但问题无法在测试环境复现。
疑问解答
1. 离开第一个@Transactional修饰的create方法时,Hibernate应该已经提交事务了吗?
正常情况下,如果create方法的事务是独立事务,方法正常执行完成且无未捕获异常时,容器会在方法退出时触发事务提交。但结合你的代码场景,create方法的事务并非独立:你的MDB使用了@TransactionManagement(TransactionManagementType.CONTAINER),容器默认会给onMessage方法分配一个全局事务,create方法的@Transactional(TxType.REQUIRED)会加入这个全局事务,而非创建新事务。此时create方法退出时,全局事务并未提交,要等到整个onMessage方法执行完毕才会触发提交。
2. 是否可能在getById()执行时,Hibernate还未完成事务提交?
完全可能。如上述分析,若create和getById都处于onMessage的全局事务中:
- 若
getById使用的EntityManager与create不同(比如容器在事务内异常分配了新的EM实例),getById的find()会直接查询数据库,而此时全局事务尚未提交,数据库中并无该实体,导致查询失败; - 即使EM实例一致,若
getById的查询绕过了一级缓存(比如使用了refresh()或其他强制查询数据库的操作),同样会因事务未提交查不到数据。
这种偶发场景通常和容器事务调度的极端情况有关,比如事务上下文传递延迟、EM实例池的异常分配等。
3. @TransactionManagement(TransactionManagementType.CONTAINER)是否会影响Hibernate的提交方式和时机?
是的,影响非常关键:
- 容器管理事务(CONTAINER)下,事务的生命周期(创建、提交、回滚)完全由EJB容器控制,而非应用代码或Hibernate;
- 对于MDB的
onMessage方法,容器默认会绑定一个全局事务,所有被调用的、使用REQUIRED事务属性的方法都会加入该全局事务; - 事务提交时机由容器决定,仅当整个
onMessage方法执行完成且无异常时,才会触发全局事务的提交,而非单个方法执行完毕就提交。
可能的解决方案
- 让create方法使用独立事务:将
create方法的事务属性改为REQUIRES_NEW,强制创建独立事务,方法退出时立即提交:
@Override @Transactional(Transactional.TxType.REQUIRES_NEW) public Id create(Message message) { // 原有逻辑不变 }
这样create方法的事务会独立于MDB的全局事务,提交后getById查询数据库就能获取到实体。
调整MDB的事务属性:如果业务允许,给MDB的
onMessage方法添加@TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED),让其不运行在容器事务中,此时create方法的REQUIRED会创建独立事务,执行完毕即提交。确保getById在事务提交后执行:若必须保留MDB的全局事务,可在
create之后、getById之前,通过容器API强制提交当前事务(注意:容器管理事务下手动提交需谨慎,可能破坏事务一致性),或通过异步延迟调用getById,等待事务提交完成。
内容的提问来源于stack exchange,提问作者canEE

