3
votes

I would like if there is a way to fine tune JPA/Hibernate to manage the following use case. I have the following object model:

class Parent {
    ...
    @OneToMany(mappedBy = "definizione", fetch = FetchType.LAZY)
    private List<Child> childs; 
    ... 
}

class Child {
    ...
    @ManyToOne(fetch = FetchType.LAZY)
    private GrandChild grandChild;
    ...
}


class GrandChild {
 ...
}

Then I execute the following code:

Parent parent = entityManager.find(id, Parent.class); // (1) 
List<Child> childs = parent.getChilds(); // (2)
GrandChild grandChild = null;
for(Child child : childs) {
     grandChild = child.getGrandChild(); // (3)
    //do somthing with childs
}

What I want to achieve is:

  1. to tune hibernate on use case basis, i.e. without changing the entity class;
  2. make hibernate perform two queries:

    select parent0_.* -- all Parent columns
    from PARENTS parent0_ 
    where parent0_.ID=?
    
    SELECT 
        childs0_.* -- all Child columns
        grandchild1_.* -- all GrandChild columns
    FROM childs childs0_
    LEFT OUTER JOIN grand_childs grandchild1_ ON childs0_.grand_child_id = grandchild1_.id
    WHERE childs0_.parent_id =?
    

The basic behaviour of the snippet above with the Child - GrandChild lazy fetch are:

  • (1) one query for the Parent entity is performed;
  • (2) one query for all Child entity of the Parent entity is performed;
  • (3) n query, one for each the GrandChild entity.

By reading Hibernate Fetching, I found the following solutions, but none of them achieve what I want:

Change entity class fetch strategy

class Child {
    ...
    @ManyToOne(fetch = FetchType.EAGER)
    private GrandChild grandChild;
    ...
}

PROS: The number of queries performed are the ones desired;
CONS: this solution affects other use cases, for some reasons I don't want to change the fetch strategy at entity class level.

Dynamic fetching via queries

This case is valid both for jpql and Crieria query.

final Parent parent = entityManager.createQuery(
        "SELECT p FROM Parent p LEFT JOIN FETCH p.childs c JOIN FETCH c.grandChild WHERE p.id = :id",
        Parent.class
)
        .setParameter("id", id)
        .getSingleResult();

List<Child> childs = parent.getChilds(); 
GrandChild grandChild = null;
for (Child child : childs) {
    grandChild = child.getGrandChild();
    //do somthing with childs
}

The executed query is:

SELECT parent0_.*,    -- all Parent fields
    childs1_.*,    -- all Child fields
    grandchild2_.* -- all GrandChild fields
FROM parents parent0_
    LEFT OUTER JOIN childs childs1_ ON parent0_.id = childs1_.parent_id
    LEFT JOIN grand_childs grandchild2_ ON childs1_.grand_child_id = grandchild2_.id
WHERE parent0_.id =?

PROS: Only one query performed.
CONS: A lot of duplicated data are loaded from the db, I don't want to load Parent entity more then once.

Dynamic fetching via JPA entity graph

@Entity 
@NamedEntityGraph(
        name = "parent.childs.grandchild",
        attributeNodes = {
                @NamedAttributeNode(value = "childs", subgraph = "childs.grandchild")
        },
        subgraphs = {
                @NamedSubgraph(
                        name = "childs.grandchild",
                        attributeNodes = {
                                @NamedAttributeNode(value = "grandChild")
                        }
                )
        }
)
public class Parent extends BaseEntity{
...
}

And the code to load:

    final Parent parent = entityManager.find(
            Parent.class,
            id,
            Collections.singletonMap(
                    "javax.persistence.fetchgraph",
                    entityManager.getEntityGraph( "parent.childs.grandchild" )
            )
    );
    List<Child> childs = parent.getChilds();
    GrandChild grandChild = null;
    for (Child child : childs) {
        grandChild = child.getGrandChild();
        //do somthing with childs
    }   

The executed query is the same of Dynamic fetching via queries, so pros and cons are the same.

1
@JBNizet, I read the doc, but I don't find a way to do what I would on the child grand-child association only, so I posted the question, to ask if is it possible, or the only way of doing it is to change te fetch strategy - landal79
I have read your updates, about Dynamic fetching via queries Hibernate will NOT load your Parent mutiple times so you will end up with a single parent object loaded. You cant achive what you want in a single query. - Wilder Valera

1 Answers

2
votes

You can perform a query and fetch all at once with JOIN FETCH.

String hql = "SELECT parent FROM Parent parent " +
            "LEFT JOIN FETCH parent.child child " +
            "JOIN FETCH child.grandChild " +
            "WHERE parent.id = :parentId";

Parent parent = entityManager.createQuery(hql, Parent.class).getSingleResult()
                             .setInteger("parentId", parentId);
List<Child> childs = parent.getChilds();

for(Child child : childs) {
    GrandChild grandChild = child.getGrandChild();
    //more code...
}

Take a look to this doc: https://docs.jboss.org/hibernate/orm/5.1/userguide/html_single/chapters/fetching/Fetching.html