hibernatejpa

JPA : how to get benefit of the 2nd level cache after Hibernate changed to prevent select n+1


Situation

Imagine the cacheable JPA Entity :

@Entity
@Table(name = "COUNTRY")
@Cacheable
@Getter
@Setter
public class CountryEntity {

    @Id
    @Column(name = "CODE")
    private String code;

    @Column(name = "NAME")
    private String name;
}

And this second JPA Entity :

@Entity
@Table(name = "CLUB")
@Getter
@Setter
public class ClubEntity {

    @Id
    @Column(name = "CODE")
    private String code;
    
    @Column(name = "NAME")
    private String name;
    
    @ManyToOne
    @JoinColumn(name = "FK_COUNTRY")
    private CountryEntity country;
}

Action

Here is a very simple instruction that will illustrate my issue :

entityManager.find(ClubEntity.class, "PSG");

Expected

Not so long ago, that code would behave like this :

So, I was expecting to only see this SQL query in the logs :

Hibernate: select ce1_0.code,ce1_0.fk_country,ce1_0.name,ce1_0.fk_stadium from club ce1_0 where ce1_0.code=?

Actual

I was surprised to observe a different behavior with a more recent version of Hibernate (6.6.29.Final actually) than the ones I was used to have. In order to prevent a potential select n+1 issue, Hibernate performs a join fetch between the ClubEntity and the CountryEntity entities :

Hibernate: select ce1_0.code,c1_0.code,c1_0.name,ce1_0.name,ce1_0.fk_stadium from club ce1_0 left join country c1_0 on c1_0.code=ce1_0.fk_country where ce1_0.code=?

The problem

Although I do understand the reason why Hibernate behaves like this (the idea is to help the developer to get rid of the select n+1 issues), I have no clue how to indicate Hibernate to not perform such an optimization. Because in my case, I mark, on purpose, all cacheable entities as EAGER (I preload them all first). With this "optimization", I have no way to get benefit of the cache since I will always pay the cost of the join in the query, when a no-join query would have done the job easily.

I tried to mark the attribute ClubEntity.country's fetch mode as LAZY, but this doesn't work :

Question

My question to you guys is : how to get an attribute typed with a cacheable entity loaded for free by getting the value from the cache, and indicate Hibernate not to perform the useless join ?

Thanks for your help :)


Solution

  • The answer mentioned by Chris is the solution :

        @ManyToOne
        @Fetch(FetchMode.SELECT)
        @JoinColumn(name = "FK_COUNTRY")
        private CountryEntity country;
    

    Because the FetchModeType is marked as EAGER, the default FetchMode is JOIN. That's why in my case, I have to force it to SELECT