遍历ArrayList时遭遇java.util.ConcurrentModificationException异常
你遇到的问题根源其实很明确:你遍历的myObjectList并不是普通的ArrayList,而是Hibernate管理的PersistentCollection(持久化集合)。当你在循环里调用objectRepository.save(obj)时,Hibernate会悄悄同步更新这个集合的内部状态(比如刷新缓存、同步实体状态),这就导致迭代器在检查集合修改次数(modCount)时发现前后不一致,直接抛出ConcurrentModificationException——哪怕你只是修改对象属性,没有显式增删集合元素,Hibernate的内部操作也可能触发这个异常。
下面给你几个可行的解决方案,按推荐程度排序:
1. 使用JPQL批量更新(最优方案)
如果你的需求只是批量修改这些对象的endDate和active状态,完全不需要把所有对象加载到内存里。直接用JPQL执行批量更新,在数据库层面完成操作,既高效又彻底避免迭代问题:
String batchUpdateJpql = "UPDATE YourEntity o SET o.endDate = :targetDate, o.active = false"; entityManager.createQuery(batchUpdateJpql) .setParameter("targetDate", someDate) .executeUpdate();
注意:把YourEntity换成你实际的实体类名,如果需要过滤特定数据,可以在JPQL里加WHERE子句补充条件。
2. 转成普通集合再遍历
如果必须加载对象到内存处理(比如还有其他业务逻辑要执行),可以先把Hibernate的持久化集合转成普通的ArrayList,脱离Hibernate的管理后再遍历:
// 把持久化集合转成普通ArrayList,脱离Hibernate的状态跟踪 List<YourEntity> tempList = new ArrayList<>(myObjectList); for(YourEntity obj : tempList) { obj.setEndDate(someDate); obj.setActive(false); // 这里你原来写的isActive(false)应该是setActive的笔误吧? objectRepository.save(obj); }
这样遍历的是独立的临时列表,Hibernate对原集合的内部修改不会影响这个列表的迭代过程,自然不会抛出异常。
3. 使用Hibernate ScrollableResults遍历(适合大数据量)
如果数据量很大,转成临时列表会占用过多内存,可以用Hibernate的ScrollableResults来逐行加载实体,同时定期flush/clear会话避免内存溢出:
Session session = entityManager.unwrap(Session.class); ScrollableResults results = session.createQuery("FROM YourEntity") .scroll(ScrollMode.FORWARD_ONLY); int counter = 0; while (results.next()) { YourEntity obj = (YourEntity) results.get(0); obj.setEndDate(someDate); obj.setActive(false); session.save(obj); // 每处理50条就flush并clear,释放内存 if (++counter % 50 == 0) { session.flush(); session.clear(); } }
另外提个小细节:你代码里的obj.isActive(false)应该是setActive(false)的笔误,is开头的方法一般是属性的getter,设置属性要用setter哦。
内容的提问来源于stack exchange,提问作者Naren

