LEFT JOIN unrelated entity to root table in JPA Criteria builder - java

I have got 2 tables TABLE_A & TABLE_B. I have got 2 entity classes for these 2 tables named TableA & TableB.
TableA has got an attribute which is actually a foreign key, is ID of TableB. But this relation is not there at table level as well as entity class level.
Which means that these two tables are unrelated with respect to entity manager.
Now I want to generate a jpa query like below
select
tablea.name, tableb.name
from TableA tablea left join TableB tableb on
tablea.tablebId=tableb.pkId
Basically Table_A is my main table.
Here is what I have trie, this is doing cross join
String rootClassNameWithPackage = "TableA";
String fkClassNameWithPackage = "TableB";
CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
CriteriaQuery<?> criteriaQuery = null;
Root<?> rootTable = null;
Root<?> fkTable = null;
try {
criteriaQuery = criteriaBuilder.createQuery(String.class);
rootTable = criteriaQuery != null ? criteriaQuery.from(Class.forName(rootClassNameWithPackage)) : null;
fkTable = criteriaQuery != null ? criteriaQuery.from(Class.forName(fkClassNameWithPackage)) : null;
List<Predicate> criteriaList = new ArrayList<>();
Predicate predicateSearchFk = criteriaBuilder.equal(rootTable.get("tablebId"), fkTable.get("pkid"));
criteriaList.add(predicateSearchFk);
criteriaQuery.where(criteriaBuilder.and(criteriaList.toArray(new Predicate[0])));
} catch (ClassNotFoundException e) {
logger.error(e.getMessage());
}
final TypedQuery<?> query = entityManager.createQuery(criteriaQuery);
query.setMaxResults(pageSize);
return query.getResultList();
As mentioned earlier, this is doing cross join. We want to left join. Unfortunately we cant change the entity classes to add the FK relationships.

Related

Order list by max date of a children (using Hibernate Critéria)

I'm trying to sort a list by the max date of a children class, using hibernate criteria:
Domain:
class Entity {
List<Children> childrens;
}
class Children {
Date date
}
Something like this in SQL:
SELECT
*
FROM
entity
INNER JOIN
children c1 ON c1.entity_id = entity.id
WHERE
c1.date =
(SELECT MAX(c2.date) FROM children c2 WHERE c2.entity_id = c1.entity_id)
ORDER BY
c1.date DESC
Does anyone know how to do this?
You can formulate something like that with HQL or the JPA Criteria API. Here is the HQL version:
from
Entity e
order by
(select max(c.date) from e.children c) desc
or with the Criteria API:
CriteriaBuiler cb = em.getCriteriaBuilder();
CriteriaQuery<Entity> q = cb.createQuery(Entity.class);
Root<Entity> r = q.from(Entity.class);
Subquery<Date> s = q.subquery(Date.class);
Join<?, ?> c = s.correlate(r).join("children");
s.select(cb.max(c.get("date")));
q.orderBy(cb.desc(s));
List<Entity> list = em.createQuery(q).getResultList();

Jpa criteria api - create class that connect other class

