How to join on a list using JPA - java

Lets say you have two Entities Cats and Licks. Each has a list of the other. How do you write JPA to find one, given the id of the other.
My question is this: why not make this more simple? This interface is confusing... but here is how you do it anyway.
#PersistenceContext
protected EntityManager em;
#Test
public void testFindCatsByLickId() throws Exception
{
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery cq = cb.createQuery();
Root<Cat> cats = cq.from( Cat.class );
Root<Lick> licks = cq.from( Lick.class );
ListJoin<Cat, Lick> joinCatL = cats.join(Cat_.lickList);
// just creating the following ListJoin object will cause this query to fail!
// ListJoin<Lick, Cat> joinLCat = lick.join(Lick_.catList);
Predicate p = cb.and(
cb.equal(licks.get(Lick_.lickId), new Integer(2))
, cb.equal(licks, joinCatL)
);
cq.select(cats).where(p);
TypedQuery query = em.createQuery(cq);
List<Cat> list = query.getResultList();
assertList( list );
assertTrue(null != list && ! list.isEmpty() );
}

The criteria API is useful when you need to dynamically compose a query from a set of optional search criteria. In this case, using JPQL leads to much simpler and more readable code:
String jpql = "select cat from Lick lick inner joinlick.cats cat where lick.id = :lickId";
List<Cat> cats = em.createQuery(jpql, Cat.class).setParameter("lickId", 2)
.getResultList();
Or even simpler, without any query at all:
Lick lick = em.get(Lick.class, 2);
List<Cat> cats = lick.getCats();

Related

How to use Criteria And pagination

Hi guys I'm trying to use criteria along with pagination but my code isn't working, here it is :
public Page<Person> getAuthorizationsTest() {
PageRequest pageRequest = new PageRequest(1, 5);
CriteriaBuilder criteriaBuilder = em.getCriteriaBuilder();
CriteriaQuery<Person> queryBase = criteriaBuilder.createQuery(Person.class);
Root<Person> root = queryBase.from(Person.class);
List<Predicate> queryConditions = new ArrayList<>();
Predicate predicate = criteriaBuilder.like(root.get("name"), "%[myValue]%");
queryConditions.add(predicate);
queryBase.where(queryConditions.toArray(new Predicate[]{}));
TypedQuery<Person> query = em.createQuery(queryBase);
List<Person> list = query.getResultList();
Page<Person> authorizations = new PageImpl<Person>(list, pageRequest, list.size());
return authorizations;
}
All seems fine, but when I execute it, I receive a list Page with all the results and not only the ones specified in my pageRequest
What am I doing wrong ?
Predicate array and where clause looking properly defined. In these lines:
Predicate predicate = criteriaBuilder.like(root.get("name"),
"%[myValue]%");
queryConditions.add(predicate);
queryBase.where(queryConditions.toArray(new Predicate[]{}));
TypedQuery<Person> query = em.createQuery(queryBase);
Just a reminder. Maybe you need criteriaBuilder.equal instead of criteriaBuilder.like. If you sure what you did you can omitted this reminder.
You need to change following lines like:
query.setFirstResult(Math.toIntExact(pageRequest.getOffset()));
query.setMaxResults(pageRequest.getPageSize());
return new PageImpl<Person>(query.getResultList(), pageRequest, getTotalCount(criteriaBuilder, queryConditions));
Then add getTotalCount method for the sake of consistency.
private Long getTotalCount(CriteriaBuilder criteriaBuilder, Predicate[] predicateArray) {
CriteriaQuery<Long> criteriaQuery = criteriaBuilder.createQuery(Long.class);
Root<T> root = criteriaQuery.from(entityType);
criteriaQuery.select(criteriaBuilder.count(root));
criteriaQuery.where(predicateArray);
return entityManager.createQuery(criteriaQuery).getSingleResult();
}
You are not using a paging request :
List list = query.getResultList();
This query will always query for all the results which is very inefficient.
Instead, you should do this the Spring way ie you should use a repository interface that extends a PagingAndSortingRepository (such as the JpaRepository interface).
Then simply use a method to paginate your results such as findAll as stated in the documentation :
PagingAndSortingRepository<User, Long> repository = // … get access to a bean
Page<User> users = repository.findAll(new PageRequest(1, 20));

