Hibernate Criteria limit select - java

So, I have a rather complex query I am trying to make using the Hibernate Criteria API. I have the following entity classes:
Code
Gift
GiftVendor
GiftVendorStatus
with the following relationships:
Code 1<>1 Gift
Gift 1<>* GiftVendor
GiftVendor 1<>1 GiftVendorStatus
I need to build a Criteria query that returns a List of Code objects, but that restricts it to only Codes that have a Gift that have at least one GiftVendor with a GiftVendorStatus of Online. Here is the code I am using to build the criteria:
Criteria base = CodeDao.getBaseCriteria();
base.createAlias("gift","gift");
base.createAlias("gift.giftVendor","giftVendor");
base.createAlias("giftVendor.giftVendorStatus","giftVendorStatus");
base.add(Restrictions.like("giftVendorStatus.description", "Online%"));
return base.list();
This gives me a List of Code objects, restricted as I would expect. However, it also does additional queries to build out all of the unused relationships of the Gift object, even though I have all of the mappings set up with a fetch mode of Lazy. This results in 4 additional, separate queries for each of my 10000+ results.
I have code to do the query using HQL that works as expected:
String hql = "select c FROM Code c inner join c.gift g inner join g.giftVendors gv inner join gv.giftVendorStatus gvs" +
" WHERE gvs.description like :desc";
HashMap<String,Object> params = new HashMap<String, Object>();
params.put("desc", "Online%");
return performQuery(hql, params);
That code gives me a List of Code objects, as expected, without doing all of the extra queries to populate the Gift object. How do I tell Hibernate not to do those extra queries with the Criteria API?
UPDATE: The problem here is not the retrieval of the Gift table, but rather unrelated one-to-one relationships from the Gift table. For example, Gift has a one-to-one relationship to GiftCommentAggregateCache. This table is in no way related to this particular query, so I would expect lazy initialization rules to apply, and the query to GiftCommentAggregateCache to not occur unless a read is attempted. However, with the Criteria query written out as above, it makes that separate query to populate the model object for GiftCommentAggregateCache.
If I use:
base.setFetchMode("gift.giftCommentAggregateCache", FetchMode.JOIN);
then I do not have any problems. However, that means that in order for this to work as I would expect, I need to add that line for every single unused one-to-one relationship that Gift has. Any ideas as to why the Lazy rules specified in the mappings are not coming into play here?
There are a few different things I have tried:
base.setFetchMode("gift", FetchMode.LAZY); // Still does additional queries
and
base.setFetchMode("gift", FetchMode.SELECT); // Still does additional queries
and
base.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY); // Still does additional queries
and
base.setResultTransformer(Criteria.ROOT_ENTITY); // Still does additional queries
and
base.setResultTransformer(Criteria.ALIAS_TO_ENTITY_MAP); // Still does additional queries
and
base.setProjection(Projections.property("gift")); // Does not do additional queries, but incorrectly returns a List of Gift objects, instead of a List of Code objects

From the Hibernate documentation:
Join fetching: Hibernate retrieves the associated instance or collection in the same SELECT, using an OUTER JOIN.
In other words, try using base.setFetchMode("gift", FetchMode.JOIN)
I hope that helps.
Cheers

Related

JPA method to lazy-load a whole entity graph without 1+n+n*m queries?

