Home > OS >  How to fetch lazy associations in Hibernate reactive?
How to fetch lazy associations in Hibernate reactive?

Time:11-12

I have two entities: Book and Page. Book can have many pages.

@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(),
)
@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(),
)

Both entities are persisted and created in database fine. But if I try to fetch Book, I get LazyInitializationException. Although I've tried fetching it with Mutiny.fetch or using LEFT JOIN FETCH it always throws the same error. Below the way I persist/fetch:

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

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

enter image description here

What am I doing wrong? I've created a repo with this small example, there are sequence of CURL requests to reproduce the error (please check the readme file).

CodePudding user response:

The problem is that the default equals and hashcode for the entities include the associations.

So, when you run the first select query, if they are called before you've fetched the association, it will cause the error.

You need to change your entities to something like:

@Entity(name = "page")
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? = null,
){
    override fun equals(other: Any?): Boolean {
        if (this === other) return true
        if (javaClass != other?.javaClass) return false

        other as Page

        if (pageId != other.pageId) return false

        return true
    }

    override fun hashCode(): Int {
        return pageId?.hashCode() ?: 0
    }
}

package org.acme

import org.hibernate.annotations.Fetch
import org.hibernate.annotations.FetchMode
import javax.persistence.*

@Entity(name = "book")
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)
    var pages: MutableSet<Page> = mutableSetOf(),
) {
    override fun equals(other: Any?): Boolean {
        if (this === other) return true
        if (javaClass != other?.javaClass) return false

        other as Book

        if (bookId != other.bookId) return false

        return true
    }

    override fun hashCode(): Int {
        return bookId?.hashCode() ?: 0
    }
}

Normally, you shouldn't include the identifier, but there are no other fields in this simple example. The Hibernate ORM documentation has a paragraph about how to implement hashcode and equals for entities that should give you a better explanation.

Another couple of things I've noticed:

  • I don't think you need to add @RegisterForReflection. That's only helpful in specific circumstances and this shouldn't be one of them.
  • Many-to-one associations are usually initialized with null. That's because you can check if the association exists using a null check. I suspect it may also cause errors because you are associating a new unmanaged object.
  • Related