JPA CriteriaQuery - How to use IN comparison operator

I have two java objects.
User(every user has an index column)
Address( every address has an user_index column too)
I have a List of all the users index list, usersIndexList as given input and I want to fetch all of the address objects based on this usersIndexList. I found an example on another thread. And tried to follow it but it does not work.
JPA CriteriaBuilder - How to use "IN" comparison operator
My code:
List<String> usersIndexList= new ArrayList<String> ();
for (User u : usersList) {
usersIndexList.add(u.getIndex());
}
CriteriaBuilder criteriaBuilder = getEntityManager().getCriteriaBuilder();
CriteriaQuery<User> subQuery = criteriaBuilder.createQuery(User.class);
Root<User> fromUser= subQuery.from(User.class);
Expression<String> exp = fromUser.get("user_index");
Predicate predicate = exp.in(usersIndexList);
subQuery.where(predicate);
TypedQuery<User> query = getEntityManager().createQuery(subQuery);
return query.getResultList();
But this query is not returning the desired result :(
Can someone please tell me, where I am doing wrong or give me an alternate solutions if its possible via nativequery or namedquery or any other way
As per your question, you want to fetch all of the address objects based on this usersIndexList. But in your code you are selecting User objects, not the Address. Is my understanding Correct? If yes, then please change your root to Address as below -
List<String> usersIndexList= new ArrayList<String> ();
for (User u : usersList) {
usersIndexList.add(u.getIndex());
}
CriteriaBuilder criteriaBuilder = getEntityManager().getCriteriaBuilder();
CriteriaQuery<Address> subQuery = criteriaBuilder.createQuery(Address.class);
Root<Address> fromAddress= subQuery.from(Address.class);
Expression<String> exp = fromAddress.get("user_index");
Predicate predicate = exp.in(usersIndexList);
subQuery.where(predicate);
TypedQuery<Address> query = getEntityManager().createQuery(subQuery);
return query.getResultList();

Conversion of SQL Left Outer Join Query to JPA Criteria

I have the following native SQL query that I am trying to convert to JPA criteria:
select et.* from t_empl_tx et, t_dept d
where et.assigned_dept = d.dept (+)
and et.employee_id = :employee_id
and (et.start_date >= d.dept_est_date and
et.start_date <= d.dept_close_date or
et.start_date is null or
d.dept is null)
(Note that (+) is roughly equivalent to a left outer join in this case. Yes, I know it denotes the OPTIONAL table, etc, etc).
Here is my attempt at the code:
EntityManager entityManager = getEntityManager();
CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
CriteriaQuery<EmployeeTransaction> criteriaQuery = criteriaBuilder.createQuery(EmployeeTransaction.class);
Root<EmployeeTransaction> root = criteriaQuery.from(EmployeeTransaction.class);
// this line bombs!
Join<EmployeeTransaction, Department> join =
root.join(EmployeeTransaction_.assignedDepartment).join(Department_.id).join(DepartmentCompositeId_.department, JoinType.LEFT);
List<Predicate> predicates = new ArrayList<>();
predicates.add(criteriaBuilder.equal(root.get(EmployeeTransaction_.id).get(EmployeeTransactionCompositeId_.employeeId), employeeId));
predicates.add(criteriaBuilder.or(
criteriaBuilder.and(
criteriaBuilder.greaterThanOrEqualTo(root.<Date>get(EmployeeTransaction_.requestedStartDate), join.get(Department_.id).<Date>get(DepartmentCompositeId_.departmentCreationDate)),
criteriaBuilder.lessThanOrEqualTo(root.<Date>get(EmployeeTransaction_.requestedStartDate), join.<Date>get(Department_.departmentCloseDate))
),
criteriaBuilder.isNull(root.get(EmployeeTransaction_.requestedStartDate)),
criteriaBuilder.isNull(join.get(Department_.id).get(DepartmentCompositeId_.departmentCreationDate))
));
criteriaQuery.select(root).where(predicates.toArray(new Predicate[]{}));
TypedQuery<EmployeeTransaction> query = entityManager.createQuery(criteriaQuery);
List<EmployeeTransaction> result = query.getResultList();
This issue seems to be that I'm trying to join a string column, assigedDepartment, to a single field of a composite ID. This is perfectly legal in SQL, but not so easy in the code.
One option is to convert to a number of subqueries, which seems to kill the point of the left outer join entirely.
Can anyone point out what I'm doing wrong?
Jason
You should post your entities so that the answers can be more specific.
However, I'll give a try.
If I am right, you can rewrite the query:
select et.*
from t_empl_tx et
left join t_dept d on et.assigned_dept = d.dept
where
et.employee_id = :employee_id
and (
et.start_date >= d.dept_est_date
and et.start_date <= d.dept_close_date
or et.start_date is null
or d.dept is null)
So, shortly, you have to move the JoinType.LEFT to assignedDepartment join:
EntityManager entityManager = getEntityManager();
CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
CriteriaQuery<EmployeeTransaction> criteriaQuery = criteriaBuilder.createQuery(EmployeeTransaction.class);
Root<EmployeeTransaction> root = criteriaQuery.from(EmployeeTransaction.class);
Join<EmployeeTransaction, Department> department = root.join(EmployeeTransaction_.assignedDepartment, JoinType.LEFT);
Path<Date> employeeTransactionRequestedStartDate = root.get(EmployeeTransaction_.requestedStartDate);
Path<DepartmentCompositeId> departmentId = department.get(Department_.id);
Path<Date> departmentCreationDate = departmentId.get(DepartmentCompositeId_.departmentCreationDate)
Path<Date> departmentCloseDate = departmentId.get(DepartmentCompositeId_.departmentCloseDate)
criteriaQuery.select(root).where(
criteriaBuilder.equal(root.get(EmployeeTransaction_.id).get(EmployeeTransactionCompositeId_.employeeId), employeeId),
criteriaBuilder.or(
criteriaBuilder.and(
criteriaBuilder.greaterThanOrEqualTo(employeeTransactionRequestedStartDate, departmentCreationDate)),
criteriaBuilder.lessThanOrEqualTo(employeeTransactionRequestedStartDate, departmentCloseDate)
),
criteriaBuilder.isNull(employeeTransactionRequestedStartDate),
criteriaBuilder.isNull(departmentCreationDate)
)
);
TypedQuery<EmployeeTransaction> query = entityManager.createQuery(criteriaQuery);
List<EmployeeTransaction> result = query.getResultList();

