Search code examples
javahibernateebean

Fetch several OneToMany relations in Hibernate (opposed to Ebean fetch)


I have a production project, that uses pretty old Ebean ORM (came from Play Framework). Out team decided to look for a migration to newer tools. In our code we have a lot of ORM Models, and it is quite usual to have huge entity graphs (up to 20 OneToMany relations at one "nesting level", each nested up to 3 levels deep, which is A LOT of relations, that should be fetched eagerly to avoid N+1 problems). Our current framework allows us to write pretty neat code to fetch OneToMany relations, hypothetical example:

@Entity
public class A {
   @OneToMany
   private List<B> bs;

   @OneToMany
   private List<C> cs;
}

Query code:

Ebean.find(A.class)
     .fetch("bs", new FetchConfig().query())
     .fetch("cs", new FetchConfig().query())
     ... etc

That code would produce 3 database queries - one for class A, and two for relations; then Ebean would combine results of those queries automatically.

I tried to produce this kind of code in Hibernate ORM by using JPA Criteria API and NamedEntityGraphs, but could not succeed - it seems like Hibernate does not like having several OneToMany relations to be fetched at once (by producing something like MultipleBagFetchException). I understand why this exception is raised (cartessian product), but I can not find part of framework, that could split one entity graph in several database queries.

Is it possible to do in Hibernate? If no, are there any 3rd party dependencies, that could do so? How do Hibernate users deal with big entity graphs?


Solution

  • Firstly it is a fundamental limitation of JPQL that it doesn't truely support creating queries to build complex graphs [JPQL FETCH JOIN does not cut it and Hibernate makes a meal out of this by generating sql cartesian product etc]. This is one of the fundamental reasons why Ebean exists.

    JPA added FetchGroup later and that takes you much closer to the capabilities of Ebean ORM's query language. You will need to try using FetchGroup with the JPQL query to see how close you get for your use cases.

    Specific issues you can hit with Hibernate include:

    • Generating SQL cartesian product when 2 ToMany paths are fetched
    • Not honoring maxRows in SQL but instead performing client side pagination (so we no longer get the DB optimizing the query for max rows)
    • No equivalent support for large queries - Ebean's findEach() that manages the number of beans held in persistence context
    • No filterMany expression support (predicates on a ToMany path rather than root)
    • No partial object support (need to convert over to DTO queries instead)

    Extra notes:

    List vs Set: This is a Hibernate specific implementation design where Hibernate gives Set "bag semantics" (better sql implementation). With Ebean we can equally use Set or List and recommend List due to it avoiding issues related equals/hashcode on mutating beans. De-duplication when converting relations into objects is the job of the persistence context and applies equally to List and Set with Ebean.

    Ebean has a different architecture wrt dirty values meaning entity bean queries are pretty close to the cost of DTO queries. Hibernate doesn't yet support partial objects and has much higher costs for storing "old values" which means Hibernate folks promote the use of DTO queries for performance reasons. We don't have the same need with Ebean due to our architectural approach (where Ebean stores old values).

    LazyInitializationException

    This is another Hibernate specific behaviour. Ebean users don't need to deal with this at all. Additionally Ebean doesn't produce N+1 plus Ebean also has query.setDisableLazyLoading(true) if we want to stop lazy loading being invoked by mapping code. These are 3 things you'll need to deal with if you use Hibernate.

    Hibernate is "mature and powerful"

    Yes but it does currently have a different view of what ORM means and maybe always will. Specifically around support for partial objects and complex queries but you could also include sql2011 history support and soft delete support.

    Ebean has been open source since 2006 (so 15 years and counting). You can also compare Ebean github issues to Hibernate JIRA issues. There are a number of different ways to view "mature" etc. As I see it, for Hibernate to get to where Ebean is at wrt partial objects and complex queries they have some work to do.