Quarkus Reactive Panache:如何避免双向关联实体Mutiny fetch无限循环
问题:Quarkus Reactive Panache获取实体时出现栈溢出及LazyInitializationException
场景复现
实体类配置了双向关联:
@Table(name = "author") public class Author extends PanacheEntityBase { private Long id; private String authorName; @OneToMany(fetch = FetchType.LAZY) private Set<Book> books; } @Table(name = "book") public class Book extends PanacheEntityBase { private Long id; private String bookName; @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "id") private Author author; @ManyToMany(fetch = FetchType.LAZY) private Set<Publisher> publishers; } @Table(name = "publisher") public class Publisher extends PanacheEntityBase { private Long id; private String publisherName; @ManyToMany(fetch = FetchType.LAZY) @JoinColumn(...) private Set<Book> books; }
执行这段查询代码时触发错误:
return find("authorId", id).firstResult() .onItem().call(au -> Mutiny.fetch(au.books()));
问题根源
Author、Book、Publisher之间的双向关联导致实体无限循环加载,同时懒加载操作脱离持久化上下文时引发LazyInitializationException。
需求
知道转成无关联DTO能解决,但想直接操作Author实体,不想用DTO,求解决方案。
可行方案
参考Quarkus官方社区讨论的解决思路,给出以下几种直接操作实体的办法:
1. 用序列化注解打断循环
如果是序列化阶段引发的栈溢出,在双向关联的其中一侧加@JsonIgnore注解,比如给Book类的author字段或者Publisher类的books字段加:
@ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "id") @JsonIgnore private Author author;
注意:这只解决序列化时的循环问题,业务逻辑里要避免循环加载还得配合其他方式。
2. 自定义查询指定加载层级
用Reactive Panache的自定义查询,通过JOIN FETCH明确加载需要的关联,别自动加载所有关联:
return find("SELECT a FROM Author a JOIN FETCH a.books b WHERE a.id = ?1", id) .firstResult() // 按需加载publisher,不要全量加载引发循环 .onItem().call(au -> Mutiny.fetch(au.getBooks().stream().findFirst().get().getPublishers()));
核心是只加载业务需要的关联层级,别碰不需要的双向关联。
3. 用实体图控制加载范围
给实体定义实体图,精准指定要加载的关联:
@NamedEntityGraph(name = "AuthorWithBooks", attributeNodes = @NamedAttributeNode("books")) @Table(name = "author") public class Author extends PanacheEntityBase { // 实体字段不变 }
查询时指定使用这个实体图:
return find("authorId", id) .withEntityGraph("AuthorWithBooks") .firstResult();
这样只会加载指定的关联,不会触发其他未配置的双向关联加载。
4. 手动断开循环引用
加载完关联后,手动把循环的引用置空,比如加载Author的books后,遍历每个Book把author设为null:
return find("authorId", id).firstResult() .onItem().call(au -> Mutiny.fetch(au.getBooks())) .onItem().invoke(au -> au.getBooks().forEach(book -> book.setAuthor(null)));
这种方式简单直接,但每次加载都要手动处理,适合小场景用。
内容的提问来源于stack exchange,提问作者Thai Huynh
相关产品推荐
相关产品推荐

