Spring Boot下Hibernate懒加载异常:为何Job集合需手动初始化而Source不用
问题原因分析
这个异常的核心是你访问了已经关闭 Session 的懒加载集合代理,具体诱因有两个:
- 第一:
@PostConstruct是 Spring Bean 初始化阶段的回调,此时事务切面尚未完成初始化,你方法上加的@Transactional注解根本不生效。每个Repository的save调用都会开启独立事务,执行完成后事务自动提交、Session 关闭。 - 第二:Kotlin 默认所有类、方法、字段都是 final 的,Hibernate 要为实体生成动态代理实现懒加载,要求实体类是非 final 的。如果你的项目配置了
kotlin-allopen插件,并且设置了自动为@Entity注解的类添加open修饰,就会出现两个实体表现不一致的情况:- Job 类被插件自动加了
open修饰,Hibernate 可以成功生成代理,在调用jobRepository.save(initialJob)时,会把你构造函数中初始化的普通MutableSet替换为懒加载实现的PersistentSet代理。save 完成后 Session 关闭,后续调用targets.add时就会触发懒加载逻辑,抛出无 Session 异常。 - Source 类因为某些原因没有被
all-open插件处理,还是 final 类,Hibernate 无法生成代理,save 时不会替换你构造中初始化的普通集合,后续操作普通集合不需要访问数据库、不需要 Session,自然不会报错。
- Job 类被插件自动加了
你手动加的initialJob.targets = mutableSetOf()本质是把 Hibernate 替换的懒加载代理集合覆盖为普通集合,绕开了懒加载逻辑,所以不会报错。
推荐解决方案
- 把初始化逻辑从
@PostConstruct迁移到ApplicationReadyEvent事件监听中,此时 Spring 上下文完全初始化完成,@Transactional注解可以正常生效,整个初始化逻辑运行在同一个事务中,Session 不会提前关闭,懒加载集合可以正常操作:
- 把初始化逻辑从
@Component class InitService( private val jobRepository: JobRepository, private val sourceRepository: SourceRepository, private val targetRepository: TargetRepository ) { @EventListener(ApplicationReadyEvent::class) @Transactional fun init() { // 原有初始化逻辑,不需要手动初始化集合 } }
- 调整关联操作逻辑:因为是双向关联,直接设置
Target实体的source和job字段即可,不需要额外操作所属方的集合,关联关系会自动维护:
- 调整关联操作逻辑:因为是双向关联,直接设置
val target = Target() target.source = source target.job = initialJob targetRepository.save(target)
- 确认
kotlin-allopen插件的配置,保证所有@Entity类的代理策略一致,避免同类配置出现不同表现的问题。
- 确认
内容的提问来源于stack exchange,提问作者adammtlx
相关产品推荐
相关产品推荐

