I am using JPAs criteria api to request some objects form my database and apply filter to the request.
the following code shows the method that assambles and executes the query:
#Override
public List<MyClass> findAll(MyClassFilterObject filter) {
final CriteriaBuilder builder = this.getEntityManager().getCriteriaBuilder();
final CriteriaQuery<MyClass> criteriaQuery = builder.createQuery(MyClass);
Root<MyClass> root = criteriaQuery.from(MyClass);
List<Predicate> criteriaList = new LinkedList<Predicate>();
if (!filter.getMyClassStatus().equals("")) {
Path<String> statusPath = root.get("MyClassStatus");
criteriaList.add(builder.equal(statusPath, MyClassStatus.valueOf(filter.getMyClassStatus())));
}
if (!filter.getMyClassType().equals("")) {
Expression<Set<MyClassType>> MyClassTypesQuery = root.get("MyClassType");
criteriaList.add(builder.isMember(MyClassType.valueOf(filter.getMyClassType()), MyClassTypesQuery));
}
if (criteriaList.size()>0){
criteriaQuery.where(builder.and(criteriaList.toArray(new Predicate[criteriaList.size()])));
}
TypedQuery<MyClass> typedQuery = this.getEntityManager().createQuery(criteriaQuery);
typedQuery.setFirstResult(filter.getPagenumber() * 10);
typedQuery.setMaxResults(10);
List<MyClass> MyClasss = typedQuery.getResultList();
if (MyClasss.isEmpty()) {
return new LinkedList<MyClass>();
}
return MyClasss;
}
this code always returns the same two objects, no matter what i choose as "MyClassType".
MyClass has a set of MyClassType (an Enum value) objects attached and i want to know if the MyClassType object from my filter is a member of this collection
I am not sure if i am using the isMember method right
Related
I have a entity Order with a list of tasks (another entity) and I need to use specification to filter the Order entity and the nested list of tasks.
Example:
Select * Order where taks.done = "S"
I need to return a list of order and tasks that was done.
My problem is the nested list, the specification filters only entity Order and not the list of tasks
public static Specification<Order> isDone(Indicator done) {
return new JoinableSpecification<>() {
#Override
public Predicate toPredicate(Root<Order> root, CriteriaQuery<?> cq, CriteriaBuilder cb) {
Path<Indicator> path = this.joinList(root, UsuarioProcesso_.tarefas, JoinType.INNER).get(Task.done);
return cb.equal(path, done);
}
};
}
You can use specification join. Example
(root, query, cb) ->
{
Join<Order, Task> taks = root.join(Task);
Expression<String> expression = taks.get(Task_.done);
Predicate predicate = expression.equals("s");
query.where(predicate);
return query.getRestriction();
};
I would like to refactor these two methods which are practically the same except for a "maxResult ()", these 2 methods refer to two different gets, one that returns me the single user and one that returns the list instead. How could I simplify these two methods (always if it makes sense)
these are the 2 methods:
First Method:
public List findFirstByTransactionId(String transactionId) {
CriteriaBuilder builder = em.getCriteriaBuilder();
CriteriaQuery<User> criteria = builder.createQuery(User.class);
Root<User> root = criteria.from(User.class);
criteria.select(root).where(builder.equal(root.get(User_.transactionId), transactionId));
criteria.orderBy(builder.asc(root.get(User_.date)));
TypedQuery<User> query = em.createQuery(criteria).setMaxResults(1);
return query.getSingleResult();
Second Method:
public List<User> findAllByTransactionId(String transactionId) {
CriteriaBuilder builder = em.getCriteriaBuilder();
CriteriaQuery<User> criteria = builder.createQuery(User.class);
Root<User> root = criteria.from(User.class);
criteria.select(root).where(builder.equal(root.get(User_.transactionId), transactionId));
criteria.orderBy(builder.asc(root.get(User_.date)));
TypedQuery<User> query = em.createQuery(criteria);
return query.getResultList();
More of an addendum.
The other answers suggesting to add a boolean parameter are valid, but: clean coding advises to always strive for minimal count of parameters. And especially such boolean parameters are discouraged. Of course, it still makes sense here to do it that way, to avoid code duplication.
But what I would do:
yes, internally have a private List<User> findFirstByTransactionId() taking a boolean parameter
but on your public interface, simply offer TWO different methods, like public List<User> findFirstUserByTransactionId() and `public List findUsersByTransactionId()``
Those two public methods can then call the internal method and pass true/false. Using a boolean to make that decision is an implementation detail, and you should avoid making that visible on the public side of things.
Well the easiest option usually is to try to move the code that is the same to its own method. Here, you have multiple options, one of them could be something like this:
Write a new (private) method:
private List<User> findFirstByTransactionId(String transactionId, boolean onlyOneResult) {
CriteriaBuilder builder = em.getCriteriaBuilder();
CriteriaQuery<User> criteria = builder.createQuery(User.class);
Root<User> root = criteria.from(User.class);
criteria.select(root).where(builder.equal(root.get(User_.transactionId), transactionId));
criteria.orderBy(builder.asc(root.get(User_.date)));
TypedQuery<User> query = em.createQuery(criteria);
if (onlyOneResult) {
query = query.setMaxResults(1);
}
return query.getResultList();
}
And then refactor your existing methods like this:
public List<User> findFirstByTransactionId(String transactionId) {
return findFirstByTransactionId(transactionId, false);
}
public List<User> findFirstByTransactionId(String transactionId) {
return findFirstByTransactionId(transactionId, true);
}
Now you eliminated 8 lines of duplicate code :)
The methods you call on query work just like any other methods, so you can just put some of the calls inside an if block like this:
public List<User> findByTransactionId(String transactionId, boolean onlyFirst) {
CriteriaBuilder builder = em.getCriteriaBuilder();
CriteriaQuery<User> criteria = builder.createQuery(User.class);
Root<User> root = criteria.from(User.class);
criteria.select(root).where(builder.equal(root.get(User_.transactionId), transactionId));
criteria.orderBy(builder.asc(root.get(User_.date)));
TypedQuery<User> query = em.createQuery(criteria);
if (onlyFirst) {
query=query.setMaxResults(1);
}
return query.getResultList();
}
I need to make a search method that uses the JPA Criteria API with multiple parameters.
Now the problem is that not every parameter is required. So some could be null, and they shouldn't be included in the query. I've tried this with the CriteriaBuilder but I couldn't see how to make it work.
With the Hibernate Criteria API this is fairly easy. Just create the criteria and then add Restrictions.
Criteria criteria = session.createCriteria(someClass.class);
if(someClass.getName() != null) {
criteria.add(Restrictions.like("name", someClass.getName());
}
How could I achieve the same with JPA's Criteria API?
Concept is to construct array of javax.persistence.Predicate which contains only predicates we want to use:
Example entity to be queried:
#Entity
public class A {
#Id private Long id;
String someAttribute;
String someOtherAttribute;
...
}
Query itself:
//some parameters to your method
String param1 = "1";
String paramNull = null;
CriteriaBuilder qb = em.getCriteriaBuilder();
CriteriaQuery cq = qb.createQuery();
Root<A> customer = cq.from(A.class);
//Constructing list of parameters
List<Predicate> predicates = new ArrayList<Predicate>();
//Adding predicates in case of parameter not being null
if (param1 != null) {
predicates.add(
qb.equal(customer.get("someAttribute"), param1));
}
if (paramNull != null) {
predicates.add(
qb.equal(customer.get("someOtherAttribute"), paramNull));
}
//query itself
cq.select(customer)
.where(predicates.toArray(new Predicate[]{}));
//execute query and do something with result
em.createQuery(cq).getResultList();
A simple solution for Spring, using lambda expressions:
Specification<User> specification = (root, query, builder) -> {
List<Predicate> predicates = new ArrayList<>();
// like
predicates.add(builder.like(root.get("name"), "%test%"));
// equal
predicates.add(builder.equal(root.get("parent_id"), 99L);
// AND all predicates
return builder.and(predicates.toArray(new Predicate[0]));
};
repository.findAll(specification);
Take a look at this site JPA Criteria API. There are plenty of examples.
Update: Providing a concrete example
Let's search for Accounts with a balance lower than a specific value:
SELECT a FROM Account a WHERE a.balance < :value
First create a Criteria Builder
CriteriaBuilder builder = entityManager.getCriteriaBuilder();
CriteriaQuery<Account> accountQuery = builder.createQuery(Account.class);
Root<Account> accountRoot = accountQuery.from(Account.class);
ParameterExpression<Double> value = builder.parameter(Double.class);
accountQuery.select(accountRoot).where(builder.lt(accountRoot.get("balance"), value));
To get the result set the parameter(s) and run the query:
TypedQuery<Account> query = entityManager.createQuery(accountQuery);
query.setParameter(value, 1234.5);
List<Account> results = query.getResultList();
BTW: The entityManager is injected somewhere in an EJB/Service/DAO.
Mikko's answer worked beautifully. Only change I needed to do, was to replace:
cq.select(customer).where(predicates.toArray(new Predicate[]{}));
with:
Predicate [] predicatesarr = predicates.toArray(new Predicate[predicates.size()]);
cq.select(customer).where(predicatesarr);
Somewhere the conversion from list to array in the original did not work.
First, Mikko's answer got me to my answer. Upvote for that.
My scenario was I wanted to parent/child relationship and I wanted to find a match on ~any~ child.
Employee has multiple JobTitle(s).
I wanted to find an employee (where the has many job titles), but find it on ~any of the jobtitles I send in.
SQL would look like:
Select * from dbo.Employee e join dbo.JobTitle jt on e.EmployeeKey = jt.EmployeeKey
WHERE ( jt.JobTitleName = 'programmer' OR jt.JobTitleName = 'badcop' )
I threw in gender and date-of-birth to complete the example (and give more "optional") criteria)
My JPA code
import org.apache.commons.lang3.StringUtils;
import org.springframework.data.jpa.domain.Specification;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Join;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import java.util.ArrayList;
import java.util.List;
public class MyEmployeeSpecification implements Specification<MyEmployee> {
private MyEmployee filter;
public MyEmployeeSpecification(MyEmployee filter) {
super();
this.filter = filter;
}
public Predicate toPredicate(Root<MyEmployee> root, CriteriaQuery<?> cq,
CriteriaBuilder cb) {
Predicate returnPred = cb.disjunction();
List<Predicate> patientLevelPredicates = new ArrayList<Predicate>();
if (filter.getBirthDate() != null) {
patientLevelPredicates.add(
cb.equal(root.get("birthDate"), filter.getBirthDate()));
}
if (filter.getBirthDate() != null) {
patientLevelPredicates.add(
cb.equal(root.get("gender"), filter.getGender()));
}
if (null != filter.getJobTitles() && filter.getJobTitles().size() > 0) {
List<Predicate> jobTitleLevelPredicates = new ArrayList<Predicate>();
Join<JobTitle, JobTitle> hnJoin = root.join("jobtitles");
for (JobTitle hnw : filter.getJobTitles()) {
if (null != hnw) {
if (StringUtils.isNotBlank(hnw.getJobTitleName())) {
jobTitleLevelPredicates.add(cb.equal(hnJoin.get("getJobTitleName"), hnw.getFamily()));
}
}
}
patientLevelPredicates.add(cb.or(jobTitleLevelPredicates.toArray(new Predicate[]{})));
}
returnPred = cb.and(patientLevelPredicates.toArray(new Predicate[]{}));
return returnPred;
}
}
But I figured mine out because of predicates.toArray(new Predicate[]{}) , aka, the varargs trick. (Thanks Mikko)
I'm also doing the "implements Specifiction" method.
Other helpful links:
JPA Specifications by Example
JPA CriteriaBuilder conjunction criteria into a disjunction criteria
In Hibernate, is there a way to create an add an Alias to a Criterion object. I have the following to work with:
I have a dynamic search from big Database with many tables. The search has many (25+) optional non-exclusive parameters selected clien-side. This requires the use of the Hibernate Criteria API for managability. In my DAO I have the following method:
Public List<myPojoClass> getDataByCriterion( List<Criterion> restrictionList) {
Session s = HibernateUtil.currentSession();
Criteria c = s.createCriteria(myPojo.class);
for (Criterion crit : restrictionList){
c.add(crit);
}
List<myPojoClass> response = c.list();
return response;
}
I need to do a Join with myOtherPojo.class and would like to know if it is possible to add an alias to the Criteria list above.
Somthing like :
restrictionsList.add(... ...createAlias("myOtherPojo.class" , "mop");
then, I need o add other Logical and to this class as above.
You again! ;)
You could pass a collection of entries (like a HashMap<String, String>) and iterate over them to populate your aliases... like this:
Public List<myPojoClass> getDataByCriterion( List<Criterion> restrictionList, HashMap<String,String> aliases) {
Session s = HibernateUtil.currentSession();
Criteria c = s.createCriteria(myPojo.class);
for (Criterion crit : restrictionList){
c.add(crit);
}
for (Entry<String, String> entry : aliases.entrySet()){
c.createAlias(entry.getKey(), entry.getValue());
}
List<myPojoClass> response = c.list();
return response;
}
You could do something similar if you want to change the fetch modes. Of course the calling method needs to know the data model so it can set up the aliases properly, otherwise you can get errors at runtime.
from what I know there is now way to create joins without instance of Criteria. I suggest you create some wrapper for criteria which would contain criteria and alias definition if necessary and then use Criteria as visitor (like from this Pattern)
interface CriterionWrapper {
void visit(Criteria c);
}
class OnlyCriterionWrapper implements CriterionWrapper {
private Criterion c;
public void visit(Criteria c){c.add(c);}
}
class CriterionWrapper implements CriterionWrapper{
private Criterion c;
private String whateverIsNeededToCreateAlias
public void visit(Criteria c){
c.createAlias(whateverIsNeededToCreateAlias);
c.add(c);
}
}
and then pass List as parameter to your getDataByCriterion() method
I love the type safety CriteriaQuery brings ing JPA 2.0 but it also brings a bit of boiler-plate code. For example, let say I have an entity called NamedEntity, which simply has an id and a String field called "name" (assume it has the unique constraint set to true). Here's what the NamedEntityManager might look like:
public class NamedEntityManager
{
//inject using your framework
EntityManager entityManager;
//retrieve all existing entities of type NamedEntity from DB
public Iterable<NamedEntity> queryAll()
{
CriteriaBuilder builder = entityManager.getCriteriaBuilder();
CriteriaQuery<NamedEntity> query = builder.createQuery(NamedEntity.class);
return entityManager.createQuery(query).getResultList();
}
//retrieve a single entity of type NamedEntity from DB using specified name
public NamedEntity queryByName(String name)
{
CriteriaBuilder builder = entityManager.getCriteriaBuilder();
CriteriaQuery<NamedEntity> query = builder.createQuery(NamedEntity.class);
Root<NamedEntity> root = query.from(NamedEntity.class);
query = query.where(root.<NamedEntity>get("name").in(name));
//skipped the try/catch block for the sake of brevity
return entityManager.createQuery(query).getSingleResult();
}
}
Is there a way to condense the code in order to avoid copying/pasting the same lines of code into each query method? Perhaps somehow reuse the CriteriaQuery object?
I was looking for something like that, you could take a look at Querydsl (LGPL licensed) which can have JPA as backend.
Im still reading into it, but from their examples, it looks pretty clean.
HQLQuery q = new HibernateQuery(session);
QCat cat = new QCat("cat"); // query type
List<Cat> cats = q.from(cat).where(cat.name.between("A", "B")).list(cat);
In JPA 2.1, it will most probably be possible to mix JPQL and Criterias. With such an approach you could define a base query with JPQL and then use the Criteria API to dynamically add small parts.
I figure the API will be less verbose then, since you only need to use small parts of it.
Then Use JPA-2.0 MetaData model.
http://docs.jboss.org/hibernate/jpamodelgen/1.0/reference/en-US/html_single/
It seems there's no way to reduce the amount of code. I guess something had to be sacrificed to gain type safety.
Way outdated, this post, but I want to add what I recently built for simple queries
public static class Jpa2Whatsoever {
private final EntityManager em;
public class Jpa2WhatsoeverProgress<T> {
private CriteriaQuery<T> cq;
private List<Predicate> predicates = new ArrayList<>();
private Root<T> root;
public Jpa2WhatsoeverProgress(Class<T> type) {
this.cq = em.getCriteriaBuilder().createQuery(type);
this.root = cq.from(type);
}
public Jpa2WhatsoeverProgress<T> where(String attributeName, Object value) {
Predicate equal = em.getCriteriaBuilder().equal(root.get(attributeName), value);
predicates.add(equal);
return this;
}
public List<T> getResultList() {
Predicate[] predicatesArray = new Predicate[predicates.size()];
TypedQuery<T> typedQuery = em.createQuery(cq.select(root).where(predicates.toArray(predicatesArray)));
List<T> resultList = typedQuery.getResultList();
return Collections.unmodifiableList(resultList);
}
}
public Jpa2Whatsoever(EntityManager entityManager) {
this.em = entityManager;
}
public <T> Jpa2WhatsoeverProgress<T> select(Class<T> type) {
return new Jpa2WhatsoeverProgress<T>(type);
}
}
You can use it like this
List<MyEntity> matchingEntities = new Jpa2Whatsoever(entityManager).select(MyEntity.class).where("id", id).where("due", new Date()).getResultList();
In the end I stopped this. Mainly because I saw that I had only two queries and I would have to extend the DSL to get the required query characteristics into it, such as
greater than, less than
Metamodel support
QueryBuilder.currentDate() and alike.
Further, I find it ugly to always call where while it actually corresponds to a more SQLly and. Anyway, if someone is interested in a very simple query API, it is still worth a try.
BTW: Forget about the names, this was a prototype, nothing more.