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();
}
Related
I have a query using a JOIN and ORDER BY and want to use it within my repository using the Criteria Api.
Here I found, how to wrap such a query into a CriteriaQuery (Link).
CriteriaQuery<Pet> cq = cb.createQuery(Pet.class);
Root<Pet> pet = cq.from(Pet.class);
Join<Pet, Owner> owner = cq.join(Pet_.owners);
cq.select(pet);
cq.orderBy(cb.asc(owner.get(Owner_.lastName),owner.get(Owner_.firstName)));
On the other side, I found some examples to use the Criteria Api in Combination with a JpaRepository (example).
The Problem is that all methods in the repository expect a Specification:
T findOne(Specification<T> spec);
which is always build like this:
public static Specification<PerfTest> statusSetEqual(final Status... statuses) {
return new Specification<PerfTest>() {
#Override
public Predicate toPredicate(Root<PerfTest> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
return cb.not(root.get("status").in((Object[]) statuses));
}
};
}
So at one side I know how to create a CriteriaQuery, and on the other side I need a Specification which is build from a Predicate, and I can not figure out how to parse the CriteriaQuery into a Specification/Predicate.
Try something like this (I assumed pet has many owners):
public static Specification<Pet> ownerNameEqual(String ownerName) {
return new Specification<Pet>() {
#Override
public Predicate toPredicate(Root<Pet> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) {
Join<Pet, Owner> owners = root.join("owners");
criteriaQuery.orderBy(criteriaBuilder.desc(root.get("id")));
return criteriaBuilder.equal(owners.get("name"), ownerName);
}
};
}
This is just an example to search all pets whose at least one owner has name equal to ownerName
But you could add a method List<Pet> findByOwnersNameOrderByIdDesc(String ownerName); in your repository instead (as an equivalent to Specification).
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
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
I'm new in JPA and want to implement a generic JPA DAO and need to find the number of rows of a query result set to implement pagination. After searching the web, I can't find a practical way to do that. Here is the code suggested in many articles:
public <T> Long findCountByCriteria(CriteriaQuery<?> criteria) {
CriteriaBuilder builder = em.getCriteriaBuilder();
CriteriaQuery<Long> countCriteria = builder.createQuery(Long.class);
Root<?> entityRoot = countCriteria.from(criteria.getResultType());
countCriteria.select(builder.count(entityRoot));
countCriteria.where(criteria.getRestriction());
return em.createQuery(countCriteria).getSingleResult();
}
However, that code doesn't work when using join. Is there any way to count the rows of a query result set using the JPA Criteria API?
UPDATE :
here is the code that create CriteriaQuery :
CriteriaQuery<T> queryDefinition = criteriaBuilder.createQuery(this.entityClass);
Root<T> root = queryDefinition.from(this.entityClass);
and some joins may be added to the root until the query have been executed:
public Predicate addPredicate(Root<T> root) {
Predicate predicate = getEntityManager().getCriteriaBuilder().ge(root.join(Entity_.someList).get("id"), 13);
return predicate;
}
and the generated exception is like :
org.hibernate.hql.ast.QuerySyntaxException: Invalid path:
'generatedAlias1.id' [select count(generatedAlias0) from entity.Entity
as generatedAlias0 where ( generatedAlias0.id>=13L ) and (
(generatedAlias1.id<=34L ) )]
which generatedAlias1 should be on Entity and generatedAlias0 should be on the association that I joined on that.
Note that I implement Join properly because when I execute query without count query it executes without error and the Join works properly but when I try to execute count query it throws exception.
I've done this:
public Long getRowCount(CriteriaQuery criteriaQuery,CriteriaBuilder criteriaBuilder,Root<?> root){
CriteriaQuery<Long> countCriteria = criteriaBuilder.createQuery(Long.class);
Root<?> entityRoot = countCriteria.from(root.getJavaType());
entityRoot.alias(root.getAlias());
doJoins(root.getJoins(),entityRoot);
countCriteria.select(criteriaBuilder.count(entityRoot));
countCriteria.where(criteriaQuery.getRestriction());
return this.entityManager.createQuery(countCriteria).getSingleResult();
}
private void doJoins(Set<? extends Join<?, ?>> joins,Root<?> root_){
for(Join<?,?> join: joins){
Join<?,?> joined = root_.join(join.getAttribute().getName(),join.getJoinType());
doJoins(join.getJoins(), joined);
}
}
private void doJoins(Set<? extends Join<?, ?>> joins,Join<?,?> root_){
for(Join<?,?> join: joins){
Join<?,?> joined = root_.join(join.getAttribute().getName(),join.getJoinType());
doJoins(join.getJoins(),joined);
}
}
of course you do not need Root as input parameter you could get it from criteria query,
#lubo08 gave correct answer - kudos for him.
But for two corner cases his/her code won't work:
When criteria query's restrictions use aliases for joins - then COUNT also require these aliases to be set.
When criteria query use fetch join [root.fetch(..) instead of root.join(..)]
So for completeness I dared to improve his/her solution and present below:
public <T> long count(final CriteriaBuilder cb, final CriteriaQuery<T> criteria,
Root<T> root) {
CriteriaQuery<Long> query = createCountQuery(cb, criteria, root);
return this.entityManager.createQuery(query).getSingleResult();
}
private <T> CriteriaQuery<Long> createCountQuery(final CriteriaBuilder cb,
final CriteriaQuery<T> criteria, final Root<T> root) {
final CriteriaQuery<Long> countQuery = cb.createQuery(Long.class);
final Root<T> countRoot = countQuery.from(criteria.getResultType());
doJoins(root.getJoins(), countRoot);
doJoinsOnFetches(root.getFetches(), countRoot);
countQuery.select(cb.count(countRoot));
countQuery.where(criteria.getRestriction());
countRoot.alias(root.getAlias());
return countQuery.distinct(criteria.isDistinct());
}
#SuppressWarnings("unchecked")
private void doJoinsOnFetches(Set<? extends Fetch<?, ?>> joins, Root<?> root) {
doJoins((Set<? extends Join<?, ?>>) joins, root);
}
private void doJoins(Set<? extends Join<?, ?>> joins, Root<?> root) {
for (Join<?, ?> join : joins) {
Join<?, ?> joined = root.join(join.getAttribute().getName(), join.getJoinType());
joined.alias(join.getAlias());
doJoins(join.getJoins(), joined);
}
}
private void doJoins(Set<? extends Join<?, ?>> joins, Join<?, ?> root) {
for (Join<?, ?> join : joins) {
Join<?, ?> joined = root.join(join.getAttribute().getName(), join.getJoinType());
joined.alias(join.getAlias());
doJoins(join.getJoins(), joined);
}
}
Although it is still not perfect, because only one root is honored.
But I hope it helps somebody.
I can't tell you where your problem is, but I can tell you that count queries with joins work well, at least in eclipselink jpa. My guess is that this is standard stuff, so it should work also in hibernate. I would start by simplifying your code in order to catch where the problem is.
I see that you have copied some pieces of your cont query from your main query. Maybe you can try to change a little bit this approach, just for debugging purpose.
What I do usually is:
CriteriaQuery cqCount = builder.createQuery();
Root<T> root = cq.from(T.class);
cqCount.select(builder.count(root));
ListJoin<T, Entity> join = root.join(T_.someList);
Predicate predicate = builder.ge(join.get(Entity_.id), "myId");
cqCount.where(predicate);
TypedQuery<Long> q = em.createQuery(cqCount);
Looking at your pseudo-code, it seems that you are using the wrong class in the join method: it must be the starting class, not the target class.
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.