使用JPA fetchCount()偶发ConcurrentModificationException问题求助
问题分析与解决方案
这个偶发的ConcurrentModificationException本质是因为EntityManager不是线程安全的——如果两次查询共享同一个EntityManager实例,且存在并发访问(或当前线程内EntityManager的内部状态被意外修改),就会触发这个异常,进而导致JDBC连接处理失败,最终无法提取ResultSet。
解决方案1:每次查询使用独立的EntityManager实例
EntityManager设计为非线程安全,共享实例会导致内部状态被并发修改。确保每次查询都获取新的EntityManager实例:
// 分页查询用独立EntityManager try (EntityManager em = entityManagerProvider.get()) { List<MyEntity> list = new JPAQuery<>(em) .select(myEntity) .from(myEntity) .where(buildCondition(someId)) .orderBy(myEntity.uuid.asc()) .offset(offset) .limit(limit) .fetch(); } // 计数查询用独立EntityManager try (EntityManager em = entityManagerProvider.get()) { long count = new JPAQuery<MyEntity>(em) .from(myEntity) .where(buildCondition(someId)) .fetchCount(); }
如果你的EntityManagerProvider是容器管理(比如Spring),要确保它返回的是线程绑定的EntityManager代理(而非单例实例),这样每个线程/请求会拿到独立的实例。
解决方案2:合并查询,一次性获取列表和总数
用QueryDSL的fetchResults()方法,一次查询同时拿到分页数据和总条数,避免两次查询共享EntityManager的风险:
EntityManager em = entityManagerProvider.get(); JPQLQuery<MyEntity> query = new JPAQuery<>(em) .select(myEntity) .from(myEntity) .where(buildCondition(someId)) .orderBy(myEntity.uuid.asc()) .offset(offset) .limit(limit); QueryResults<MyEntity> results = query.fetchResults(); List<MyEntity> dataList = results.getResults(); long totalCount = results.getTotal();
这种方式只使用一次EntityManager,从根源上避免了两次查询的状态冲突。
解决方案3:临时清理EntityManager状态(应急方案)
如果必须共享EntityManager,在第一次查询后清理会话状态,避免残留对象影响后续查询:
EntityManager em = entityManagerProvider.get(); // 执行分页查询 List<MyEntity> list = new JPAQuery<>(em) .select(myEntity) .from(myEntity) .where(buildCondition(someId)) .orderBy(myEntity.uuid.asc()) .offset(offset) .limit(limit) .fetch(); // 清理会话,重置EntityManager内部状态 em.clear(); // 执行计数查询 long count = new JPAQuery<MyEntity>(em) .from(myEntity) .where(buildCondition(someId)) .fetchCount();
注意:这种方式只能临时缓解问题,无法彻底解决多线程环境下的并发冲突,优先推荐前两种方案。
内容的提问来源于stack exchange,提问作者yoyo
相关产品推荐
相关产品推荐

