Hibernate Parent/Child SELECT N+1 issue - java

I jave the following mapped superclass that provides a basic implementation for a parent/child self relationship to create a parent/child list for unlimited nesting of items (i.e. Categories)
#MappedSuperclass
public abstract class ParentChildPathEntity<N extends ParentChild> implements MaterializedPath<N> {
#ManyToOne(fetch=FetchType.LAZY)
#JoinColumn(name = "parent_id")
private N parent;
#Column(name = "name", unique = true)
private String name;
#OneToMany(mappedBy = "parent", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
private Set<N> children = new HashSet<N>();
If I load the entire table with fetch join on the parent and children, a single select loads all the records and i can happily traverse the tree. my problem comes in when i specify to retrieve a node on the tree. i want the node and all its children in a single select. below is the hql for loading the entire table:
hql.append(String.format("tree from %s tree ", tableName));
hql.append("left join fetch tree.parent ");
hql.append("left join fetch tree.children ");
if i specify the node name, i.e.:
where tree.name = :name
then hibernate retrieves the node, but when i access the children i get the SELECT N+1 issue. I realize why this is happening, (because of the tree.name = :name) but is there a way to write the HQL so it loads the specified node and all its children?
I'm just trying to figure out a way to support a simple nested item's list where i can retrieve any parent node and its children with a single select
thanks in advance,

Have you tried using the #BatchSize annotation?
#BatchSize(size = 20)
Ex:
#OneToMany(mappedBy = ..., fetch = FetchType.LAZY)
#BatchSize(size = 20)
public SortedSet<Item> getItems() { ... }
Then, if you specify the join to children in your HQL, you should be able to avoid n+1 select. I am not sure, offhand, if there is a way to specify the batch size in the HQL statement.

Related

Backward compatibility of #orderColumn: hibernate

I have 2 entities with oneToMany relationship. I want to maintain the insertion order for child entity. I used #orderColumn for that. Code:
Parent Class:
#Entity
public class Order{
private String orderId;
#OneToMany(mappedBy = "order", cascade = CascadeType.ALL, fetch = FetchType.LAZY, orphanRemoval = true)
#NotEmpty
#OrderColumn
private List<OrderItem> orderItems = new ArrayList<>();
}
Child class:
#Entity
public class OrderItem{
#Id
private String orderItemId;
#ManyToOne
#JoinColumn(name = "order_id", nullable = false)
private Order order;
}
The issue that I'm facing here is orderColumn is not backward compatible. i.e. it adds an column in the child table with name "order_item_order". It works fine for the records that are getting created after this change but for the previous records, the column is null and it results in below exception:
org.hibernate.HibernateException: null index column for collection
I have tried setting the default value to 0 for the column. In that case it returns only one record for child.
Suggestions please.
You have two solutions :
Proceed with the #OrderColumn but fill it with the right values : index starting at 0, incrementing by 1 (migrate your data thanks to a sql scripts or a two steps migration from java)
Proceed with #OrderBy annotation : add a creation_date column, fill it when you store the object (like in the create(ModelClass model) method of your repository) and set it to a default value in the past

DTO projection on a recursive structure

In my data model there is an entity "location" which is recursively. Furthermore there are relations to other entities.
The corresponding JPA (Spring Data JPA) entity looks like:
#Entity
#Table(name = "location")
class Location{
#OneToMany(mappedBy = "parent", orphanRemoval = true)
#OrderBy("name ASC")
Set<Location> children = null
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "parent_id")
Location parent = null
#Column(name = "name")
String name = null
#OneToMany(mappedBy = "location", fetch = FetchType.EAGER)
Stops stops = null
...
What is the most performant way to do a read only query? I just need the information inside the entity (table location) with the complete recursive structure but no information from the related entities.
I've read the phrase DTO projection, but nothing about what to do with a recursive structure.
Reading a recursive structure is usually done by making use of what SQL calls a recuresive CTE. JPA does not support that out of the box, because not all RDBMS support it. If you know that your DBMS supports it, you can make use of the following SQL to do this:
WITH RECURSIVE nodes(id, parent_id) AS (
SELECT id, parent_id FROM location l where id = ?
UNION ALL
SELECT l.id, l.parent_id FROM nodes n JOIN location l ON n.parent_id = l.id
)
SELECT id, parent_id FROM nodes
With that you get a list of a specific and all parent location ids as well as their respective parents which is flat. You will have to bring structure into this.
List<Object[]> result = //get the result of the query
Map<Integer, LocationDto> locationMap = new HashMap<>();
result.forEach(r -> locationMap.put(result.get(0), new LocationDto(result[0], result[1])));
locationMap.values().forEach(l -> l.setParent(locaitonMap.get(l.getParentId())));
If you don't want to make use of plain SQL because of portability concerns or just because you don't want to give up on your abstraction, you can make use of Blaze-Persistence which works on top of JPA and adds support for CTEs. Your query with blaze-persistence would look like this
List<LocationCte> result = criteriaBuilderFactory.create(entityManager, LocationCte.class)
.withRecursive(LocationCte.class)
.from(Location.class, "l")
.bind("id").select("l.id")
.bind("parent").select("l.parent.id")
.where("id").eq(initialId)
.unionAll()
.from(Location.class, "l")
.innerJoinOn(LocationCte.class, "cte")
.on("cte.parent").eqExpression("l.id)
.end()
.bind("id").select("l.id")
.bind("parent").select("l.parent.id")
.end()
.from(LocationCte.class)
.getResultList();
You will also need this special entity class
#CTE
#Entity
public class LocationCte {
#Id Integer id;
Integer parent;
}

How to fetch grand children table with Hibernate annotation?

I have 3 tables working as LetterOfCredit has many ProformatInvoice which has many PurchaseOrder.
I would like to get in my entity LetterOfCredit the list of all the PurchaseOrder linked via ProformatInvoice.
In SQL it looks like :
SELECT *
FROM purchase_order
JOIN proformat_invoice pi ON pi.id = pi_id
JOIN letter_of_credit lc ON lc.id = pi.lc_id
WHERE lc_id = 3;
But in LetterOfCredit.java, I tried to use proformat_invoice as a jointable but I get an empty list....
#OneToMany(fetch = FetchType.LAZY)
#JoinTable(
name="proformat_invoice",
joinColumns = #JoinColumn(name="lc_id", referencedColumnName = "id"),
inverseJoinColumns = #JoinColumn(name="id", referencedColumnName = "pi_id")
)
private List<PurchaseOrder> purchaseOrders;
Could you tell me what I did wrong?
We can fetch the grand children efficiently by using hibernate annotation ---- #OneToMany( mappedBy = "category", fetch = FetchType.LAZY ).
So in this case this will not give an exception if its unable to fetch the sub children till session is open.
We can manually pull the sub children by using the getters in our code without getting any exception , as we have already told the compiler that it will be lazy loading because there are child elements and there sub child elements.

How can I use Hibernate to load a list of entities and a subset of those entities' related entities?

I have a query I'd like to run against a table, let's call it parent where I'm grabbing all rows that match a certain criteria. In SQL:
select * from parent where status = 'COMPLETE';
I have this table and another related table (child) defined as Hibernate entities such that:
#Entity
#Table(name = "parent")
public class Parent {
//...
#OneToMany(mappedBy = "parent")
private Set<Child> children;
//...
}
#Entity
#Table(name = "child")
public class Child {
//...
#ManyToOne
#JoinColumn(name = "parent_id")
private Parent parent;
#Column(name = "key")
private String key;
//...
}
I'd like my query to ALSO pull two optional child records where key is one of two values. So, in SQL:
select *
from parent p, child ca, child cb
where p.status = 'COMPLETED'
and p.id *= ca.parent_id
and ca.key = 'FIRST_KEY'
and p.id *= cb.parent_id
and cb.key = 'SECOND_KEY';
I could do this in Hibernate by just grabbing the result from the first query and iterating over the children collection looking for the rows I want but that's terribly inefficient: one query that does two outer joins vs one query + an additional query for each looking for the children I care about.
Is there a way to replicate the outer joins in the query above in Hibernate where the objects returned will have the children collection only populated with the two (or less, since they are optional) entities I am interested in?
You don't need two outer joins. You could simply use this HQL and Hibernate will add the right children to the right parent:
List<Parent> parentList = session
.createQuery("from Parent p left join fetch p.children c" +
" where p.status = 'COMPLETE' " +
" and (c.key = 'FIRST_KEY' or c.key = 'SECOND_KEY')")
.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY)
.list();
for(Parent parent:parentList) {
System.out.println(parent);;
}
Hope that solves your problem.

