JPA Criteria select with two subqueries / joins - java

I'm fighting this whole day and I can't figure it out. I'm JPA beginner, so for now Criteria API is a nightmare for me. The problem:
I have 3 entities: Policy, Customer, Insurer. Policy has references to a Customer and an Insurer (eager fetch here). Customer has list of policies, so do Insurer (lazy fetch here).
I'm trying to find all policies in a way described below (SQL):
SELECT * FROM POLICY as p WHERE
p.CUSTOMER_ID IN (SELECT ID FROM CUSTOMER as c WHERE [customerPredicates])
AND
p.INSURER_ID IN (SELECT ID FROM INSURER as i WHERE [insurerPredicates])
AND
[policyPredicates]
Where CUSTOMER_ID / INSURER_ID are JoinColumns generated from #ManyToOne relationships in Policy.
customerPredicates / insurerPredicates / policyPredicates are lists of predicates ('where' conditions prepared from given search criteria).
How can I achieve that in Criteria API? What are rules / good practices for creating this kind of queries?

Try to rewrite the SQL first to use JOINS
SELECT *
FROM POLICY as p
INNER JOIN CUSTOMER as c ON p.CUSTOMER_ID = c.ID
INNER JOIN INSURER as i ON p.INSURER_ID = i.ID
WHERE [customerPredicates])
AND
[insurerPredicates])
AND
[policyPredicates]
The all you need is criteria api is to get main criteria (for the Policy entity) and create aliases for Customer entity and Insurer entity.
Criteria criteria = session.createCriteria(Policy.class, "p");
criteria.setFetchMode("p.Customer", FetchMode.JOIN);
criteria.createAlias("p.Customer", "c");
and add your restriction to the "c" alias for Customer.
And the same for Insurer

Related

Hibernate criteria - Inner Join OR Condition

I want to ignore default Join Restriction in createAlias. I have a OnetoOne relationship.
My Problem is Hibernate generates default restriction for the join relationship.
Pojo
Note : No column for diagnosticTemplate in charge table.
Charge.java
#OneToOne(mappedBy = "charge")
private DiagnosticTemplate diagnosticTemplate;
DiagnosticTemplate.java
#OneToOne
#JoinColumn(name = "charge")
#Exclude
private Charge charge;
Query
select
*
from
charges c
inner join diagnostic_template dt
on (dt.charge = c.id and dt.status=1) or (dt.status=0)
Criteria
Criteria criteria = getSession().createCriteria(Charge.class, "charge")
.createAlias("charge.diagnosticTemplate", "diagnosticTemplate",
JoinType.INNER_JOIN,
Restrictions.or(
Restriction.and(Restrictions.eqProperty("charge.id",
"diagnosticTemplate.charge"),
Restrictions.eq("diagnosticTemplate.status",true)),
Restrictions.eq("diagnosticTemplate.status",false) ))
Hibernate Query
select
*
from
charges c
inner join diagnostic_template dt
on dt.charge = c.id and (dt.charge = c.id and dt.status=1) or (dt.status=0)
How to avoid this condition? or anything wrong with my relationship?
Please help..!
When you join a charge with charge.diagnosticTemplate, it means that Hibernate will try to lookup a DiagnosticTemplate that is linked with this charge. Thus, the generated condition dt.charge = c.id does make sense.
Remember that you have define the relationship using foreign key. So, soft relation like dt.status=0 cannot be understood by Hibernate.
If you still wish to achieve you query, you can consider joining the two instance indirectly (not using association path). Refer to How to join Multiple table using hibernate criteria where entity relationship is not direct?.

How can I annotate this complex join in JPA?

