Search by nested property of collection field with criteria api - jpa

I'm trying to find all entities that have some nested elements and nested elemens have collections of elements, and I need to find it by property of those collections.
It would be something like this
class A{
private B b;
}
class B{
private Collection<C> cCol;
}
class C{
private String name;
}
So I want to get all A elements that have B elements that have a C which name matches given parameter.
Not sure how to do it with JPA Critieria API. I know there is in predicate, or MEMEBER OF in JPQL but I need to search by property of element in collection, not a collection member.
Tried things like root.get(a.b.c.name) and also with root.fetch(a.b) or root.fetch(b.c) but always ended up with some exceptions about illegal api usage

I want to get all A elements that have B elements that have a C which name matches given parameter.
When trying to navigate the criteria API I find it immensely helpful to write the JPQL query first. Here it is:
SELECT a
FROM A a
WHERE EXISTS(
SELECT c FROM a.b b JOIN b.cCol c WHERE c.name = 'condition'
)
Now the criteria API becomes clearer (if that is possible at all):
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<A> aQuery = cb.createQuery(A.class);
Root<A> a = aQuery.from(A.class);
Subquery<C> cSubquery = aQuery.subquery(C.class);
Root<A> aSubroot = cSubquery.correlate(a);
Join<A, B> b = aSubroot.join("b"); // "b" is the name of the property of A that points to B
Join<B, C> c = b.join("cCol"); // "cCol" is the name of the property of C that holds the related C objects
cSubquery.select(c);
cSubquery.where(cb.equal(c.get("name"), "XXXXXXX"));
aQuery.where(cb.exists(cSubquery));
TypedQuery<A> aTypedQuery = em.createQuery(aQuery);
aTypedQuery.getResultList();
The names of the Java variables are the same as in the JPQL, e.g. Join<A, B> b corresponds to the JPQL FROM a.b b.

The below should work
root.get("a").get("b").get("name")
See
How to create specification using JpaSpecificationExecutor by combining tables?

I was successful by using
root.**join**("a").get("b").get("name"):
because it's a collection.

I did it by using joins like this:
builder = entityManager.getCriteriaBuilder();
CriteriaQuery<A> query = builder.createQuery(A.class);
Root<A> root = query.from(A.class);
root.join("b").join("c").get("name");

Related

CriteriaBuilder query with sub-query, single column result, max function and generic types using metamodel

