JPA query a collection using between clause - java

My entity has a collection of another entity on which I need to do a BETWEEN criteria.
I do not want to use the native query.
I am trying to achieve this using the criteria API.
Below is a short snippet of my entity.
#Entity
#Table(name = "ref_dates")
public class Dates{
#Id
#Column(name = "ID")
private int id;
#OneToMany(fetch = FetchType.EAGER)
#JoinTable(
name="ref_dates_prg",
joinColumns = #JoinColumn( name="DATE_PRG_ID"),
inverseJoinColumns = #JoinColumn( name="DATE_ID")
)
private Set<DateInfo> dates;
}
It has several other properties, geter/setters, etc which I have not mentioned here.
I need to do a query on this Set for the id's in DateInfo object using between clause.
I tried using Expression<Set<DateInfo>> but haven't reached anywhere.
Thanks for all the help.
Here is my criteria build up.
final CriteriaBuilder criteriaBuilder = em.getCriteriaBuilder();
final CriteriaQuery<NetPrgTimePeriod> criteriaQuery = criteriaBuilder.createQuery(Dates.class);
List<Predicate> criteriaList = new ArrayList<Predicate>();
final Root<Dates> root = criteriaQuery.from(Dates.class);
Join<Dates, DateInfo> dateJoin = root.join("dates", JoinType.LEFT);
Predicate runDatesRange = criteriaBuilder.between(
dateJoin.<Integer> get("id"), startDate.getId(), endDate.getId());
criteriaList.add(runDatesRange);
Join<Dates, TimeInfo> timeJoin = root.join("times", JoinType.LEFT);
Predicate timeBlocksRange = criteriaBuilder.between(
timeJoin.<Integer> get("id"), startTime.getId(), endTime.getId());
criteriaList.add(timeBlocksRange);
criteriaQuery.where(criteriaBuilder.and(criteriaList.toArray(new Predicate[0])));
TypedQuery<NetPrgTimePeriod> query = em.createQuery(criteriaQuery);
List<Dates> results = query.getResultList();

Assuming you actually mapped your collection correctly, the main part you seem to be missing is the Join:
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Dates> query = cb.createQuery(Dates.class);
Root<Dates> root = query.from(Dates.class);
Join<Dates, DateInfo> infos = root.join("dates", JoinType.LEFT);
query.distinct(true);
em.createQuery(query.where(cb.between(infos.<Integer>get("id"), 1, 10))).getResultList();
Of course you can substitute metamodel fields where I used strings (which will also obsolete the need for this ugly <Integer> selector - assuming your id is an integer).

Related

JPA Criteria query to fetch data from master and child table with entitygraph