Let's say I have the following tables:
Locations
================
LocationKey, LocationName
Employees
================
EmployeeKey, FirstName, LastName
EmployeeLocationXRef
================
EmployeeLocationXRefKey, LocationKey, EmployeeKey
TimeSheets
=================
TimeSheetKey, EmployeeLocationXRefKey, Minutes
OK, so as you can see, in order for me to get all of the TimeSheets for a Location and Employee, I can do the following in SQL
select
*
from TimeSheets ts
join EmployeeLocationXRef ex on (ex.EmployeeLocationXRefKey = ts.EmployeeLocationXRefKey)
join Locations l on (l.LocationKey = ex.LocationKey)
join Employees e on (e.EmployeeKey = ex.EmployeeKey)
where
l.LocationKey = 'xxxx'
and e.EmployeeKey = 'yyyy'
But I have tried every way I can think of to map with (with annotations) in JPA using #JoinTable, etc.
Any ideas how this could be done? Unfortunately, this is a legacy system and the schema cannot be changed.
EDIT
Forgot to mention the EmployeeLocationXRef entity has not been directly mapped. Which could be the real problem. I will see about creating that entity mapping and see if it makes it easier.
I don't see anything particularly difficult or strange about this schema. There should simply be one entity per table and (unless there are unique constraints):
a ManyToOne between EmployeeLocationXRef and Location, using a JoinColumn
a ManyToOne between EmployeeLocationXRef and Employee, using a JoinColumn
a ManyToOne between Timesheet and EmployeeLocationXRef, using a JoinColumn
(these associations can of course be made bidirectional, or be in the other direction if you prefer so).
The JPQL query would simply be
select ts from Timesheet ts
join ts.employeeXRef ex
join ex.location l
join ex.employee e
where
l.locationKey = 'xxxx'
and e.employeeKey = 'yyyy'
which is a straightforward translation of the SQL query.

Hibernate Native SQL Query retrieving multiple entities in join

Referencing with this answer to this correlated thread,
the trick posted by ehrhardt works fine.
But, what I have to do if I have to join with multiple entities? for example:
List<Person> peopleWithBooks = session.createSQLQuery(
"select {p.*}, {b.*}, {m.*} from person p, book b, magazine m where <complicated join>")
.addEntity("p", Person.class)
.addJoin("b", "p.books")
.addJoin("m", "p.magazines")
.addEntity("p", Person.class)
.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY)
.list();
Hibernate aggregates right the first join, but not the second (magazine entities are not grouped).
There are any tricks or there is a limit to join with only one correlated entity?
And if I have to join with entities that have sub entities? (my goal is to retrieve all selected data with only one custom query)

How to fetch all data in one query

