Hibernate Reactive懒加载关联获取抛出LazyInitializationException问题排查
我有两个实体:Book和Page,一个Book包含多个Page。实体代码如下:
Book实体
@Entity(name = "book") @RegisterForReflection internal data class Book ( @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "id") val bookId: Long? = null, @OneToMany(mappedBy = "book", fetch = FetchType.LAZY) @Fetch(FetchMode.JOIN) val pages: MutableSet<Page> = mutableSetOf(), )
Page实体
@Entity(name = "page") @RegisterForReflection internal data class Page( @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "id") val pageId: Long? = null, @ManyToOne(fetch = FetchType.LAZY) @Fetch(FetchMode.JOIN) @JsonIgnore var book: Book? = Book(), )
两个实体可正常持久化到数据库,但获取Book时会抛出LazyInitializationException。我尝试过用Mutiny.fetch或LEFT JOIN FETCH预加载关联数据,仍会触发相同错误。以下是持久化和查询的实现代码:
BookRepo
@ApplicationScoped internal class BookRepo : PanacheRepository<Book> { fun getBook(bookId: Long): Uni<Book> { return findById(bookId) .call { it -> Mutiny.fetch(it.pages) } .map { it ?: throw RuntimeException("Not Found") } } fun persistBook(book: Book): Uni<Book> { return Panache.withTransaction { persist(book) } } }
PageRepo
@ApplicationScoped internal class PageRepo : PanacheRepository<Page> { fun persistPage(page: Page, bookId: Long): Uni<Page> { return Panache.withTransaction { getSession().chain { s -> val book = s.getReference(Book::class.java, bookId) page.book = book persist(page) } } } }
我哪里操作有误?
1. 实体映射配置冲突
你在@OneToMany和@ManyToOne上同时设置了fetch = FetchType.LAZY和@Fetch(FetchMode.JOIN),这两个配置逻辑冲突:
FetchType.LAZY表示延迟加载关联数据FetchMode.JOIN强制Hibernate用JOIN语句一次性加载关联数据,本质是立即加载行为
这种冲突会导致Hibernate加载逻辑混乱,建议移除@Fetch(FetchMode.JOIN),让延迟加载配置生效;若需立即加载,直接设置fetch = FetchType.EAGER(不推荐,会影响查询性能)。
2. Mutiny.fetch的使用时机错误
findById(bookId)执行完成后,Panache会自动关闭Hibernate会话。此时调用Mutiny.fetch(it.pages),关联集合已脱离会话上下文,必然抛出LazyInitializationException。
正确做法是在会话未关闭时完成关联数据加载,有两种可行方案:
方案一:用LEFT JOIN FETCH的JPQL查询
修改getBook方法,通过JPQL显式预加载pages集合,确保同一会话内完成加载:
fun getBook(bookId: Long): Uni<Book> { return find("SELECT b FROM book b LEFT JOIN FETCH b.pages WHERE b.bookId = ?1", bookId) .firstResult() .map { it ?: throw RuntimeException("Not Found") } }
方案二:在事务中执行fetch操作
将findById和Mutiny.fetch放在同一事务上下文内,保证fetch时会话处于打开状态:
fun getBook(bookId: Long): Uni<Book> { return Panache.withTransaction { findById(bookId) .call { it -> Mutiny.fetch(it.pages) } } .map { it ?: throw RuntimeException("Not Found") } }
3. Page实体默认值问题
Page实体中book字段的默认值是Book(),会创建未持久化的Book实例。若持久化Page时bookId无效或关联失败,会导致关联关系异常。应将默认值改为null:
var book: Book? = null,
内容的提问来源于stack exchange,提问作者E. Dn

