How to set a Collection/List to a named parameter of a JPA criteria query? - jpa

A single named parameter can be set to a JPA criteria query something like the following. The parameter is of the type Long in this case.
public StateTable find(Long id)
{
CriteriaBuilder criteriaBuilder=entityManager.getCriteriaBuilder();
CriteriaQuery<StateTable> criteriaQuery = criteriaBuilder.createQuery(StateTable.class);
Metamodel metamodel=entityManager.getMetamodel();
EntityType<StateTable> entityType = metamodel.entity(StateTable.class);
Root<StateTable> root = criteriaQuery.from(entityType);
ParameterExpression<Long> parameterExpression=criteriaBuilder.parameter(Long.class);
criteriaQuery.where(criteriaBuilder.equal(root.get(StateTable_.stateId), parameterExpression));
TypedQuery<StateTable> typedQuery = entityManager.createQuery(criteriaQuery);
return typedQuery.setParameter(parameterExpression, id).getSingleResult();
}
This query inside the method returns a single object of the StateTable (just say state) entity which I'm dealing with and corresponds to the following JPQL query.
entityManager.createQuery("select s from StateTable s where s.stateId=:id")
.setParameter("id", id)
.getSingleResult();
I need to find more than one row that corresponds to a list of ids supplied via java.util.List<Long>. The following is the incomplete version of the criteria query.
public List<StateTable> find(List<Long> ids)
{
CriteriaBuilder criteriaBuilder=entityManager.getCriteriaBuilder();
CriteriaQuery<StateTable> criteriaQuery=criteriaBuilder.createQuery(StateTable.class);
Metamodel metamodel=entityManager.getMetamodel();
EntityType<StateTable> entityType = metamodel.entity(StateTable.class);
Root<StateTable> root = criteriaQuery.from(entityType);
ParameterExpression<Long> parameterExpression = criteriaBuilder.parameter(Long.class);
criteriaQuery.where(criteriaBuilder.in(root.get(StateTable_.stateId)).value(parameterExpression));
TypedQuery<StateTable> typedQuery = entityManager.createQuery(criteriaQuery);
return typedQuery.setParameter(parameterExpression, 1L).getResultList();
}
It uses an in() query but I made it return only a single row, since I don't know whether it is possible to set a list of ids to ParameterExpression or not.
In short, this criteria query should correspond to the following JPQL query.
entityManager.createQuery("from StateTable where stateId in(:id)")
.setParameter("id", ids)
.getResultList();
Is there a way to set a List<Long> to ParameterExpression as specified?

The following approach worked for me.
public List<StateTable> find(List<Long> ids)
{
CriteriaBuilder criteriaBuilder=entityManager.getCriteriaBuilder();
CriteriaQuery<StateTable> criteriaQuery=criteriaBuilder.createQuery(StateTable.class);
Metamodel metamodel=entityManager.getMetamodel();
EntityType<StateTable> entityType = metamodel.entity(StateTable.class);
Root<StateTable> root = criteriaQuery.from(entityType);
//ParameterExpression<Long> parameterExpression = criteriaBuilder.parameter(Long.class);
//criteriaQuery.where(criteriaBuilder.in(root.get(StateTable_.stateId)).value(parameterExpression));
criteriaQuery.where(root.get(StateTable_.stateId).in(ids));
TypedQuery<StateTable> typedQuery = entityManager.createQuery(criteriaQuery);
return typedQuery.getResultList();
}
I just added the following line.
criteriaQuery.where(root.get(StateTable_.stateId).in(ids));
removing the above commented lines from the incomplete version of the query in the question.

I was recently investigating the same thing and found a solution that shouldn't impact server-side query caching.
Using a ParameterExpression as part of the In clause
Please note that this should have been a response to a comment from Jannik Jochem under this page's answer; however, I am few rep short for that, so feel free to kill this post and add a comment if you have enough rep.

Related

How to avoiding AND condition if parameter is null in Spring Data JPA query

I am trying to get the result of one query using Spring Data JPA. Here I am sending some parameter and receiving result according to that.
My repository query is,
#Query("select u.username,p.pname from Users u join u.priviJoin p where u.username = :uname AND p.pname = :pname")
List<Users> findByUsername(#Param("uname") String uname , #Param("pname") String pname );
And calling from controller like the following,
#RequestMapping(value = "/joinResult", method = RequestMethod.GET)
public List<Users> joinResultShow()
{
return (List<Users>) userRepo.findByUsername("test_user","testRole");
}
Here we can see that if I am passing some value then only checking according to that parameter. Here I need to modify my query like if parameter is null, then not need to use AND condition in query.
How can I modify this query for avoiding AND condition if parameter is null? I am new to Spring Data JPA world.
Here are some possible options for you
1. Create multiple methods in your repository like
#Query("select u.username,p.pname from Users u join u.priviJoin p where u.username = :uname AND p.pname = :pname")
List<Users> findByusernamewithRole(#Param("uname") String uname , #Param("pname") String pname );
#Query("select u.username,p.pname from Users u join u.priviJoin p where u.username = :uname")
List<Users> findByUsernameWithoutRole(#Param("uname") String uname);
Write a custom respository and use EntityManager. With this you can create a dynamic queries based on your input using CriteriaBuilder and use this criteria in querying.
Last and the most preferred option in case of dynamic inputs(like you have) is Querydsl.
Some articles about querydsl
http://www.baeldung.com/querydsl-with-jpa-tutorial
http://www.querydsl.com/static/querydsl/latest/reference/html/ch02.html

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
)

I am trying to use dynamic order by but the list retrieved is not ordered

public List<Series> findSeries(int period, String fieldname, int num) {
TypedQuery<Series> query = em.createQuery(
"select s from Series s where s.period = ?1 order by ?2",
Series.class);
query.setParameter(1, period);
query.setParameter(2, fieldname);
query.setMaxResults(num);
return query.getResultList();
}
This is the method I am using. I think order by isn't even getting executed, it doesn't give any error even when I pass incorrect fieldname.
When it comes to dynamic limit and ordering, its best to use PagingAndSortingRepository so now my Repository extends this repository. I can simply use JPA criteria query as below.
If u want to learn more about JPA criteria query i found this very helpful http://docs.spring.io/spring-data/data-jpa/docs/1.0.x/reference/html/#jpa.query-methods.query-creation
#Repository
public interface SeriesRepository extends PagingAndSortingRepository<Series,Long>{
List<Series> findByPeriod(int period, Pageable pageable);
}
And then when I call this method from my dao i can just instantiate PageRequest which is one of the implementation of Pageable. I can add limit and sorting order to this instance.
public List<Series> getSeriesByFilter(int period, String fieldname, int num) {
Sort sort = new Sort(Sort.Direction.ASC, fieldname);
Pageable pageable = new PageRequest(0, num, sort);
return seriesRepo.findByPeriod(period, pageable);
}
You cannot pass variables as column name in order by.
There is a work around which may help you achieve what you are trying.
public List<Series> findSeries(int period, String fieldname, int num) {
String query = "select s from Series s where s.period = "+period+" order by "+fieldname;
return entityManager.createQuery(query).getResultList();
}
Check this question Hibernate Named Query Order By parameter
There are ways to pass column name in order by in ASP, however I am not able to find anything in Spring or JPA.
"Order By" using a parameter for the column name
http://databases.aspfaq.com/database/how-do-i-use-a-variable-in-an-order-by-clause.html

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;

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.