I have multiple entities that are queried via JPA2 Criteria Query.
I am able to join two of these entities and get the result at once:
CriteriaBuilder criteriaBuilder = em.getCriteriaBuilder();
CriteriaQuery<LadungRgvorschlag> criteriaQuery = criteriaBuilder.createQuery(LadungRgvorschlag.class);
Root<LadungRgvorschlag> from = criteriaQuery.from(LadungRgvorschlag.class);
Join<Object, Object> ladung = from.join("ladung");
from.fetch("ladung", JoinType.INNER);
Then i try to join an additional table like that:
ladung.join("ladBerechnet");
ladung.fetch("ladBerechnet", JoinType.LEFT);
i get the following error:
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=generatedAlias3,role=null,tableName=ladberechnet,tableAlias=ladberechn3_,origin=ladungen ladung1_,columns={ladung1_.id ,className=de.schuechen.beans.tms.master.LadBerechnet}}] [select generatedAlias0 from de.schuechen.beans.tms.master.LadungRgvorschlag as generatedAlias0 inner join generatedAlias0.ladung as generatedAlias1 inner join generatedAlias1.ladBerechnet as generatedAlias2 left join fetch generatedAlias1.ladBerechnet as generatedAlias3 inner join fetch generatedAlias0.ladung as generatedAlias4 where ( generatedAlias0.erledigt is null ) and ( generatedAlias0.belegart in (:param0, :param1) ) and ( generatedAlias1.fzadresse in (:param2, :param3) ) and ( generatedAlias1.zudatum<=:param4 ) and ( 1=1 ) order by generatedAlias0.belegart asc, generatedAlias1.fzadresse asc, generatedAlias1.zudatum asc, generatedAlias1.zulkw asc]
How can i tell JPA/Hibernate, that it should select all the entities at once?
With JPA 'some dialects of JPA' you can chain join fetches, but I don't think you can/should do both a join and a join fetch.
For instance, if we have a Program that has a one-to-many relation to a Reward that has a relation to a Duration, the following JPQL would get a specific instance with the rewards and duration pre-fetched:
SELECT DISTINCT
program
FROM
Program _program
LEFT JOIN FETCH
_program.rewards _reward
LEFT JOIN FETCH
_reward.duration _duration
WHERE
_program.id = :programId
}
With the equivalent Criteria code:
CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
CriteriaQuery<Program> criteriaQuery = criteriaBuilder.createQuery(Program.class);
Root<Program> root = criteriaQuery.from(Program.class);
Fetch<Program, Reward> reward = root.fetch("rewards", JoinType.LEFT);
Fetch<Reward, Duration> duration = reward.fetch("duration", JoinType.LEFT);
criteriaQuery.where(criteriaBuilder.equal(root.get("id"), programId));
TypedQuery<program> query = entityManager.createQuery(criteriaQuery);
return query.getSingleResult();
Note that the intermediate variables reward and duration are not needed here, but they're just for informational purposes. root.fetch("rewards", JoinType.LEFT).fetch("duration", JoinType.LEFT) would have the same effect.
What it comes to JPA, you cannot chain join fetches in Criteria API queries (citation from specification):
An association or attribute referenced by the fetch method must be
referenced from an entity or embeddable that is returned as the result
of the query. A fetch join has the same join semantics as the
corresponding inner or outer join, except that the related objects are
not top-level objects in the query result and cannot be referenced
elsewhere by the query.
And it is also not supported in JPQL queries:
The association referenced by the right side of the FETCH JOIN clause
must be an association or element collection that is referenced from
an entity or embeddable that is returned as a result of the query.
It is not permitted to specify an identification variable for the
objects referenced by the right side of the FETCH JOIN clause, and
hence references to the implicitly fetched entities or elements cannot
appear elsewhere in the query.
With HQL it seems to be possible: Hibernate documentation EclipseLink does not provide such a extension, so syntax of following query is accepted by Hibernate, but not by EclipseLink:
SELECT a FROM A a LEFT JOIN FETCH a.bb b LEFT JOIN FETCH b.cc
In EclipseLink same can be done via query hints.

Hibernate Critieria join two tables with condition on 2nd table and result the 1st table

I have a question using Hibernate Criteria, I need to convert this query using criteria.
SELECT * FROM A a_ INNER JOIN B b_ ON a_.column1=b_.column1 AND b_.column2 IN (X,Y) AND active='Y';
I need the result as table A.
I just solved this issue, here is my code
Criteria criteria = session.createCriteria(ProductOffer.class);
criteria.setResultTransformer(CriteriaSpecification.DISTINCT_ROOT_ENTITY);
Date effDate = TypeConvertUtil.toDate(param.get("effDate"));
criteria.add(Restrictions.le("effDate", effDate));
criteria.add(Restrictions.gt("expDate", effDate));
criteria.createAlias("productOfferPropDefs", "def",JoinType.LEFT_OUTER_JOIN);
criteria.setFetchMode("productOfferPropDefs", FetchMode.JOIN);
criteria.add(Restrictions.le("def.effDate", effDate));
criteria.add(Restrictions.gt("def.expDate", effDate));
criteria.createAlias("def.productOfferProps", "prop",JoinType.LEFT_OUTER_JOIN);
criteria.setFetchMode("def.productOfferProps", FetchMode.JOIN);
criteria.add(Restrictions.le("prop.effDate", effDate));
criteria.add(Restrictions.gt("prop.expDate", effDate));
productOfferList = criteria.list();
Please beware that
criteria.createAlias("productOfferPropDefs", "def",JoinType.LEFT_OUTER_JOIN);
this parameter is important:
JoinType.LEFT_OUTER_JOIN
if you did not use it, and your relation is one-to-many, it will hit the 1:N classic issue for hibernate
If the associations are defined, see http://docs.jboss.org/hibernate/core/3.3/reference/en/html/querycriteria.html#querycriteria-associations
In case associations are not specified in the entities definition, you can't use criteria.
You can use HQL to do inner joins (need to write in implicit join notation), for doing left joins you have to use native SQL.

Categories