You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.13 04:55:23