proximator
proximator

Reputation: 688

Override fetchType EAGER with LAZY at runtime in Hibernate

All my hibernate entities have EAGER fetch type and I would like to override it only in some specific cases, this seems to be a bug in hibernate and was fixed in 5.4.11 (see: HHH-8776) and I am using 5.4.18.

But it does not work in my cases, maybe I am missing something. Bellow the details of the entities used in my test:

   @Entity
    @NamedEntityGraphs({
        @NamedEntityGraph(name = "graph.AuthorBooks", 
                          attributeNodes = @NamedAttributeNode("books")),
        
        @NamedEntityGraph(name = "graph.AuthorBooksReviews", 
                          attributeNodes = @NamedAttributeNode(value = "books", subgraph = "books"),
                          subgraphs = @NamedSubgraph(name = "books", 
                                                     attributeNodes = @NamedAttributeNode("reviews")))})
    public class Author implements Serializable {
    
        private static final long serialVersionUID = 1L;
    
        @Id
        @Column(name = "id", updatable = false, nullable = false)
        private Long id;
        @Version
        @Column(name = "version")
        private int version;
    
        @Column
        private String firstName;
    
        @Column
        private String lastName;
    
        @ManyToMany(mappedBy = "authors", fetch = FetchType.EAGER)
        private Set<Book> books = new HashSet<>();
    
        public Long getId() {
            return this.id;
        }
    
        public void setId(final Long id) {
            this.id = id;
        }
    
        public int getVersion() {
            return this.version;
        }
    
        public void setVersion(final int version) {
            this.version = version;
        }
    
        public String getFirstName() {
            return firstName;
        }
    
        public void setFirstName(String firstName) {
            this.firstName = firstName;
        }
    
        public String getLastName() {
            return lastName;
        }
    
        public void setLastName(String lastName) {
            this.lastName = lastName;
        }
    
        public Set<Book> getBooks() {
            return this.books;
        }
    
        public void setBooks(final Set<Book> books) {
            this.books = books;
        }
    
        @Override
        public boolean equals(Object obj) {
            if (this == obj) {
                return true;
            }
            if (!(obj instanceof Author)) {
                return false;
            }
            Author other = (Author) obj;
            if (id != null) {
                if (!id.equals(other.id)) {
                    return false;
                }
            }
            return true;
        }
    
        @Override
        public int hashCode() {
            final int prime = 31;
            int result = 1;
            result = prime * result + ((id == null) ? 0 : id.hashCode());
            return result;
        }
    
        @Override
        public String toString() {
            String result = getClass().getSimpleName() + " ";
            if (firstName != null && !firstName.trim().isEmpty()) {
                result += "firstName: " + firstName;
            }
            if (lastName != null && !lastName.trim().isEmpty()) {
                result += ", lastName: " + lastName;
            }
            return result;
        }
    }


        @Entity
    public class Book implements Serializable {
    
        private static final long serialVersionUID = 1L;
    
        @Id
        @GeneratedValue(strategy = GenerationType.AUTO)
        @Column(name = "id", updatable = false, nullable = false)
        private Long id;
        @Version
        @Column(name = "version")
        private int version;
    
        @Column
        private String title;
    
        @Column
        @Temporal(TemporalType.DATE)
        private Date publishingDate;
    
        @ManyToMany
        @JoinTable(
                name = "BookAuthor",
                joinColumns = {
                    @JoinColumn(name = "bookId", referencedColumnName = "id")},
                inverseJoinColumns = {
                    @JoinColumn(name = "authorId", referencedColumnName = "id")})
        private Set<Author> authors = new HashSet<>();
    
        @OneToMany(mappedBy = "book", fetch = FetchType.EAGER)
        private Set<Review> reviews = new HashSet<>();
    
        public Long getId() {
            return this.id;
        }
    
        public void setId(final Long id) {
            this.id = id;
        }
    
        public int getVersion() {
            return this.version;
        }
    
        public void setVersion(final int version) {
            this.version = version;
        }
    
        public String getTitle() {
            return title;
        }
    
        public void setTitle(String title) {
            this.title = title;
        }
    
        public Date getPublishingDate() {
            return publishingDate;
        }
    
        public void setPublishingDate(Date publishingDate) {
            this.publishingDate = publishingDate;
        }
    
        public Set<Author> getAuthors() {
            return authors;
        }
    
        public void setAuthors(Set<Author> authors) {
            this.authors = authors;
        }
    
        public Set<Review> getReviews() {
            return reviews;
        }
    
        public void setReviews(Set<Review> reviews) {
            this.reviews = reviews;
        }
    
        @Override
        public String toString() {
            String result = getClass().getSimpleName() + " ";
            if (title != null && !title.trim().isEmpty()) {
                result += "title: " + title;
            }
            return result;
        }
    
        @Override
        public boolean equals(Object obj) {
            if (this == obj) {
                return true;
            }
            if (!(obj instanceof Book)) {
                return false;
            }
            Book other = (Book) obj;
            if (id != null) {
                if (!id.equals(other.id)) {
                    return false;
                }
            }
            return true;
        }
    
        @Override
        public int hashCode() {
            final int prime = 31;
            int result = 1;
            result = prime * result + ((id == null) ? 0 : id.hashCode());
            return result;
        }
    }
  
    @Entity
public class Review implements Serializable {