Let's imagine I have these entity classes (I omitted the JPA annotations):
class TableA { Long id; List<TableB> tableBs; }
class TableB { Long id; List<TableC> tableCs; }
class TableC { Long id; List<TableD> tableDs; }
class TableD { Long id; int foo; }
This gives us this entity "graph"/"dependencies":
TableA ---OneToMany--> TableB ---OneToMany--> TableC ---OneToMany--> TableD
If I want to deeply load all sub-entities, sub-sub-entities and sub-sub-sub-entities of one TableA object, JPA will produce these queries:
1 query to get one TableA: it's fine, of course
1 query to lazy-load tableA.getTableBs(): it's fine too => we get n TableB entities
n queries to lazy-load all tableA.getTableBs()[1..n].getTableCs() => we get m TableC entities per TableB entity
n*m queries to lazy-load all tableA.getTableBs()[1..n].getTableCs()[1..m].getTableDs()
I'd like to avoid this 1+n*(m+1) queries to lazy-load all sub-sub-sub-entities of my TableA object.
If I had to do the queries by hand, I'd just need 4 queries:
SAME: 1 query to get one TableA
SAME: 1 query to lazy-load tableA.getTableBs(): it's fine
BETTER: 1 query to get all TableC WHERE id IN (tableA.getTableBs()[1..n].getId()) // The "IN" clause is computed in Java, I do one SQL query, and then from the TableC{id,parentTableBId,...} result, I populate each TableB.getTableC() list with Java
WAY BETTER: 1 query to get all TableD WHERE id IN (tableA.getTableBs()[1..n].getTableCs()[1..m].getId()) // Same IN-clause-computing and tree-traversal to assign all TableD childs to each TableC parents
I'd like to call either:
JpaMagicUtils.deeplyLoad(tableA); // and it does the "IN" clauses building (possibly splitting into 2 or 3 queries to have have too many "IN" ids) + tree children assignation itself, or
JpaMagicUtils.deeplyLoad(tableA, "getTablesBs().getTableCs()"); JpaMagicUtils.deeplyLoad(tableA, "getTableBs().getTableCs().getTableDs()"); // to populate one level at a time, and have a better granularity of which fields to load in bulk, and which fields not to load.
I don't think there is a way with JPA for that.
Or is there a way with JPA for that?
Or as a non-JPA-standard way, but perhaps with Hibernate? or with another JPA implementation?
Or is there a library that we can use just to do that (on top of any JPA implementation, or one implementation in particular)?
If I had to do the queries by hand, I'd just need 4 queries
Well, why not one query joining four tables?
Seriously though, if you want to limit the number of queries, I'd first try Hibernate's #Fetch(FetchMode.JOIN) (not sure if there are similar annotations for other JPA providers). It is a hint that tells Hibernate to use a join to load child entities (rather than issuing a separate query). It does not always work for nested one-to-many associations, but I'd try defining it at the deepest level of the hierarchy and then working your way up until you find the performance acceptable (or the size of the result set forbidding).
If you're looking for generic solution, then sadly I do not know any JPA provider that would follow the algorithm you describe, neither in general nor as an opt-in feature. This is a very specific use case, and I guess the price of being robust as a library is not incorporating optimizations for special-case scenarios.
Note: if you want to eagerly load an entity hierarchy in one use case but keep the entities lazily-loaded in general scenarios, you'll want to look up JPA entity graphs. You may also need to write a custom query using FETCH JOIN, but I don't think nested FETCH JOINS are generally supported.

FetchMode.JOIN to eagerly fetch a member list apparently yields cartesian product

I'm trying to optimize a complex operation involving all the Books and Videos in multiple Libraries (this is not the actual domain, for nondisclosure reasons).
The code originally used Criteria to load all the Libraries, then loaded the member Books lazily by iterating. I basically added a couple of FetchMode clauses:
List<Library> library = session
.createCriteria(Library.class)
.setFetchMode("books", FetchMode.JOIN)
.setFetchMode("videoShelves.videos", FetchMode.JOIN)
.list();
The second FetchMode clause appears to work, or at least doesn't result in obvious problems.
The first, though, blows out the number of Libraries from 6 to 248. So it looks to me as if maybe each Library is replicated once for each Book it has.
What are the conditions under which Hibernate might create unexpected duplicate instances when a FetchMode is added to the query?
The reason of this behaviour is an outer join, as #TimBiegeleisen suggested.
For more information refer
Hibernate does not return distinct results for a query with outer join fetching enabled for a collection (even if I use the distinct keyword)?
Possible solutions from the link provided above
Using Set
List<Library> library = ...;
return new ArrayList<Library>(new LinkedHashSet<Library>(library));
Using the Criteria.DISTINCT_ROOT_ENTITY result transformer
List<Library> library = session
.createCriteria(Library.class)
.setFetchMode("books", FetchMode.JOIN)
.setFetchMode("videoShelves.videos", FetchMode.JOIN).
.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY)
.list();
But, the best solution is not fetching all data in one request of course. For an example, if you want to show a list of libraries (with number of books, for an example) you can use projections to load only data you want to.
Using setFetchMode() will, by default, result in an outer join, which is what you are currently observing. If you want an INNER JOIN between the three tables then try this instead:
List<Library> library = session.createCriteria(Library.class, "library")
.createAlias("library.book", "books")
.createAlias("books.video", "videos").
.list();