I am trying to fetch data from parent to child both based on filter criteria using JPA Criteria query so that can avoid multiple queries to DB, but not able to achieve desired result. Following are my sample entities( without getters/setters)
#Entity
public class ParentTable implements Serializable{
private static final long serialVersionUID = 1L;
#Id
#Column(name = "ID")
private String id;
#Column(name = "KEY_COLUMN",length = 30)
private String keyColumn;
#Column(name = "CODE",length = 30)
private String code;
#Column(name = "KEY_DESC",length = 240)
private String desc;
#OneToMany(mappedBy = "parentTable",cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private List<ChildTable> childTableList;
}
#Entity
public class ChildTable implements Serializable{
private static final long serialVersionUID = 1L;
public ChildTable() {
super();
}
#Id
#Column(name = "ID",length = 80)
private String id;
#Column(name = "PARENT_KEY_COLUMN",length = 30,insertable = false,updatable = false)
private String parentKeyColumn;
#Column(name = "CHILD_CODE",length = 30)
private String childCode;
#Column(name = "CHILD_DESC",length = 240)
private String chldDesc;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "PARENT_KEY_COLUMN", referencedColumnName = "KEY_COLUMN")
private ParentTable parentTable;
}
Criteria builder snippet -
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<ParentTable> query = cb.createQuery(ParentTable.class);
Root<ParentTable> fromParent = query.from(ParentTable.class);
Join<ParentTable, ChildTable> details = fromParent.join("childTableList");
List<Predicate> conditions = new ArrayList();
conditions.add(cb.equal(details.get("childCode"), childCode));
conditions.add(cb.equal(details.get("chldDesc"),chldDesc));
TypedQuery<ParentTable> typedQuery = em.createQuery(query.select(fromParent).where(conditions.toArray(new Predicate[] {})));
List<ParentTable> parentTableList = typedQuery.getResultList();
This executes and gives result of parent table only, if i fetch childtable data I can see JPA query getting exceuted again, can this be avoided and fetch list of child entities which matches 3 dynamic params? 1. ParentTable.code, 2. ChildTable.childCode , 3. ChildTable.chldDesc .
Can anyone help me to construct JPA query like below which executes in one DB hit instead of multiple round trip, which is happening in above snippet of code?
select * from ParentTable p,ChildTable c where p.KEY_COLUMN=c.PARENT_KEY_COLUMN and p.CODE=? and c.CHILD_CODE=? and c.CHILD_DESC=?
Update :
CriteriaBuilder builder = entityManager.getCriteriaBuilder();
CriteriaQuery<ParentTable> cq = builder.createQuery(ParentTable.class);
Root<ParentTable> root = cq.from(ParentTable.class);
Join<ParentTable, ChildTable> join = root.join("childTableList");
Predicate p1=builder.equal(root.get("code"), "code");
Predicate p2=builder.like(join.get("chldDesc"), "%chldDesc%");
Predicate p3=builder.equal(join.get("childCode"), "childCode");
Predicate andPredicate = builder.and(p1,p2, p3);
cq.select(root).where(andPredicate);
EntityGraph<ParentTable> fetchGraph = entityManager.createEntityGraph(ParentTable.class);
fetchGraph.addSubgraph("childTableList");
List<ParentTable> parentTableList=entityManager.createQuery(cq).setHint("javax.persistence.loadgraph", fetchGraph).getResultList();
parentTableList.forEach(System.out::println);
This approach forms the expected query like above mentioned but again one more query is formed like below , why second query is triggered even-though its not required?
select * from ParentTable parentTable0_ where parentTable0_.code=?
JPA is required to give you managed entity results that reflect the data in the database. The filter you put on the query does not filter the internal relationships.
Your 'join' clause only affects the filter applied to returning ParentTable entities. Every entity returned will have a complete 'childTableList' collection, so even though you are returning ParentTable instances that have specific codes and descriptions, the childTableList shows all its children. That is JPA for you, and specific providers do have a way to filter these mapped collections (AdditionalCriteria ) IMO they are a bad route with many problems.
If you want to have childTableEntries that match the specific codes and descriptions, your query should be more of the form (using JPQL)
"Select c, p from ChildTable c join c.parentTable p where c.childCode = :code and c.chldDesc = :desc"
This will return you a List result, where the Object array has the child and parent entries for each row that matches. So duplicate parents if one has more than one child that matches.
Otherwise, the extra query is being caused by accessing the childTableList on the parentTable entries, because they are marked lazy. Your criteria query is specifying 'join', as you want to use the childTableList entries in filtering parentTable entities. If you want the childTableList fetched with the parentTables, you need to use fetchJoins. Root implements FetchParent, which would allow you to specify a 'fetch' on the childTableList in addition to the join you've defined. In JPQL, something like:
"select p from ParentTable p fetch join p.childTableList, join p.childTableList c where c.childCode = :code and c.chldDesc = :desc"

JPA Criteria API on ManyToOne optional relationship

