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.
Related
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.
We're in the process of converting our java application from using SOLR/Lucene to Elasticsearch 5.6.6.
In 1 particular area, we would previously build 1 Lucene Search BooleanQuery and run it against 3 different entities, looking for matches. We didn't need to specify the entity it would be used against until you ran the actual query.
BooleanQuery luceneQuery = myQueryBuilder.buildMyQuery();
session.createFullTextQuery(luceneQuery, entityOne);
session.createFullTextQuery(luceneQuery, entityTwo);
session.createFullTextQuery(luceneQuery, entityThree);
One sub-query within [luceneQuery] above searched on taxId, which entityOne doesn't have (no taxId indexed field) but the other 2 entities do have. However it all worked fine, no exceptions were given, I believe it just ignored the unknown/un-indexed field, not exactly sure how it worked, but it did.
Now we're converting over to Elasticsearch DSL, we need to give the entity up front so I (for better or worse) build the query 3 different times, against each entity, like so:
QueryBuilder entityOneQB = session.getSearchFactory().buildQueryBuilder().forEntity(EntityOne.class).get();
QueryBuilder entityTwoQB = session.getSearchFactory().buildQueryBuilder().forEntity(EntityTwo.class).get();
QueryBuilder entityThreeQB = session.getSearchFactory().buildQueryBuilder().forEntity(EntityThree.class).get();
// Create 3 exact (except for which entity they point at) queries
Query entityOneQuery = myQueryBuilder.buildMyQuery(entityOne);
Query entityTwoQuery = myQueryBuilder.buildMyQuery(entityTwo);
Query entityThreeQuery = myQueryBuilder.buildMyQuery(entityThree);
Where buildMyQuery() has a number of sub-queries but the one dealing with taxId looks something like:
qb.bool().should(
qb.keyword()
.onField("taxId")
.matching(taxId)
.createQuery()
);
However, now, since, entityOne doesn't have taxId as an indexed column/field, createQuery() throws an exception:
SearchException: Unable to find field taxId in EntityOne
My questions are:
Is there some way to tell Lucene to ignore the field if the entity doesn't have it?
If not, is there some way, using the passed in QueryBuilder to determine what the entity is, so that, within the taxId subquery code, I can basically say if (entityType == EntityOne) {return null;} so that this particular sub-query won't be included in the overall query?
Is there some way to tell Lucene to ignore the field if the entity doesn't have it?
Just a clarification: it's Hibernate Search that implements the DSL and throws exceptions, not Lucene. Lucene is the underlying technology, and doesn't perform much validation.
If your goal is to retrieve all three entities in a single result list, and if fields with the same name in different entity types are configured similarly (e.g. field "name" appears in entity 1 and 2, but has the same analyzer), you could simply build a single query and retrieve all three types in that single query. You will have to:
Make sure, when building the single Lucene query, to always use the query builder of an entity type that actually defines the field your targeting: if targeting taxId for instance, you can use the query builder for EntityTwo or for EntityThree, but not the one for EntityOne. Yes, that's right: you can mix multiple query builders in a single query, as long as fields with the same name are configured similarly in all targeted entities.
build the FullTextQuery that way: session.createFullTextQuery(luceneQuery, EntityOne.class, EntityTwo.class, EntityThree.class);.
If not, is there some way, using the passed in QueryBuilder to determine what the entity is, so that, within the taxId subquery code, I can basically say if (entityType == EntityOne) {return null;} so that this particular sub-query won't be included in the overall query?
No, there is not. You could pass add a parameter to your method to pass the entity type, though: buildMyQuery(Class<?> type, QueryBuilder queryBuilder) instead of buildMyQuery(QueryBuilder queryBuilder).
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();
I want to perform a criteria count query on my supertype Class which has its own HBM and not count the subclasses.
The query would then be a "non-polymorphic" query.
I tried by adding polymorphism="explicit" to my hbm and it works, but i'd like to do it only on one query and not on every queries.
I am not 100% what you are after, but you can specify the class type in a query like this:
select user from User as user where user.class=MyUserClass;
So in Hibernate, you can refer to "class" attribute to filter out results based on what class type the object has.
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