JPA-2.0 Simple Select-Where question

I am stuck with a problem concerning JPA-2.0 queries with relationships. How would it be possible to select any Dataset with at least one Event with type = B?
#Entity
class Dataset {
#OneToMany(fetch = FetchType.LAZY, mappedBy = "dataset")
public List<Event> events;
}
#Entity
class Event {
#ManyToOne
#JoinColumn
public Dataset dataset;
public Type type;
}
enum Type {
A, B, C
}
My starting point is
CriteriaBuilder _builder = em.getCriteriaBuilder();
CriteriaQuery<Dataset> _criteria = _builder.createQuery(Dataset.class);
// select from
Root<Dataset> _root = _criteria.from(Dataset.class);
_criteria.select(_root);
// apply some filter as where-clause (visitor)
getFilter().apply(
_root, _criteria, _builder, em.getMetamodel()
);
// how to add a clause as defined before?
...
Any ideas on this. I tried to create a subqueries as well as a join, but I somehow did it wrong and always got all datasets as result.
Try
SELECT d FROM DataSet d WHERE EXISTS
(SELECT e FROM Event e WHERE e.dataSet = d and e.type = :type)
EDIT: As Pascal pointed out it looks like you are using the Criteria API. Not as familiar with this, but I'll have a stab.
CriteriaBuilder builder = em.getCriteriaBuilder();
CriteriaQuery<Dataset> criteria = builder.createQuery(Dataset.class);
Root<Dataset> root = criteria.from(Dataset.class);
criteria.select(root);
// build the subquery
SubQuery<Event> subQuery = criteria.subQuery(Event.class);
Root<Event> eventRoot = subQuery.from(Event.class);
subQuery.select(eventRoot);
ParameterExpression<String> typeParameter = builder.parameter(String.class);
Predicate typePredicate = builder.equal(eventRoot.get(Event_.type), typeParameter));
// i have not tried this before but I assume this will correlate the subquery with the parent root entity
Predicate correlatePredicate = builder.equal(eventRoot.get(Event_.dataSet), root);
subQuery.where(builder.and(typePredicate, correlatePredicate);
criteria.where(builder.exists(subQuery)));
List<DataSet> dataSets = em.createQuery(criteria).getResultList();
Phew that was hard work. I'm going back to Linq now.