Let's say I have the following SQL:
select * from table1
inner join table2 on table1.id = table2.table1id;
inner join table3 on table2.id = table3.table2id;
inner join table4 on table3.id = table4.table3id;
and I have Java entities like table1, table2, table3 and table4.
I want to map this query using criteria API. In order to do that I created class Table5 which contains all of fields of all classes.
Then I created a repository with the method:
public List<Table5> getAllTable5 () {
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Table5> query = cb.createQuery(Table5.class);
Root<Table1> root = query.from(Table1.class);
Join<Table1, Table2> table1Table2Join= root.join(Table1_.TABLE2);
Join<Table2, Table3> table2Table3Join= root.join(Table2_.TABLE3);
Join<Table3, Table4> table3Table4Join= root.join(Table3_.TABLE4);
query.multiselect(
root.get // all fields
);
TypedQuery<Table5> typedQuery = em.createQuery(query);
return typedQuery.getResultList();
}
Is it possible to create class like:
class Table5 {
private Table1 table1;
private Table2 table2;
private Table3 table3;
private Table4 table4;
// getters setters constructors
}
If yes, how should getAllTable5 method look like?
You can either select every table entity, or select just the first one and rely on join fetching.
public List<Table5> getAllTable5 () {
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Table1> query = cb.createQuery(Table1.class);
Root<Table1> root = query.from(Table1.class);
query.select(root);
TypedQuery<Table1> typedQuery = em.createQuery(query);
EntityGraph entityGraph = em.createEntityGraph();
// Apply subgraphs for the associations to fetch
typedQuery.setHint("javax.persistence.loadgraph", entityGraph);
List<Table1> list = typedQuery.getResultList();
List<Table5> table5s = new ArrayList<>(list.size());
for (Table1 t : list) {
table5s.add(new Table5(t, t.getTable2(), , t.getTable2().getTable3(), , t.getTable2().getTable3().getTable4());
}
return table5s;
}

JPA Criteria query double joins and hibernate error with-clause

Based on this answer https://stackoverflow.com/a/2111420/3989524 I first created a working SQL:
SELECT d.*
FROM device d
LEFT OUTER JOIN installation_record ir1 ON (d.id = ir1.device)
LEFT OUTER JOIN installation_record ir2 ON (d.id = ir2.device AND ir1.install_date < ir2.install_date)
WHERE ir2.id IS NULL AND ir1.uninstall_date IS NULL;
and then a criteria query which looks to produce an equivalent HQL (in the end), but Hibernate throws an error:
org.hibernate.hql.internal.ast.QuerySyntaxException: with-clause referenced two different from-clause elements
The HQL from the error message:
SELECT generatedAlias0 FROM Device AS generatedAlias0
LEFT JOIN generatedAlias0.installationRecordList AS generatedAlias1
LEFT JOIN generatedAlias0.installationRecordList AS generatedAlias2
WITH generatedAlias1.installDate<generatedAlias2.installDate
WHERE ( generatedAlias2.id IS NULL ) AND ( generatedAlias1.uninstallDate IS NULL )
The criteria query:
final CriteriaBuilder cb = em.getCriteriaBuilder();
final CriteriaQuery<Device> cq = cb.createQuery(Device.class);
final Root<Device> root = cq.from(Device.class);
final Join<Device, InstallationRecord> join1 = root.join(Device_.installationRecordList, JoinType.LEFT);
final Join<Device, InstallationRecord> join2 = root.join(Device_.installationRecordList, JoinType.LEFT);
join2.on(cb.lessThan(join1.get(InstallationRecord_.installDate), join2.get(InstallationRecord_.installDate)));
cq.select(root);
final List<Predicate> predicates = Lists.newArrayList();
predicates.add(cb.isNull(join2.get(InstallationRecord_.id)));
predicates.add(cb.isNull(join1.get(InstallationRecord_.uninstallDate)));
cq.where(predicates.toArray(new Predicate[] {}));
return em.createQuery(cq).getResultList();
Is there anyway to get what I want or there no other way around some internal hibernate bug.
Maybe:
final Join<InstallationRecord, InstallationRecord> join2 = root.join(InstallationRecord_.id, JoinType.LEFT);
because ON (d.id = ir2.device AND is missing in the HQL.

How to join 2 tables based on passing a parameter for search operation?

I have 2 entities Addemp and Job.I want to join these 2 tables based on empid .empid is foreign key in job table and primary key in addemp table
here i am doing a search operation based on employee id .i am using criteria builder for search operation
the relationship here is manytoone
public List<Object[]> findEmployeeList(Integer id)
{
EntityManager em=null;
try
{
em=getEntityManager();
CriteriaBuilder cb=em.getCriteriaBuilder();
CriteriaQuery cq=cb.createQuery(Addemp.class);
Root<Addemp>rt= cq.from(Addemp.class);
Join<Addemp,Job> job=rt.join(Addemp_.jobCollection);
Predicate predicate=cb.equal(rt.get("empId"),id);
cq.where(predicate);
/* cq.select(rt.get("firstName"));
cq.where (cb.equal(rt.<String>get("empId"),id));*/
Query qry= em.createQuery(cq);
return qry.getResultList();
Based on the question and JPQL query from the above comments here is the criteria query proposal:
public List<Tuple> findEmployeeList(Integer id)
{
em = getEntityManager();
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Tuple> cq = cb.createTupleQuery();
Root<Addemp> empRoot = cq.from(Addemp.class);
Root<Job> jobRoot = cq.from(Job.class);
cq.multiselect(empRoot.get("empId").alias("id"),
empRoot.get("firstName").alias("first"),
empRoot.get("lastName").alias("last"),
jobRoot.get("jobTitle").alias("title"),
jobRoot.get("empStatus").alias("status"),
jobRoot.get("subUnit").alias("subunit"));
cq.where(cb.equal(empRoot.get("empId"), id));
TypedQuery<Tuple> q = em.createQuery(cq);
return q.getResultList();
}
Next you may want to extract a tuple result:
List<Tuple> tuples = service.findEmployeeList(2);
for (Tuple t : tuples) {
StringBuilder builder = new StringBuilder(64)
.append(t.get("id")).append(", ")
.append(t.get("first")).append(", ")
.append(t.get("last")).append(", ")
.append(t.get("title")).append(", ")
.append(t.get("status")).append(", ")
.append(t.get("subunit"));
System.out.println(builder.toString());
}
I hope it helps.

Select the last (or first) inserted element of a OneToMany relationship using Criteria Query?

Consider entity A with a unidirectional #OneToMany relationship to entity B. B has a persistent timestamp field set when persisted and never changed thereafter.
How do I make a criteria (tuple) query that returns for each A the last (or first) B added to the collection?
Here's my take at it so far:
// Root query
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Tuple> q = cb.createTupleQuery();
Root<A> root = q.from(A.class);
Join<A, B> bItem = root.join("listOfB");
// Subquery
Subquery<Date> sq = q.subquery(Date.class);
Join<A, B> sqBItem = sq.correlate(bItem);
Expression<Date> sqBItemCreatedOn = sqBItem.get("createdOn"); // <-- TS on sqBItem
sq.select(cb.greatest(sqBItemCreatedOn)).groupBy(root);
// Back to the root query
Expression<Date> bItemCreatedOn = bItem.get("createdOn"); // <-- TS on bItem
q.multiselect(root, bItem).where(cb.equal(bItemCreatedOn, sq));
em.createQuery(q).getResultList(); // <-- fails
I also tried with
q.multiselect(root, cntItem).where(cb.in(cntCreatedOn).value(sq));
but to no avail.
Please note that the relationship is unidirectional at this stage so I wonder if the subquery part is working as it should. By making the relationship bidirectional, I guess I could query B instead and group on A but I have no control over the domain model.
Here is my attempt (without a subquery):
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Tuple> q = cb.createTupleQuery();
Root<A> root = q.from(A.class);
Join<A, B> bItem = root.join("listOfB");
q.multiselect(
root.alias("A"),
cb.greatest(bItem.<Date>get("createdOn")).alias("TS")); //MAX
// cb.least(bItem.<Date>get("createdOn")).alias("TS")); //MIN
q.orderBy(cb.asc(root));
q.groupBy(root);
List<Tuple> tuples = em.createQuery(q).getResultList()
for (Tuple array : result) {
System.out.println(array.get("A").toString() + ":\t" + array.get("TS"));
}
From JPQL perspective this might look like:
SELECT root, MAX(b.createdOn)
FROM A root JOIN root.listOfB bItem
GROUP BY root
ORDER BY root;

Categories