I'm trying to do a simple query using JPA Criteria API on following structure
1) Employee
public class Employee {
#Id
#Column(name = "ID", length = 64)
private String id;
#Column(name = "NAME", length = 512)
private String name;
#ManyToOne(optional = true)
#JoinColumn(name = "ORG_ID", nullable = true)
private InternalOrg organization;
}
2) InternalOrg
public class InternalOrg {
#Id
#Column(name = "ID", length = 64)
private String id;
#Column(name = "ORGANIZATION", length = 512)
private String organization;
#Column(name = "CODE", length = 64)
private String code;
}
3) Query
EntityManager em = getEntityManager();
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Employee> cq = cb.createQuery(Employee.class);
Root<Employee> emp = cq.from(Employee.class);
cq.where(cb.or(emp.get(Employee_.organization).isNull(),
cb.equal(emp.get(Employee_.organization).get(InternalOrg_.code), "1")));
return em.createQuery(cq).getResultList();
As you can see "organization" attribute on Employee is optional. What I'm trying to do is a query using criteria API that returns all records where "employee.organization" is NULL or "employee.organization.code" is equal to a parameter. How do I proceed?
I did some tests and realized that if I change from this:
cq.where(cb.or(emp.get(Employee_.organization).isNull(),
cb.equal(emp.get(Employee_.organization).get(InternalOrg_.code), "1")));
To this:
cq.where(cb.or(emp.get(Employee_.organization).isNull()));
It works but only returns records where organization is NULL.
If I change to this:
cq.where(cb.equal(emp.get(Employee_.organization).get(InternalOrg_.code), "1"));
Records where employee.organization is NULL are ignored.
How do I return employees which organization satisfies criteria AND employees where organization IS NULL?
Thanks in advance,
finally found the solution.
The only way to create get desired result is to fetch (JoinType.LEFT) relationship earlier, here is the final criteria query:
EntityManager em = getEntityManager();
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Employee> cq = cb.createQuery(Employee.class);
Root<Employee> emp = cq.from(Employee.class);
emp.fetch(Employee_.domain, JoinType.LEFT);
cq.where(cb.or(emp.get(Employee_.organization).isNull(),
cb.equal(emp.get(Employee_.organization).get(InternalOrg_.code), "1")));
return em.createQuery(cq).getResultList();
Thank you for support!
Conditions that are set by calling the CriteriaQuery.where method can restrict the results of a query on the CriteriaQuery object. Calling the where method is analogous to setting the WHERE clause in a JPQL query.
Example:
EntityManager em = ...;
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Employee> cq = cb.createQuery(Employee.class);
Root<Employee> emp = cq.from(Employee.class);
cq.where(emp.get(Employee_.organization).isNull());
To specify multiple conditional predicates, use the compound predicate methods (and/or/not) of the CriteriaBuilder interface.
cq.where(emp.get(Employee_.organization).isNull())
.or(cb.eq(emp.get(Employee_.organization.code), "ABC"));
Update:
Try this:
cq.where(
cb.or(
cb.isNull(emp.get(Employee_.organization)),
cb.equal(emp.get(Employee_.organization).get(InternalOrg_.code), "1")));

JPA Criteria multiselect with fetch