ORDER BY using Criteria API

When I write a HQL query
Query q = session.createQuery("SELECT cat from Cat as cat ORDER BY cat.mother.kind.value");
return q.list();
Everything is fine. However, when I write a Criteria
Criteria c = session.createCriteria(Cat.class);
c.addOrder(Order.asc("mother.kind.value"));
return c.list();
I get an exception org.hibernate.QueryException: could not resolve property: kind.value of: my.sample.data.entities.Cat
If I want to use Criteria and Order, how should I express my "order by"?
You need to create an alias for the mother.kind. You do this like so.
Criteria c = session.createCriteria(Cat.class);
c.createAlias("mother.kind", "motherKind");
c.addOrder(Order.asc("motherKind.value"));
return c.list();
This is what you have to do since sess.createCriteria is deprecated:
CriteriaBuilder builder = getSession().getCriteriaBuilder();
CriteriaQuery<User> q = builder.createQuery(User.class);
Root<User> usr = q.from(User.class);
ParameterExpression<String> p = builder.parameter(String.class);
q.select(usr).where(builder.like(usr.get("name"),p))
.orderBy(builder.asc(usr.get("name")));
TypedQuery<User> query = getSession().createQuery(q);
query.setParameter(p, "%" + Main.filterName + "%");
List<User> list = query.getResultList();
It's hard to know for sure without seeing the mappings (see #Juha's comment), but I think you want something like the following:
Criteria c = session.createCriteria(Cat.class);
Criteria c2 = c.createCriteria("mother");
Criteria c3 = c2.createCriteria("kind");
c3.addOrder(Order.asc("value"));
return c.list();
You can add join type as well:
Criteria c2 = c.createCriteria("mother", "mother", CriteriaSpecification.LEFT_JOIN);
Criteria c3 = c2.createCriteria("kind", "kind", CriteriaSpecification.LEFT_JOIN);
For Hibernate 5.2 and above, use CriteriaBuilder as follows
CriteriaBuilder builder = sessionFactory.getCriteriaBuilder();
CriteriaQuery<Cat> query = builder.createQuery(Cat.class);
Root<Cat> rootCat = query.from(Cat.class);
Join<Cat,Mother> joinMother = rootCat.join("mother"); // <-attribute name
Join<Mother,Kind> joinMotherKind = joinMother.join("kind");
query.select(rootCat).orderBy(builder.asc(joinMotherKind.get("value")));
Query<Cat> q = sessionFactory.getCurrentSession().createQuery(query);
List<Cat> cats = q.getResultList();

Categories

Resources