My objective is to replace an old JPQL query with a generic type-safe helper method using javax.persistence.metamodel and javax.persitence.criteria.
The query is essentially
select * from table
where field1 = arg1
and field2 = arg2
and field3 = (select max (field3)
from table
where field1 = arg1
and field2 = arg2
and field3 <= arg3
)
Admittedly this is maybe too specialized a query to generalize but I see the need for 2 or 3 other more generic helpers which I can model on this solution.
I have been googling the Criteria documentation (one problem is it's easy to surf a google search result list and mistakenly move from a javax.persitence page to a JBoss Hibernate page... and they are NOT the same).
I have obviously not found a one-stop shop that tells me all I need to know:
how to select a single field in a CriteriaQuery
how to structure a subquery in a CriteriaQuery Expression
how to write a max aggregate function call using CriteriaBuilder
how to properly use Static Metamodel attributes to specify generic classes in a CriteriaBuilder query, when the table being queried has a composite key which is mapped by composite key class (using #EmbeddedId)
OK. I already had the answer before I posted the question but I thought it might be useful to publish what I found.
The use case is a CHARGE table that provides CHG_NU values for ranges of product-option-level values. The appropriate
CHG_NU from the table is the one that matches a PROD_CD and OPTION_TYPE and does not exceed the OPTION_LEVEL.
Here's the method I ended up writing (the comments are specific to the above use-case but the code is generic):
public static <X, KT, PT, BT, NT extends Number> X findWithUpperLimit (Class<X> rootClass, Class<NT> numericClass,
SingularAttribute<X, KT> keyAttr,
SingularAttribute<KT, PT> arg1Attr, PT arg1Val,
SingularAttribute<KT, BT> arg2Attr, BT arg2Val,
SingularAttribute<KT, NT> numericAttr, NT number,
EntityManager em)
{
List<X> results;
CriteriaBuilder cb = em.getCriteriaBuilder ();
// set up the query (returns a full record of the CHARGE table)...
CriteriaQuery<X> cq = cb.createQuery (rootClass);
// ... and the subquery (returns only the BigDecimal OPT_LEVEL)
Subquery<NT> sq = cq.subquery (numericClass);
// set up the root objects for the CHARGE table. Both the query and the subquery are on the same table
Root<X> root = cq.from (rootClass);
Root<X> sqRoot = sq.from (rootClass);
// the query objects and the criteria builder are used to structure the query,
// the root objects are used to get metadata from the table to assign table elements to the criteria
// the subquery gets the closest optLevel to the passed-in number...
sq.select (cb.max (sqRoot.get (keyAttr).get (numericAttr)))
.where (cb.and
(cb.equal (sqRoot.get (keyAttr).get (arg1Attr), arg1Val),
cb.equal (sqRoot.get (keyAttr).get (arg2Attr), arg2Val),
cb.le (sqRoot.get (keyAttr).get (numericAttr), number)
));
// ...and the main query matches the passed-in prodCd, optType and the optLevel found by the subquery.
cq.select (root).where (cb.and (cb.equal (root.get (keyAttr).get (arg1Attr), arg1Val),
cb.equal (root.get (keyAttr).get (arg2Attr), arg2Val),
cb.equal (root.get (keyAttr).get (numericAttr), sq)
));
results = em.createQuery (cq).getResultList ();
return results.size() == 0 ? null : results.get (0);
}
This is a code snippet that calls it:
Charge charge = DAOHelper.findWithUpperLimit (Charge.class, BigDecimal.class,
Charge_.key,
ChargeKey_.prodCd, invoice.getCharge().getChargeKey().getProdCd(),
ChargeKey_.optType, invoice.getCharge().getChargeKey().getOptType(),
ChargeKey_.optLevel, invoice.getCharge().getChargeKey().getOptType(),
em);
and here's the SQL that it generates:
select charge0_.OPTION_TYPE_CD as OPTION_1_50_,
charge0_.OPTION_LEVEL as OPTION_LEV2_50_,
charge0_.PROD_CD as PROD_CD3_50_,
charge0_.CHG_NU as CHG_NU4_50_
from CHARGE charge0_
where charge0_.PROD_CD=?
and charge0_.OPTION_TYPE_CD=?
and charge0_.OPTION_LEVEL=(select max(charge1_.OPTION_LEVEL)
from CHARGE charge1_
where charge1_.PROD_CD=?
and charge1_.OPTION_TYPE_CD=?
and charge1_.OPTION_LEVEL<=1358.00
)

TypedQuery<x> returns vector of Object[] instead of list of x-type object

I have a method:
public List<Timetable> getTimetableTableForRegion(String id) {
List<Timetable> timetables;
TypedQuery<Timetable> query = em_read.createQuery("SELECT ..stuff.. where R.id = :id", Timetable.class).setParameter("id", Long.parseLong(id));
timetables = query.getResultList();
return timetables;
}
which returns this:
so, what am I missing in order to return a list of Timetable's?
ok, so, ..stuff.. part of my JPQL contained an inner join to other table. Even through in SELECT there were selected fields just from one table, which was used as type - Timetable, Eclipslink was unable to determine if this fields are part of that entity and instead of returning list of defined entity returned list of Object[].
So in conclusion: Use #OneToMany/#ManyToOne mappings (or flat table design) and query just for ONE table in your JPQL to be able to typize returned entities.
Not sure it might be something is looking for, but I had similar problem and converted Vector to ArrayList like this:
final ArrayList<YourClazz> results = new ArrayList<YourClazz>();;
for ( YourClazzkey : (Vector<YourClazz>) query.getResultList() )
{
results.add(key);
}
i have faced the same problem. and my entity has no one to one or one to many relationship. then also jpql was giving me queryresult as vector of objects. i changed my solution to query to criteria builder. and that worked for me.
code snippet is as below:
CriteriaBuilder builder = this.entityManager.getCriteriaBuilder();
CriteriaQuery<Timetable> criteria = builder.createQuery(Timetable.class);
Root<Enumeration> root = criteria.from(Timetable.class);
criteria.where(builder.equal(root.get("id"), id));
List<Timetable> topics = this.entityManager.createQuery(criteria) .getResultList();
return topics;

LINQ to Entities does not recognize the method with Let Statement

I am in the process of converting an application that uses LINQ to SQL over to LINQ to Entities. I use a repository pattern and I have run in a problem that works in LINQ to SQL but not Entities.
In my data layer, I use LINQ statements to fill my object graph so that none of my database entities are exposed anywhere else. In this example, I have a Lookup Respository that returns a list of Categories. It looks like this:
public IQueryable<Entities.DomainModels.Category> getCategories()
{
return (from c in Categories
where !c.inactive
orderby c.categoryName
select new Entities.DomainModels.Category
{
id = c.categoryID,
category = c.categoryName,
inactive = c.inactive
});
}
Later, I want to put the categories into a sub query and it looks like this:
var d = from p in Programs
let categories = (from pc in p.Categories
join c in getCategories() on pc.categoryID equals c.id
select c)
select new
{
id = p.id,
title = p.title
categories = categories.ToList()
};
When I run this, I get the following error:
LINQ to Entities does not recognize the method 'System.Linq.IQueryable`1[Entities.DomainModels.Category] getCategories()' method, and this method cannot be translated into a store expression.
For reference, the following works though it doesn't return the data I need (it's basically a join):
var q = from p in Programs
from pc in p.Categories
join c in getCategories() on pc.categoryID equals c.id
select new
{
id = p.id,
category = c
};
I understand what the error means in concept however LINQ to SQL would make it work. I have this pattern throughout my data layer and I really want to keep it. Should this be working? If not, how can I modify it without mixing my layers.
You cant pass getCategories() to EF.
The query must be destructible to expression tree.
Calculate getCategories() first.
eg
var simpleList = getCategories().Select(id).Tolist;
then use a contains
where(t=> simpleList.Contains(t.CatId) // or the query syntax equivalent

Entity Framework: selecting from multiple tables

I have a statement:
var items = from e in db.Elements
join a in db.LookUp
on e.ID equals a.ElementID
where e.Something == something
select new Element
{
ID = e.ID,
LookUpID = a.ID
// some other data get populated here as well
};
As you can see, all I need is a collection of Element objects with data from both tables - Elements and LookUp. This works fine. But then I need to know the number of elements selected:
int count = items.Count();
... this call throws System.NotSupportedException:
"The entity or complex type 'Database.Element' cannot be constructed in a LINQ to Entities query."
How am I supposed to select values from multiple tables into one object in Entity Framework? Thanks for any help!
You are not allowed to create an Entity class in your projection, you have to either project to a new class or an anonymous type
select new
{
ID = e.ID,
LookUpID = a.ID
// some other data get populated here as well
};
Your code doesn't work at all. The part you think worked has never been executed. The first time you executed it was when you called Count.
As exception says you cannot construct mapped entity in projection. Projection can be made only to anonymous or non mapped types. Also it is not clear why you even need this. If your class is correctly mapped you should simply call:
var items = from e in db.Elements
where e.Something == something
select e;
If LookupID is mapped property of your Element class it will be filled. If it is not mapped property you will not be able to load it with single query to Element.

Tuple result Criteria API subquery

I am trying to use subqueries in an application I am writing using JPA 2.0 type-safe criteria API, with Hibernate 3.6.1.Final as my provider. I have no problem selecting primitive types (Long, MyEntity, etc.), but I want to select multiple columns.
Here's an example of something completely reasonable. Ignore the needless use of subquery -- it is simply meant as illustrative.
EntityManager em = getEntityManager();
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Tuple> cq = cb.createTupleQuery();
Subquery<Tuple> subQ = cq.subquery(Tuple.class);
Expression<Long> subqCount;
{
Root<MyEntity> root = subQ.from(MyEntity.class);
Path<MyEntity> filter = root.get(MyEntity.challenge);
subqCount = cb.count(root);
// How to select tuple?
Selection<Tuple> tuple = cb.tuple(filter, subqCount);
// !! Run-time exception here
Expression<Tuple> tupleExpr = (Expression<Tuple>) tuple;
// Not sure why I can't use multiSelect on a subQuery
// #select only accepts Expression<Tuple>
createSubQ.select(tupleExpr);
createSubQ.groupBy(filter);
}
cq.multiselect(subqCount);
Although the compiler doesn't complain, I still get a run-time exception.
java.lang.ClassCastException: org.hibernate.ejb.criteria.expression.CompoundSelectionImpl cannot be cast to javax.persistence.criteria.Expression
Is this a bug in hibernate, or am I doing something wrong?
If you can't use multiselect on a subquery, then how can you perform a groupBy?
If you can't use groupBy on a subquery, why is it in the API?
I have the same problem.
I can only attempt to answer your last question by saying you can only really use sub queries to perform very simple queries like:
SELECT name FROM Pets WHERE Pets.ownerID in (
SELECT ID FROM owners WHERE owners.Country = "SOUTH AFRICA"
)
The other thing I wanted to say was how much this incident reminds me of xkcd #979.
I had similar problem.
I had specification, and I wanted to get ids of objects matching this specification.
My solution:
CriteriaBuilder criteriaBuilder = em.getCriteriaBuilder();
CriteriaQuery<Tuple> tupleCriteriaQuery = criteriaBuilder.createTupleQuery();
Root<Issue> root = tupleCriteriaQuery.from(Issue.class);
tupleCriteriaQuery = tupleCriteriaQuery.multiselect(root.get(IssueTable.COLUMN_ID));//select did not work.
tupleCriteriaQuery = tupleCriteriaQuery.where(issueFilter.toPredicate(root, tupleCriteriaQuery, criteriaBuilder));
List<Tuple> tupleResult = em.createQuery(tupleCriteriaQuery).getResultList();
First I select columns (In my case I need only one column), and then I call where method to merge with my given specification.