I have following model:
#Entity
#Table(name = "SAMPLE_TABLE")
#Audited
public class SampleModel implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "ID")
private Long id;
#Column(name = "NAME", nullable = false)
#NotEmpty
private String name;
#Column(name = "SHORT_NAME", nullable = true)
private String shortName;
#ManyToOne(fetch = FetchType.LAZY, optional = true)
#JoinColumn(name = "MENTOR_ID")
private User mentor;
//other fields here
//omitted getters/setters
}
Now I would like to query only columns: id, name, shortName and mentor which referes to User entity (not complete entity, because it has many other properties and I would like to have best performance).
When I write query:
CriteriaBuilder builder = em.getCriteriaBuilder();
CriteriaQuery<SampleModel> query = builder.createQuery(SampleModel.class);
Root<SampleModel> root = query.from(SampleModel.class);
query.select(root).distinct(true);
root.fetch(SampleModel_.mentor, JoinType.LEFT);
query.multiselect(root.get(SampleModel_.id), root.get(SampleModel_.name), root.get(SampleModel_.shortName), root.get(SampleModel_.mentor));
query.orderBy(builder.asc(root.get(SampleModel_.name)));
TypedQuery<SampleModel> allQuery = em.createQuery(query);
return allQuery.getResultList();
I have following exception:
Caused by: org.hibernate.QueryException: query specified join fetching, but the owner of the fetched association was not present in the select list [FromElement{explicit,not a collection join,fetch join,fetch non-lazy properties,classAlias=generatedAlias1,role=com.sample.SampleModel.model.SampleModel.mentor,tableName=USER_,tableAlias=user1_,origin=SampleModel SampleModel0_,columns={SampleModel0_.MENTOR_ID ,className=com.sample.credential.model.User}}]
at org.hibernate.hql.internal.ast.tree.SelectClause.initializeExplicitSelectClause(SelectClause.java:214)
at org.hibernate.hql.internal.ast.HqlSqlWalker.useSelectClause(HqlSqlWalker.java:991)
at org.hibernate.hql.internal.ast.HqlSqlWalker.processQuery(HqlSqlWalker.java:759)
at org.hibernate.hql.internal.antlr.HqlSqlBaseWalker.query(HqlSqlBaseWalker.java:675)
at org.hibernate.hql.internal.antlr.HqlSqlBaseWalker.selectStatement(HqlSqlBaseWalker.java:311)
at org.hibernate.hql.internal.antlr.HqlSqlBaseWalker.statement(HqlSqlBaseWalker.java:259)
at org.hibernate.hql.internal.ast.QueryTranslatorImpl.analyze(QueryTranslatorImpl.java:262)
at org.hibernate.hql.internal.ast.QueryTranslatorImpl.doCompile(QueryTranslatorImpl.java:190)
... 138 more
Query before exception:
SELECT DISTINCT NEW com.sample.SampleModel.model.SampleModel(generatedAlias0.id, generatedAlias0.name, generatedAlias0.shortName, generatedAlias0.mentor)
FROM com.sample.SampleModel.model.SampleModel AS generatedAlias0
LEFT JOIN FETCH generatedAlias0.mentor AS generatedAlias1
ORDER BY generatedAlias0.name ASC
I know that I can replace fetch with join but then I will have N+1 problem. Also I do not have back reference from User to SampleModel and I do not want to have..
I ran into this same issue, and found that I was able to work around it by using:
CriteriaQuery<Tuple> crit = builder.createTupleQuery();
instead of
CriteriaQuery<X> crit = builder.createQuery(X.class);
A little extra work has to be done to produce the end result, e.g. in your case:
return allQuery.getResultList().stream()
map(tuple -> {
return new SampleModel(tuple.get(0, ...), ...));
})
.collect(toList());
It's been a long time since the question was asked. But I wish some other guys would benefit from my solution:
The trick is to use subquery.
Let's assume you have Applicant in your Application entity (one-to-one):
#Entity
public class Application {
private long id;
private Date date;
#OneToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "some_id")
private Applicant applicant;
// Other fields
public Application() {}
public Application(long id, Date date, Applicant applicant) {
// Setters
}
}
//...............
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<Application> cbQuery = cb.createQuery(Application.class);
Root<Application> root = cbQuery.from(Application.class);
Subquery<Applicant> subquery = cbQuery.subquery(Applicant.class);
Root subRoot = subquery.from(Applicant.class);
subquery.select(subRoot).where(cb.equal(root.get("applicant"), subRoot));
cbQuery.multiselect(root.get("id"), root.get("date"), subquery.getSelection());
This code will generate a select statement for Application, and select statements for Applicant per each Application.
Note that you have to define an appropriate constructor corresponding to your multiselect.
I got the same problem using EclipseLink as the JPA provider : I just wanted to return the id of a mapped entity («User» in Gazeciarz's example).
This can be achieved quite simply by replacing (in the query.multiselect clause)
root.get(SampleModel_.mentor)
with something like
root.get(SampleModel_.mentor).get(User_.id)
Then, instead of returning all the fields of User, the request will only return the its id.
I also used a tuple query but, in my case, it was because my query was returning fileds from more than one entity.

Hibernate criteria implementation for this entity model (subquery, self-join)

Given the following entity one-to-many model:
One Repository can be linked to many AuditRecords.
Many AuditRecords can all link to the same Repository
#Entity
class AuditRecordEntity {
private AuditRepositoryEntity auditRepository;
#ManyToOne(cascade = CascadeType.ALL)
#JoinColumn(name = AUDIT_REPOSITORY_DB_COLUMN_NAME, nullable = false, updatable = false)
public AuditRepositoryEntity getAuditRepository() {
return auditRepository;
}
...
}
#Entity
class AuditRepositoryEntity {
private List<AuditRecordEntity> auditRecords = new ArrayList<AuditRecordEntity>();
#OneToMany(mappedBy = "auditRepository")
public List<AuditRecordEntity> getAuditRecords() {
return auditRecords;
}
...
}
Minor correction, in ERD diagram below, for 'repositoryId', read 'auditRepository'
I am trying to get the Criteria API implementation to:
Get the latest (by accessTime) AuditRecord for each distinct Repository? I.e. a list of AuditRecords, one for each Repository, where the AuditRecord is the last AuditRecord for that Repository (in the case where a Repository has many AuditRecords).
I have the HQL query to do this:
select auditRecord from AuditRecordEntity auditRecord where auditRecord.accessTime =
(select max(auditRecord2.accessTime) from AuditRecordEntity auditRecord2 where
auditRecord2.auditRepository = auditRecord.auditRepository)
But need to use the Criteria APi instead:
CriteriaBuilder builder = getEntityManager().getCriteriaBuilder();
CriteriaQuery<Object> query = builder.createQuery();
Root<AuditRecordEntity> root = query.from(AuditRecordEntity.class);
// what next?
I have got this to work(around) by using the output from the HQL query as input to the criteria API:
final List<UUID> auditRecordIds = execute("select auditRecord from AuditRecordEntity auditRecord where auditRecord.accessTime =
(select max(auditRecord2.accessTime) from AuditRecordEntity auditRecord2 where
auditRecord2.auditRepository = auditRecord.auditRepository)")
Root<AuditRecordEntity> root = criteriaQuery.from(AuditRecordEntity.class);
criteriaQuery.select(root);
List<Predicate> predicates = new ArrayList<Predicate>();
predicates.add(root.get("id").in(auditRecordIds.toArray()));
entitySearchCriteria.addPredicates(predicates);
...

jpa criteria for many to many relationship

I have 2 POJO classes in Java, Answer and Collaborator, in a many-to-many relationship.
class Answer {
#ManyToMany(cascade = CascadeType.ALL)
#JoinTable(name = "ANSWERS_COLLABORATORS", joinColumns = { #JoinColumn(name = "aid") }, inverseJoinColumns = { #JoinColumn(name = "cid") })
private Set<Collaborator> collaborators = new HashSet<Collaborator>(0);
}
Class Answer has a set of Collaborator, but a Collaborator doesn't keep a set of Answer.
What I need to do from Hibernate CriteriaQuery is to find the collaborators for an answer given by id.
I have already done this with Hibernate Criteria (org.hibernate.Criteria) using result transformer, but I'm stuck when it comes to using CriteriaQuery, because I don't have a list of answers to give to the join.
It's done, finally...
Here's the code:
public List<Collaborator> getCollaborators(Long answerId) {
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<Collaborator> criteriaQuery = cb.createQuery(Collaborator.class);
Root<Answer> answerRoot = criteriaQuery.from(Answer.class);
SetJoin<Answer, Collaborator> answers = answerRoot.join(Answer_.collaborators);
criteriaQuery.where(cb.equal(answerRoot.get(Answer_.id), answerId));
return entityManager
.createQuery(criteriaQuery.select(answers))
.getResultList();
}
Using HQL:
You can use this:
Criteria criteria = session.createCriteria(Answer.class);
criteria.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);
criteria.createAlias("collaborators", "collaborators");
criteria.add(Restrictions.eq("collaborators.id",desiredCollaboratorId);
to get all the Answers associated to a certain Collaborator.
And this:
Criteria criteria = session.createCriteria(Answer.class);
criteria.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);
criteria.setFetchMode("collaborators", FetchMode.JOIN)
criteria.add(Restrictions.idEq(desiredAnswerId));
dsrTrackingCriteria.setProjection(Projections.property("collaborators"));
To get all Collaborators associated to a certain Answer.
Using JPA2 Criteria API you can do something like:
CriteriaBuilder cb = em.getCriteriaBuilder(); //creted from EntityManager instance
CriteriaQuery<Long> cq = cb.createQuery(Collaborator.class);
Root<Answer> rootAnswer = cq.from(Answer.class);
Join<Collaborator,Answer> joinAnswerCollaborator = rootAnswer.join("collaborators"); //(or rootAnswer.join(Answer_.collaborators); if you've created the metamodel with JPA2
Using criteria Builder :
Join<CLASS_A, CLASS_B> join = root.join(WHAT_UVE_DECLARED_IN_MAPPEDBY, JoinType.INNER);
searchCriteria.add(criteriaBuilder.like(join.get("FIELD_IN_SUBCLASS").as(String.class), "%blabla%"));
Join<Answer , Collaborator> join = root.join("collaborators",JoinType.INNER);
predicates.add(criteriaBuilder.equal(join.get("id"),id));

Categories