Spring Boot3升级后Hibernate6批量语句排序警告与内存溢出问题
问题描述
将Spring Boot 2升级至Spring Boot 3(内部从Hibernate 5切换到Hibernate 6)后,遇到两个问题:
- 持久化过程中出现从未在Hibernate 5中见过的警告:
The batch containing 4590 statements could not be sorted. This might indicate a circular entity relationship.
- 执行定时任务时发生Java堆内存溢出(OutOfMemoryError),目前无法复现。该定时任务包含获取数据表、检查JSON文件生成状态及EntityManager刷写操作。
实体结构为深度嵌套:
- Datasheet包含Diagram列表,Diagram通过
@OneToMany(cascade = CascadeType.ALL)和@OrderBy("index")关联Curve列表 - Curve同样通过
@OneToMany(cascade = CascadeType.ALL)和@OrderBy("index")关联Coordinate列表,所有对象在同一事务中保存 - 单个Diagram通常包含1-10个Curve,每个Curve有5000+坐标点
内存溢出错误日志:
14:47:08 INFO c.d.c.service.AuthenticationService - getCurrentUsername returned: Dhyani 14:47:09 INFO ParserService - Parsing took 823 ms 14:47:09 WARN ParserService - Input file parsed with 0 errors. 14:47:09 INFO ParserService - Creating logs took 9 ms 14:47:09 WARN o.hibernate.engine.spi.ActionQueue - The batch containing 3685 statements could not be sorted. This might indicate a circular entity relationship. 14:47:12 INFO c.d.core.mail.service.MailService - Send 'Datasheet Update Notification' to Datasheet Owner. 14:47:12 WARN o.t.s.p.AbstractStandardFragmentInsertionTagProcessor - [THYMELEAF][http-nio-8080-exec-6][mail-template] Deprecated unwrapped fragment expression "__${topContent}__" found in template mail-template, line 20, col 18. Please use the complete syntax of fragment expressions instead ("~{__${topContent}__}"). The old, unwrapped syntax for fragment expressions will be removed in future versions of Thymeleaf. 14:47:13 WARN o.t.s.p.AbstractStandardFragmentInsertionTagProcessor - [THYMELEAF][http-nio-8080-exec-6][approval-flow-mails] Deprecated unwrapped fragment expression "this :: ds-name-and-revision" found in template approval-flow-mails, line 69, col 30. Please use the complete syntax of fragment expressions instead ("~{this :: ds-name-and-revision}"). The old, unwrapped syntax for fragment expressions will be removed in future versions of Thymeleaf. 14:47:13 INFO c.d.core.mail.service.MailConnector - Sending Mail from R-test-ePowerDatashe@test.com to asdf@test.com; subject: ePower 2.0 Message: Your Datasheet was changed 14:47:16 INFO c.d.core.mail.service.MailService - Mail sent! 14:47:16 INFO c.d.c.s.s.AppUserDetailsService - numActiveUsers: 1 for username: kobayahi 14:47:16 INFO c.d.c.service.AuthenticationService - null:productive 14:47:16 INFO c.d.c.service.AuthenticationService - getCurrentUsername returned: KOBAYAHI 14:47:16 INFO c.d.c.service.AuthenticationService - null:productive 14:47:16 INFO c.d.c.service.AuthenticationService - Auth with currentUsername 14:47:17 INFO c.d.c.s.s.AppUserDetailsService - numActiveUsers: 1 for username: vielemem 14:47:17 INFO c.d.c.service.AuthenticationService - null:productive 14:47:17 INFO c.d.c.service.AuthenticationService - getCurrentUsername returned: VIELEMEM 14:47:17 INFO c.d.c.service.AuthenticationService - null:productive 14:47:17 INFO c.d.c.service.AuthenticationService - Auth with currentUsername 14:47:18 INFO c.d.c.s.s.AppUserDetailsService - numActiveUsers: 1 for username: torremar 14:47:18 INFO c.d.c.service.AuthenticationService - null:productive 14:47:18 INFO c.d.c.service.AuthenticationService - getCurrentUsername returned: TORREMAR 14:47:18 INFO c.d.c.service.AuthenticationService - null:productive 14:47:18 INFO c.d.c.service.AuthenticationService - Auth with currentUsername 14:47:18 WARN org.apache.pdfbox.cos.COSDocument - Warning: You did not close a PDF Document 14:49:39 ERROR o.s.s.s.TaskUtils$LoggingErrorHandler - Unexpected error occurred in scheduled task java.lang.OutOfMemoryError: Java heap space 14:49:39 WARN com.zaxxer.hikari.pool.HikariPool - HikariPool-2 - Thread starvation or clock leap detected (housekeeper delta=2m13s625ms839µs60ns). 14:49:39 ERROR o.a.coyote.http11.Http11NioProtocol - Error processing async timeouts java.util.concurrent.ExecutionException: java.lang.OutOfMemoryError: Java heap space at java.base/java.util.concurrent.FutureTask.report(FutureTask.java:122) at java.base/java.util.concurrent.FutureTask.get(FutureTask.java:191) at org.apache.coyote.AbstractProtocol.startAsyncTimeout(AbstractProtocol.java:660) at org.apache.coyote.AbstractProtocol.lambda$start$0(AbstractProtocol.java:646) at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:539) at java.base/java.util.concurrent.FutureTask.runAndReset(FutureTask.java:305) at java.base/java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:305) at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1136) at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:635) at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:63) at java.base/java.lang.Thread.run(Thread.java:840) Caused by: java.lang.OutOfMemoryError: Java heap space at java.base/java.util.concurrent.ConcurrentHashMap$KeySetView.iterator(ConcurrentHashMap.java:4635) at org.apache.coyote.AbstractProtocol.lambda$startAsyncTimeout$1(AbstractProtocol.java:667) at org.apache.coyote.AbstractProtocol$$Lambda$2323/0x00007f64f0f66240.run(Unknown Source) ... 7 common frames omitted 14:49:39 INFO c.d.epower.service.DatasheetService - ExportJsonScheduler started
咨询点:
- 为何Hibernate 6会记录该排序警告而Hibernate 5不会?
- 该警告是否会引发堆内存溢出问题?
问题解答
1. 为什么Hibernate 6出现排序警告而Hibernate 5没有?
Hibernate 6对批量语句排序的逻辑做了优化和严格性提升:
- Hibernate 5的批量语句排序逻辑相对宽松,即使存在循环依赖或者无法排序的语句,也不会主动抛出警告,只会默默跳过排序或者采用默认顺序执行。
- Hibernate 6增强了对批量操作中语句依赖关系的检测,当它尝试对批量SQL语句进行排序以优化执行顺序(比如先插入父实体再插入子实体)时,如果发现存在无法解析的依赖循环(或者因为
@OrderBy导致的排序依赖冲突),就会抛出这个警告。
你的场景中,虽然实体结构看起来是单向嵌套,但@OrderBy注解可能让Hibernate在处理批量插入时,对实体的保存顺序产生额外的依赖判断,加上批量操作规模极大(单个Curve有5000+坐标点),导致Hibernate无法完成语句排序,从而触发警告。
2. 该警告是否会引发堆内存溢出?
这个警告本身不会直接导致堆内存溢出,但它背后的批量处理逻辑变化,可能和OOM问题存在关联:
- Hibernate 6在处理无法排序的批量语句时,可能会放弃批量优化,改为逐个执行语句,或者在内存中保留更多的语句元数据和实体状态,这会增加内存开销。
- 你的场景中,单个事务要保存大量Coordinate实体,Hibernate 6的一级缓存会加载所有这些实体,加上批量处理效率降低,内存占用会比Hibernate 5更高,最终触发OOM。
从日志时间线来看,警告出现在持久化阶段,OOM发生在后续定时任务执行时,两者时间接近,说明批量处理的内存开销是OOM的诱因之一。
优化建议
针对你的场景,建议做以下优化来解决警告和OOM问题:
- 拆分大事务:不要在单个事务中保存所有嵌套实体,分批次保存Curve和Coordinate,减少单次事务的内存占用。
- 调整级联范围:如果某些子实体不需要全量级联操作,缩小
CascadeType的范围,避免一次性加载所有关联实体。 - 配置批量插入参数:设置
spring.jpa.properties.hibernate.jdbc.batch_size为合适值(比如500),同时开启hibernate.order_inserts=true和hibernate.order_updates=true,帮助Hibernate更好地处理批量语句排序。 - 采用延迟加载或DTO投影:在定时任务中,若不需要完整实体对象,使用DTO投影只加载需要的字段,减少内存占用。
- 排查隐藏循环依赖:仔细检查实体关系,确认是否存在未配置
mappedBy的双向关联,这可能导致Hibernate误判为循环依赖。
内容的提问来源于stack exchange,提问作者zFr3eak
相关产品推荐
相关产品推荐

