jpa criteria for many to many relationship - java

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));

Related

Hibernate - JPA - oneToMany - count as subquery and use as a Predicate

I have the following relation and I need to get consumers which have at least one purchase (as a subquery, because this is a part of a bigger query).
#Entity
#Table(name = "consumers")
public class Consumer extends User {
#JsonIgnore
#OneToMany(cascade = CascadeType.ALL)
#JoinColumn(name = "consumer_id")
private List<Purchase> purchases;
}
and the query
CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
CriteriaQuery<Consumer> criteriaQuery = criteriaBuilder.createQuery(Consumer.class);
Root<Consumer> root = criteriaQuery.from(Consumer.class);`
Join<Consumer, Purchase> purchases = root.join(Consumer_.purchases, JoinType.LEFT);
sub.select(criteriaBuilder.count(purchases.get(Purchase_.id)));
sub.where(criteriaBuilder.equal(root.get(Consumer_.id), purchases.get(Purchase_.consumer).get(Consumer_.id)));
predicates.add(criteriaBuilder.greaterThanOrEqualTo(sub, 0L));

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")));

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 query a collection using between clause

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).

Resolving Criteria on Polymorphic child class attribute jpa hibernate query

Using hibernate 3.6.10 with hibernate jpa 2.0.
My problem boils down to needing to set some criteria on a column of a child object during a somewhat complex joining query.
I have a set of objects similar to:
#Entity
#Inheritance(strategy = InheritanceType.JOINED)
public class Ball
{
private String name;
//...getter and setter crud...
}
#Entity
public class BeachBall extend ball
{
private boolean atTheBeach;
//...getter and setter crud...
}
#Entity
public class SoccerBall extend ball
{
private int numberOfKicks;
//...getter and setter crud...
}
#Entity
public class Trunk
{
private Set<Ball> balls;
#OneToMany(mappedBy = "trunk", cascade = CascadeType.ALL, orphanRemoval = true)
public Set<Ball> getBalls()
{
return balls;
}
}
#Entity
public class Car
{
private Trunk trunk;
private String carModel;
//...getter and setter crud...
}
Now i need to query how many soccer balls have 20 kicks in a car with a specific model.
Using JPA I tried to do something like:
CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
CriteriaQuery<Car> criteriaQuery = criteriaBuilder.createQuery(Car.class);
Root<Car> car= criteriaQuery.from(Car.class);
Join<Car, Trunk> trunkJoin = car.join(Car_.trunk);
Join<Trunk, Ball> ballJoin = trunkJoin.join(Trunk_.Balls);
criteriaQuery.select(trunk);
Predicate [] restrictions = new Predicate[]{ criteriaBuiler.equal(car.get(carModel), "Civic"), criteriaBuilder.equal(ballJoin.get("numberOfKicks"), 20)};
criteriaQuery.where(restrictions);
TypedQuery<Car> typedQuery = entityManager.createQuery(criteriaQuery);
Car carWithSoccerBalls = typedQuery.getSingleResult();
At runtime the above code dies because numberOfKicks is only on soccerballs and due to how its typed in Trunk it only knows about ball. If i manually create a from on the soccerballs and setup criteria to join it i can query numberOfKicks, however i feel like there must be a way to inform the query that the set is in fact a set.
Please note i cannot post any of the actual code so all above examples are just examples.
Using JPA and hibernate like above is there a way to force hibernate to know that the set< ball > is actually set< soccerball >?
Due to time restrictions i'm taking the easy way out :(. If anyone can answer better then what i have i'll gladly choose their answer over mine.
To make the criteria api recognize that i'm looking for the inherited table i changed my query code to be:
CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
CriteriaQuery<Car> criteriaQuery = criteriaBuilder.createQuery(Car.class);
Root<Car> car= criteriaQuery.from(Car.class);
Root<Soccerball> soccerballs = criteriaQuery.from(SoccerBall.class);
Join<Car, Trunk> trunkJoin = car.join(Car_.trunk);
Join<Trunk, Ball> ballJoin = trunkJoin.join(Trunk_.Balls);
criteriaQuery.select(trunk);
Predicate [] restrictions = new Predicate[]{ criteriaBuiler.equal(car.get(carModel), "Civic"), criteriaBuilder.equal(soccerball.get("numberOfKicks"),20), criteriaBuilder.equal(soccerball.get(SoccerBall_.id),car.get(Car_.id))};
criteriaQuery.where(restrictions);
TypedQuery<Car> typedQuery = entityManager.createQuery(criteriaQuery);
Car carWithSoccerBalls = typedQuery.getSingleResult();
The following retrieves all Cars with nested list attributes satisfying equality criteria for subclass type in a collection and equality on root element.
I've modified the query to work with the datamodel in the original question.
CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
CriteriaQuery<Car> carQuery = criteriaBuilder.createQuery(Car.class);
Root<Car> carRoot = carQuery.from(Car.class);
Subquery<SoccerBall> ballQuery = carQuery.subquery(SoccerBall.class);
Root<SoccerBall> soccerBall = ballQuery.from(SoccerBall.class);
ballQuery.select(soccerBall);
ballQuery.where(criteriaBuilder.equal(soccerBall.get(SoccerBall_.numberOfKicks), 25));
Join<Car, Trunk> carTrunkJoin = carRoot.join(Car_.trunk);
SetJoin<Trunk, Ball> trunkBallJoin = carTrunkJoin.join(Trunk_.balls);
carQuery.select(carRoot);
carQuery.where(criteriaBuilder.and(
trunkBallJoin.in(ballQuery),
criteriaBuilder.equal(carRoot.get(Car_.carModel), "Civic")));
TypedQuery<?> typedQuery = entityManager.createQuery(carQuery);
List<?> result = typedQuery.getResultList();
The equivalent SQL is:
SELECT * FROM car JOIN trunk JOIN ball WHERE ball.id IN (SELECT soccerball.id FROM soccerball WHERE soccerball.numberOfKicks = 25)

Categories