What are the duplicates that are returned by Hibernate queries for collections

The hibernate documentation for executing queries says that:
Queries that make use of eager fetching of collections usually return
duplicates of the root objects, but with their collections
initialized. You can filter these duplicates through a Set.
For example if I have an Order class with list of OrderLines having one-to-many mapping between them. Then if I use Hibernate queries, in this context is my Order class called as root object? Then why Hibernate wants to load duplicate elements at all?
Please help me in understanding this, I am new to Hibernate so finding it difficult to understand the concept.
The root entity is the entity which is selected by the query:
select o from Order o ...
In this case, o is the root, of type Order.
Now if you do
select o from Order o left join fetch o.lines
and you have 2 orders, each with 3 lines, in database, then the underlying SQL query will return 6 rows, and Hibernate will also return a list of 6 Order objects. But the list will contain the first order 3 times, and the second order 3 times.
You can avoid this by
using Set<Order> deduplicated = new HashSet<Order>(listOfOrders)
adding the distinct keyword to the query:
select distinct o from Order o left join fetch o.lines

Derived Queries not working with DBRef

I am using Mongo with Spring Data.
I have two (first class) entities (#Documents) Entity1 and Entity2, where Entity1 has a reference (#DBRef) of Entity2 within it. Everything works fine, but when executing derived queries such as :
public List<Entity1> findByEntity2Property1(String property1)
The above query returns no results although there are documents with the given query params. Why is that?
I don't think the query can work as you expect it to work as MongoDB pretty much does not allow restricting results on properties of related documents pointed to via a DBRef. So you essentially have to query all Entity2 instances matching the given value on property Property1 and then query for Entity1 instances matching the DBRefed property against the results of the first query.

Hibernate Criteria Order By

I have a table called Gift, which has a one-to-many relationship to a table called ClickThrough - which indicates how many times that particular Gift has been clicked. I need to query for all of the Gift objects, ordered by ClickThrough count. I do not need the ClickThrough count returned, as I don't need anything done with it, I just want to use it for purposes of ordering.
I need the query to return a List of Gift objects directly, just ordered by ClickThrough count. How do I do this using the Criteria API? I can find a lot of documentation out there on similar information to this, but nothing quite like what I need.
Note for anyone else that comes through here looking to order by a property/column:
When using the approaches mentioned here, no results were found. The fix was to use criteria.addOrder(Order.asc(property)); instead. Notice the difference is to use addOrder, rather than add;
I've had this issue several times after running here for a quick reference.
If you want to return a list of entities or properties in combination with a count you will need a group by. In criteria this is done through ProjectionList and Projections. e.g.
final ProjectionList props = Projections.projectionList();
props.add(Projections.groupProperty("giftID"));
props.add(Projections.groupProperty("giftName"));
props.add(Projections.count("giftID"), "count"); //create an alias for the count
crit.setProjection(props);
crit.add(Order.desc("count")); //use the count alias to order by
However, since you're using ProjectionList you will also need to use AliasToBeanConstructorResultTransformer.
You have a one-to-many relationship from Gift to ClickThrough so I assume each ClickThrough is a record with some datetime or other information associated with it. In this case, I would add a "count" field to your mapping file and attach the ordering to the criterion:
criterion.add(Order.asc(count)))
The mapping property looks something like:
<property name="count" type="integer" formula="(select count(*) from Gift g inner join ClickThrough ct on ct.gift_id=g.id where g.id=ID)"/>
If you can't or don't want to change the mapping file, you could use Collections.sort() with a Comparator though it seems less efficient having to return so much data from the DB.

Categories

Resources