I am new to Eclipselink JPA. I have a set of complex search queries with a number of search parameters, some of the parameters are optional. Is there a way to specify optional parameters in named query? Is the below approach an efficient way to do?
select o from Product o WHERE :value is null or o.category = :value'
Your approach would probably work, but I normally use the Criteria API in similar situations. For example, you could conditionally add predicates to your criteria query if the parameters are provided:
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Product> cq = cb.createQuery(Product.class);
Root<Product> e = cq.from(Product.class);
List<Predicate> predicates = new ArrayList<Predicate>();
if (categoryValue != null)
predicates.add(cb.equal(e.get("category"), categoryValue));
if (someOtherValue != null)
predicates.add(cb.equal(e.get("someField"), someOtherValue));
// AND all of the predicates together:
if (!predicates.isEmpty())
cq.where(cb.and(predicates.toArray(new Predicate[predicates.size()])));
List<Product> products = em.createQuery(cq).getResultList();
This is kind of an ugly example, but the CriteriaBuilder is very powerful once you become familiar with the API.
Related
I need to create a query by deciding on runtime. Basically I have few parameters and one of them will specify If I should use And or Or to combine criterias. By using Spring JPA Repository how can I do it? It is easy to do it for parameters as :
#Query(value = "SELECT u FROM User u WHERE u.name IN :names")
List<User> findUserByNameList(#Param("names") Collection<String> names);
But if I want to add one more criteria lets say size, and I need to decide which one of (AND,OR) to use to combine criteria, how can I do it?
Example:
SELECT u FROM User u WHERE u.name IN :names OR/AND size = 10;
public List<User> getUsers(List<String> names, Integer size, boolean useOrPredicate) {
CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
CriteriaQuery<User> criteriaQuery = criteriaBuilder.createQuery(User.class);
Root<User> user = criteriaQuery.from(User.class);
Predicate predicate1 = user.get("name").in(names);
Predicate predicate2 = criteriaBuilder.equal(user.get("size"), size);
Predicate predicate;
if(useOrPredicate)
predicate = criteriaBuilder.or(predicate1, predicate2);
else
predicate = criteriaBuilder.and(predicate1, predicate2);
criteriaQuery.where(predicate);
return entityManager.createQuery(criteriaQuery).getResultList();
}
How can I use the "order by" clause with "asc nulls first"?
This is my code:
CriteriaBuilder builder = em.getCriteriaBuilder();
CriteriaQuery<MyClassModel> query = builder.createQuery(MyClassModel.class);
//esprToOrder is a Expression<?> istance that containing the sort field...
query.orderBy(builder.asc(esprToOrder));
TypedQuery<MyClassModel> myQ = em.createQuery(query);
List<MyClassModel> myList = myQ.getResultList();
myList doesn't contain any records with null field sort...
JPA doesn't support specification of "NULLS FIRST|LAST". Some implementations may allow it (by casting to allow extra methods), but it's not part of the JPA spec.
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.
I'm learning jpa-hibernate basics.
I have this query for getting all users:
CriteriaBuilder cb = getEntityManager().getCriteriaBuilder();
CriteriaQuery cq = cb.createQuery();
cq.select(cq.from(Utente.class));
return getEntityManager().createQuery(cq).getResultList();
Now I want to filter by a boolean field named 'ghost' where it equals true (or false, it depends).
Translated:
SELECT * FROM users WHERE ghost = 0;
Do I have to use cq.where() ? How?
Yes, you have to use cq.where().
Try something like this:
Root<Utente> utente = cq.from(Utente.class);
boolean myCondition = true; // or false
Predicate predicate = cb.equal(utente.get(Utente_.ghost), myCondition);
cq.where(predicate);
Where I have used the canonical metamodel class Utente_ that should be generated automatically. This avoids the risk of making errors in typing field names, and enhances type safety. Otherwise you can use
Predicate predicate = cb.equal(utente.get("ghost"), myCondition);
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.