quarkusmutinyquarkus-reactivequarkus-hibernate-reactive

Reactive panache: stop fetching bi-directional related entity with Mutiny fetch


An example of stack overflow when get an object using java quarkus reactive panache:

@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;
}

I do find an author and got error with LazyInitializationException.

return find("authorId", id).firstResult()
.onItem().call(au -> Mutiny.fetch(au.books())); Root cause: Bi-directional relationships between authors and publishers can result in an infinite loop of related entities.

Of course, if I map it to a DTO object with no relationships, the problem is solved. Reffered link: https://github.com/quarkusio/quarkus/issues/23757 The problem is that I want to keep working something with Author, not AuthorDTO...


Solution

  • In your project you have multiple associations:

    The first problem is that you are not telling Jackson that the association between Author and Book is bidirectional, this might result in a StackOverflow exception. You can solve it using the annotations @JsonManagedReference and @JsonBackReference:

    class Author {
    
        ...
    
        @JsonManagedReference
        @OneToMany(fetch = FetchType.LAZY, mappedBy = "author")
        public Set<Book> books = new HashSet<>();
    }
    
    class Book {
    
        ...
        @JsonBackReference
        @ManyToOne(fetch = FetchType.LAZY)
        @JoinColumn(name = "author_id")
        public Author author;
    
    }
    

    The LazyInitializationException happens because you are fetching the books association, but not the publishers one. Also, in general, you shouldn't use Mutiny.fetch to fetch the associations. The problem with it is that you will end up with a N+1 issue. You can solve the problem using a HQL query:

    
    @ApplicationScoped
    public class AuthorRepo implements PanacheRepository<Author> {
    
        public Uni<Author> findAndFetch(Long id) {
            return find( "select a from Author a left join fetch a.books b left join fetch b.publishers where a.id = ?1", id )
                    .firstResult();
        }
    
        ...
    }
    

    This way you can get everything you need with one query.

    I've forked your project and added all the changes needed to make it work quickly in this commit.

    I haven't tested it, but mapping the association with publishers as a SUBSELECT type, should also work well for this scenario. This way, you just need to fetch the first association (Mutiny.fetch(book.publishers)), and all the publishers for each book are going to be fetched.