Can Hibernate do the equivalent of fetch joins locally (via L1 cache)?

Hi, I want to query some entities with some deep eager fetching and I want to avoid both the N+1 query problem and N^k sized result sets due to NxN joins.

For example, assume I have this structure:

class Person {
    @Id
    UUID id;

    String name;

    @ManyToMany(fetch = FetchType.LAZY)
    Set<Person> friends;
}

and I want to run this HQL query:

from Person p
join fetch p.friends f1
join fetch f1.friends f2
join fetch f2.friends f3
where p.lastName = :lastName

The result set will be very large, with many Persons needlessly duplicated several times.
I would think that, if I were to fetch the entire Person table (it might not be that big), I would have enough data to follow references locally. Is Hibernate able to do this? If yes, how can I do it?

Also, is there any way to “download” intermediate tables for NxN relationships without running an actual join for Hibernate to use? If not, is a join query the only choice or is there a way to use something similar to “subselect” fetching?

The above is a silly example: in my actual case, this situation spans a few distinct tables; also, since I use multi-tenancy in one DB, while the tables are large, I can select relatively small parts of each table and have all the data I need.

What you could do is configure subselect fetching with @Fetch(FetchMode.SUBSELECT). When you then load all entities at once, and access one collection, Hibernate will initialize all collections of all previously loaded objects at once, overall with just 2 SQL statements. Though the second statement will fetch all the Person data again instead of just accessing the join table.

Since this kind of fetching might not be ideal for all your use cases in general though, I can only recommend you to look into Blaze-Persistence Entity-Views, a library that works on top of JPA/Hibernate, which supports MULTISET fetching. This fetch strategy is the best of both worlds since it’s similar to join fetching in the sense that you don’t need to execute additional SQL statements, but at the same time avoids the NxM cartesian issue, since it packs joined rows into a native composite type like e.g. JSON.

It could be as simple as this:

@EntityView(Person.class)
public interface PersonView {
    @IdMapping
    UUID getId();
    String getName();
}

@EntityView(Person.class)
public interface PersonWithFriendsView extends PersonView {
    @Mapping(fetch = MULTISET)
    Set<PersonView> getFriends();
}
2 Likes