Hibernate批量插入后后续关联实体插入性能骤降问题排查
使用Hibernate 5.2.5.Final执行数千条Booking的批量插入操作,每条Booking关联的Budget已存在于数据库中,批量插入代码如下:
public List<Long> batchInsertBookings(List<Booking> bookings, String resource) { List<Long> generatedIds = new ArrayList<>(); router.setDataSource(resource); try (StatelessSession statelessSession = em.unwrap(Session.class).getSessionFactory().openStatelessSession()) { int batchSize = 20; int numberOfEntities = bookings.size(); UserTransaction userTransaction = (UserTransaction) new InitialContext().lookup("java:comp/UserTransaction"); userTransaction.begin(); for (int i = 0; i < numberOfEntities; i += batchSize) { int endIndex = Math.min(i + batchSize, numberOfEntities); List<Booking> batch = bookings.subList(i, endIndex); // Save the batch data for (Booking entity : batch) { statelessSession.insert(entity); } // Retrieve the generated IDs after flush List<Long> batchIds = batch.stream() .map(IObject::getId) .collect(Collectors.toList()); generatedIds.addAll(batchIds); } userTransaction.commit(); } catch (Exception e) { Log.debug(e.getMessage(), getClass().getName()); } return generatedIds; }
该插入方法在新线程中执行,批量插入可正常完成且数据已入库,后续查询操作性能无明显变化,但插入带元素集合或关联的实体(如包含对应数千条Booking的字符串集合diff的ChangeLog)时,性能骤降:每个字符串元素的持久化耗时约1秒,而未执行批量插入时该操作可快速完成。
实体关联结构
1 Account (account) <-> (mainBudget) Budget 0..1 0..1 Budget (parentBudget) <-> (subBudget) Budget * 1 Budget (budget) <-> (bookings) Booking *
关联映射示例
Booking.java:
@ManyToOne(fetch=FetchType.LAZY, optional= false) @JoinColumn(name="budget_id") @BatchSize(size = 50) private Budget budget;
Budget.java:
@OneToMany(targetEntity = Booking.class, mappedBy = "budget") @Fetch(value = FetchMode.SUBSELECT) private List<Booking> booking;
已尝试的优化方案
- 为所有关联添加
@BatchSize注解解决N+1查询问题; - 配置Hikari连接池;
- 尝试不同批量插入方式(普通Session、
@Transactional+EntityManager等); - 简化Booking实体关联结构后性能恢复;
- 提前加载Booking关联避免懒加载异常;
- 移除级联操作、关闭连接池等测试。
附上相关persistence.xml配置、批量插入及后续插入的SQL日志、Booking表结构等信息,请问该性能问题的原因是什么?
StatelessSession的无特性导致缓存一致性失效
StatelessSession是无状态会话,不会将插入的Booking实体纳入Hibernate一级缓存,也不会自动更新关联实体(如Budget的booking集合)的二级缓存状态。后续插入ChangeLog时,Hibernate处理关联的Booking集合时,会因为Budget二级缓存中保留的是批量插入前的旧关联数据,触发大量无效的缓存查询或全表扫描,每个元素持久化都要进行耗时的数据库交互。FetchMode.SUBSELECT的触发逻辑冲突
Budget的booking集合使用@Fetch(FetchMode.SUBSELECT),后续操作加载Budget实体时,Hibernate会生成子查询获取所有关联的Booking。但批量插入的Booking未被纳入缓存,这个子查询会在处理ChangeLog的每个元素时重复执行,再加上数据量剧增(数千条),子查询的执行和结果处理耗时被放大,直接导致每个元素持久化耗时飙升。跨线程事务的缓存上下文隔离
批量插入在新线程中执行,该线程的StatelessSession事务提交后,后续操作线程的Hibernate缓存无法感知数据变化。Hibernate的缓存一致性机制会被迫进行大量脏检查或数据重新加载,尤其在处理关联集合时,这种检查被无限放大,造成性能骤降。关联完整性检查的叠加开销
即便移除了级联操作,Hibernate处理关联实体时仍会进行关联完整性检查。当ChangeLog关联大量Booking时,Hibernate需要逐个验证这些Booking的关联状态(如对应的Budget是否存在),而StatelessSession插入的Booking未进入缓存,每次验证都要查询数据库,叠加后形成巨大的性能开销。
内容的提问来源于stack exchange,提问作者lasbr