    private static final long serialVersionUID = 1L;

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name = "id", updatable = false, nullable = false)
    private Long id;
    @Version
    @Column(name = "version")
    private int version;

    @Column
    private String comment;

    @ManyToOne
    @JoinColumn(name = "book_id")
    private Book book;

    public Long getId() {
        return this.id;
    }

    public void setId(final Long id) {
        this.id = id;
    }

    public int getVersion() {
        return this.version;
    }

    public void setVersion(final int version) {
        this.version = version;
    }

    public String getComment() {
        return comment;
    }

    public void setComment(String comment) {
        this.comment = comment;
    }

    public Book getBook() {
        return book;
    }

    public void setBook(Book book) {
        this.book = book;
    }
    
    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if (!(obj instanceof Review)) {
            return false;
        }
        Review other = (Review) obj;
        if (id != null) {
            if (!id.equals(other.id)) {
                return false;
            }
        }
        return true;
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + ((id == null) ? 0 : id.hashCode());
        return result;
    }
    
    @Override
    public String toString() {
        String result = getClass().getSimpleName() + " ";
        if (comment != null && !comment.trim().isEmpty()) {
            result += "comment: " + comment;
        }
        return result;
    }
   }

but when I load the authors list using:

    EntityManager em = emf.createEntityManager();
    em.getTransaction().begin();

    EntityGraph<?> graph = em.createEntityGraph("graph.AuthorBooks");
    List<Author> authors = em.createQuery("SELECT DISTINCT a FROM Author a",
            Author.class).setHint("javax.persistence.loadgraph", graph).getResultList();

    em.getTransaction().commit();
    em.close();

I can see in the logs it is also getting the reviews:

12:49:10,125 DEBUG SQL:128 - select distinct author0_.id as id1_0_0_, book2_.id as id1_1_1_, author0_.firstName as firstnam2_0_0_, author0_.lastName as lastname3_0_0_, author0_.version as version4_0_0_, book2_.publishingDate as publishi2_1_1_, book2_.title as title3_1_1_, book2_.version as version4_1_1_, books1_.authorId as authorid2_2_0__, books1_.bookId as bookid1_2_0__ from Author author0_ left outer join BookAuthor books1_ on author0_.id=books1_.authorId left outer join Book book2_ on books1_.bookId=book2_.id
12:49:10,176 DEBUG SQL:128 - select reviews0_.book_id as book_id5_3_0_, reviews0_.id as id1_3_0_, reviews0_.id as id1_3_1_, reviews0_.book_id as book_id5_3_1_, reviews0_.comment as comment2_3_1_, reviews0_.rating as rating3_3_1_, reviews0_.version as version4_3_1_ from Review reviews0_ where reviews0_.book_id=?
12:49:10,183 DEBUG SQL:128 - select reviews0_.book_id as book_id5_3_0_, reviews0_.id as id1_3_0_, reviews0_.id as id1_3_1_, reviews0_.book_id as book_id5_3_1_, reviews0_.comment as comment2_3_1_, reviews0_.rating as rating3_3_1_, reviews0_.version as version4_3_1_ from Review reviews0_ where reviews0_.book_id=?
12:49:10,185 DEBUG SQL:128 - select reviews0_.book_id as book_id5_3_0_, reviews0_.id as id1_3_0_, reviews0_.id as id1_3_1_, reviews0_.book_id as book_id5_3_1_, reviews0_.comment as comment2_3_1_, reviews0_.rating as rating3_3_1_, reviews0_.version as version4_3_1_ from Review reviews0_ where reviews0_.book_id=?
12:49:10,188 DEBUG SQL:128 - select reviews0_.book_id as book_id5_3_0_, reviews0_.id as id1_3_0_, reviews0_.id as id1_3_1_, reviews0_.book_id as book_id5_3_1_, reviews0_.comment as comment2_3_1_, reviews0_.rating as rating3_3_1_, reviews0_.version as version4_3_1_ from Review reviews0_ where reviews0_.book_id=?
12:49:10,190 DEBUG SQL:128 - select reviews0_.book_id as book_id5_3_0_, reviews0_.id as id1_3_0_, reviews0_.id as id1_3_1_, reviews0_.book_id as book_id5_3_1_, reviews0_.comment as comment2_3_1_, reviews0_.rating as rating3_3_1_, reviews0_.version as version4_3_1_ from Review reviews0_ where reviews0_.book_id=?
12:49:10,192 DEBUG SQL:128 - select reviews0_.book_id as book_id5_3_0_, reviews0_.id as id1_3_0_, reviews0_.id as id1_3_1_, reviews0_.book_id as book_id5_3_1_, reviews0_.comment as comment2_3_1_, reviews0_.rating as rating3_3_1_, reviews0_.version as version4_3_1_ from Review reviews0_ where reviews0_.book_id=?

Although, in my entity graph, I am specifying to load only Authors and Books Any idea why I am running into the N+1 select issue?

Upvotes: 0

Views: 905

Answers (1)

proximator
proximator

Reputation: 688

I found the root cause of the issue. I should use fetchgraph instead of loadgraph

 EntityGraph<?> graph = em.createEntityGraph("graph.AuthorBooks");
 List<Author> authors = em.createQuery("SELECT DISTINCT a FROM Author a",
            Author.class).setHint("javax.persistence.fetchgraph", graph).getResultList();

Upvotes: 1

Related Questions