JPA2 : N+1 select even with a query with JOIN

In my application, I have an entity A with a list of entities B that should be fetched eagerly :
#Entity
public class A
{
...
/* #OrderBy("cValue.id ASC") */
#OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JoinColumn(name="A_ID", nullable=false)
private List<B> BEntries = new ArrayList<B>();
...
}
#Entity
public class B
{
...
#ManyToOne
#JoinColumn(name = "C_ID", nullable = false)
private C cValue;
...
}
In order to get the list of A, I was first doing this simple query :
CriteriaBuilder critBuilder = em.getCriteriaBuilder();
CriteriaQuery<A> critQuery = critBuilder.createQuery(A.class);
Root<A> critRoot = critQuery.from(A.class);
critQuery.select(critRoot);
But there I saw that Hibernate was doing N+1 select queries on the database, 1 on class A, and N on class B (where N is the number of tuples of A in DB).
I was very surprise that, for eager fetching, Hibernate was not directly doing a LEFT JOIN query.
So I first tried to use the annotation #Fetch(FetchMode.JOIN) of Hibernate, but it was not working as expected.
So I transformed my list query with the following additional instructions:
Join<A,B> joinAB = critRoot.join(A_.BEntries, JoinType.LEFT);
joinAB.join(B_.cValue, JoinType.LEFT);
Ok, now the resulting SQL query contains all the needed LEFT JOIN to build the full A object eagerly... but it's still doing the other N queries on B table!
I first thought it was coming from the #OrderBy annotation I put on the Bentries parameter, but even when removed, it's still doing N+1 selects instead of 1...
Any idea why it's behaving like this?... and even why it's not doing a LEFT JOIN by default on eagerly fetched collections in entities?

Categories

Resources