How can I left join two unrelated tables using JPA Criteria? - java

Here I have two tables users and orders, users has two fields first_name and last_name, and orders has a field full_name. For some reason, I cannot change the database schema.
Now I have a query:
SELECT u.* FROM users u LEFT JOIN orders o ON o.full_name = CONCAT(u.first_name, ' ', u.last_name)
And I need convert this into JPA Specifications because it is a part of other specifications. The following is my try:
Entity:
#Entity
#Table(name = "users")
class User {
#Id
private Integer id;
#Column(name = "first_name")
private String firstName;
#Column(name = "last_name")
private String lastName;
}
#Entity
#Table(name = "orders")
class Order {
#Id
private Integer orderId;
#Column(name = "full_name")
private String fullName;
}
The problem is, what should I fill in the following statement for the first parameter
(root, query, criteraBuilder) -> {
Join<User,Order> join = root.join(???,JoinType.LEFT);
join.on(blablabla_condition)
}
Is this doable?
Thanks

In order to go down the route you are trying to implement, you'll have to map the association within the User/Order as a #OneToOne or #OneToMany lazy association based on whether there can be multiple orders for a user or not. For that you'll have to map the join operation that is the complicated logic you have mapped in the native query at the top. I suggest you take a look in the #JoinFormula and #JoinColumnOrFormula as ways to map that join.

First attempt...
What if you make a method with your native query?
#Query(value = "SELECT u.* FROM users u LEFT JOIN orders o ON o.full_name = CONCAT(u.first_name, ' ', u.last_name)", nativeQuery = true)
List<User> getUserJoinedWithOrder();
UPDATED
Specification<One> p = (user, query, cb) -> {
return cb.equal(
query.from(Order.class).get("fullName"),
cb.concat(user.get("firstName"), cb.concat(" ", user.get("lastName"))));
};
List<One> list = oneRepo.findAll(p);

Related

Hibernate Criteria Builder: How to filter by a nullable entity attribute?

Scenario
I have an entity Treatment which has two FKs:
#ManyToOne
#JoinColumn(name = "product_id", nullable = true)
private Product product;
#ManyToOne
#JoinColumn(name = "product2_id", nullable = true)
private Product product2;
This Product entity has a regular id attribute as PK.
What I want:
I want to find all treatments where any of the products' ids matches a given one, in native SQL it would be something like:
select t.id, product_id,product2_id, p1.category_id,p2.category_id
from Treatment t
left join product p1 on t.product_id=p1.id
left join product p2 on t.product2_id=p2.id
where p1.category_id=17 or p2.category_id=17
What I've done so far:
Given:
CriteriaQuery<Treatment> cr = cb.createQuery(Treatment.class);
Root<Treatment> root = cr.from(Treatment.class);
My attempt was:
if (category != null ) {
Predicate predicate = cb.or(
cb.equal(root.get("product").get("category"), category),
cb.equal(root.get("product2").get("category"), category)
);
predicateList.add(predicate);
}
but it did not work: it gets treatments where both products are not null and at least one of them matches the category condition.
The generated query was (after cleaning it to make it readable):
select
...
from treatment t
cross join product p1
cross join product p2
where t.product_id=p1.id
and t.product2_id=p2.id
and (p1.category_id=17 or p2.category_id=17)
order by t.id desc
Try building paths using From#join, where you can explicitly define join type, something like:
Predicate predicate = cb.or(
cb.equal(root.join("product", JoinType.LEFT).get("category"), category),
cb.equal(root.join("product2", JoinType.LEFT).get("category"), category)
);
This should work:
Predicate predicate = (root1, cq1, cb1) -> Specifications.where(
cb1.equal(root1.get("product").get("category"), category)).or(
cb1.equal(root1.get("product2").get("category"), category))
.toPredicate(root, cb, cq);

DTO projection on a recursive structure

In my data model there is an entity "location" which is recursively. Furthermore there are relations to other entities.
The corresponding JPA (Spring Data JPA) entity looks like:
#Entity
#Table(name = "location")
class Location{
#OneToMany(mappedBy = "parent", orphanRemoval = true)
#OrderBy("name ASC")
Set<Location> children = null
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "parent_id")
Location parent = null
#Column(name = "name")
String name = null
#OneToMany(mappedBy = "location", fetch = FetchType.EAGER)
Stops stops = null
...
What is the most performant way to do a read only query? I just need the information inside the entity (table location) with the complete recursive structure but no information from the related entities.
I've read the phrase DTO projection, but nothing about what to do with a recursive structure.
Reading a recursive structure is usually done by making use of what SQL calls a recuresive CTE. JPA does not support that out of the box, because not all RDBMS support it. If you know that your DBMS supports it, you can make use of the following SQL to do this:
WITH RECURSIVE nodes(id, parent_id) AS (
SELECT id, parent_id FROM location l where id = ?
UNION ALL
SELECT l.id, l.parent_id FROM nodes n JOIN location l ON n.parent_id = l.id
)
SELECT id, parent_id FROM nodes
With that you get a list of a specific and all parent location ids as well as their respective parents which is flat. You will have to bring structure into this.
List<Object[]> result = //get the result of the query
Map<Integer, LocationDto> locationMap = new HashMap<>();
result.forEach(r -> locationMap.put(result.get(0), new LocationDto(result[0], result[1])));
locationMap.values().forEach(l -> l.setParent(locaitonMap.get(l.getParentId())));
If you don't want to make use of plain SQL because of portability concerns or just because you don't want to give up on your abstraction, you can make use of Blaze-Persistence which works on top of JPA and adds support for CTEs. Your query with blaze-persistence would look like this
List<LocationCte> result = criteriaBuilderFactory.create(entityManager, LocationCte.class)
.withRecursive(LocationCte.class)
.from(Location.class, "l")
.bind("id").select("l.id")
.bind("parent").select("l.parent.id")
.where("id").eq(initialId)
.unionAll()
.from(Location.class, "l")
.innerJoinOn(LocationCte.class, "cte")
.on("cte.parent").eqExpression("l.id)
.end()
.bind("id").select("l.id")
.bind("parent").select("l.parent.id")
.end()
.from(LocationCte.class)
.getResultList();
You will also need this special entity class
#CTE
#Entity
public class LocationCte {
#Id Integer id;
Integer parent;
}

How can I use Hibernate to load a list of entities and a subset of those entities' related entities?

I have a query I'd like to run against a table, let's call it parent where I'm grabbing all rows that match a certain criteria. In SQL:
select * from parent where status = 'COMPLETE';
I have this table and another related table (child) defined as Hibernate entities such that:
#Entity
#Table(name = "parent")
public class Parent {
//...
#OneToMany(mappedBy = "parent")
private Set<Child> children;
//...
}
#Entity
#Table(name = "child")
public class Child {
//...
#ManyToOne
#JoinColumn(name = "parent_id")
private Parent parent;
#Column(name = "key")
private String key;
//...
}
I'd like my query to ALSO pull two optional child records where key is one of two values. So, in SQL:
select *
from parent p, child ca, child cb
where p.status = 'COMPLETED'
and p.id *= ca.parent_id
and ca.key = 'FIRST_KEY'
and p.id *= cb.parent_id
and cb.key = 'SECOND_KEY';
I could do this in Hibernate by just grabbing the result from the first query and iterating over the children collection looking for the rows I want but that's terribly inefficient: one query that does two outer joins vs one query + an additional query for each looking for the children I care about.
Is there a way to replicate the outer joins in the query above in Hibernate where the objects returned will have the children collection only populated with the two (or less, since they are optional) entities I am interested in?
You don't need two outer joins. You could simply use this HQL and Hibernate will add the right children to the right parent:
List<Parent> parentList = session
.createQuery("from Parent p left join fetch p.children c" +
" where p.status = 'COMPLETE' " +
" and (c.key = 'FIRST_KEY' or c.key = 'SECOND_KEY')")
.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY)
.list();
for(Parent parent:parentList) {
System.out.println(parent);;
}
Hope that solves your problem.

Hibernate DetachedQuery and Spring's HibernateTemplate with Restriction giving wrong results

My data structure is like this
Department
-> Employees
-> Gender
-> CityID -> Cities
->CityID
->CountryID -> Countries
-> CountryID
Department Class:
public class Department {
#OneToMany(mappedBy = "departmentid", cascade = CascadeType.ALL, fetch = FetchType.EAGER)
private Set<Employee> employees = new HashSet<>();
}
I build Crteria like this:
DetachedCriteria criteria = DetachedCriteria.forClass(Department.class);
DetachedCriteria detlCrit = criteria.createCriteria("employees");
detlCrit.add(Restrictions.eq("gender", "MALE"));
detlCrit.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);
I have 1 Department, 2 Employees in the Tables (1 male, 1 female).
When I excecute this criteria iam expecting Hibernate build one 'Department' object, one 'Employee' object, and city, country etc.,
But what iam getting is 1 Department, 2 Employees.
When I see the queries executed by Hibernate in logs, it shows two queries
First Query:
Select * from Department, Employee
Left outer join City on Employee.cityID = City.cityID
Left outer join Country on City.countryID = City.countryID
Where Employee.DeptID = Department.DeptID
AND Employee.Gender = 'MALE';
Second query:
Select * from Employee
Left outer join City on Employee.cityID = City.cityID
Left outer join Country on City.countryID = City.countryID
Where Employee.DeptID = Department.DeptID;
Second query is wrong there is no Restriction applied on Gender='MALE';
What iam doing wrong? any suggestions? how to solve this?
sorry queries may be not exactly correct, but you got the idea.
Any more details needed please ask, I can provide.
Thanks in advance..
Try this,using SessionFactory.
#Autowired
private SessionFactory sessionFactory;
Criteria criteria = sessionFactory.getCurrentSession().createCriteria(Department.class);
criteria.add(Restrictions.eq("gender", "MALE"));
Hope I was useful.
The first query is selecting Department entities and the filtering is applied as you specified in your where clause.
But you cannot truncate associations, you always have to fetch them all eagerly or lazily. That's because Hibernate has to maintain consistency guarantees when flushing back the loaded Department entity and possibly cascading the employees state back to the database.
The second query is most likely because you use a FetchType.EAGER on your employees collection:
#OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL, mappedBy = "department", orphanRemoval = true)
private List<Employee> employees = new ArrayList<>();
Once the Department is fetched, the employee collection is fetched eagerly as well.
Try with an HQL query liken this one:
select distinct d
from Department d
left join fetch d.employees e
where e.gender = :gender

JPA2 : N+1 select even with a query with JOIN

In my application, I have an entity A with a list of entities B that should be fetched eagerly :
#Entity
public class A
{
...
/* #OrderBy("cValue.id ASC") */
#OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JoinColumn(name="A_ID", nullable=false)
private List<B> BEntries = new ArrayList<B>();
...
}
#Entity
public class B
{
...
#ManyToOne
#JoinColumn(name = "C_ID", nullable = false)
private C cValue;
...
}
In order to get the list of A, I was first doing this simple query :
CriteriaBuilder critBuilder = em.getCriteriaBuilder();
CriteriaQuery<A> critQuery = critBuilder.createQuery(A.class);
Root<A> critRoot = critQuery.from(A.class);
critQuery.select(critRoot);
But there I saw that Hibernate was doing N+1 select queries on the database, 1 on class A, and N on class B (where N is the number of tuples of A in DB).
I was very surprise that, for eager fetching, Hibernate was not directly doing a LEFT JOIN query.
So I first tried to use the annotation #Fetch(FetchMode.JOIN) of Hibernate, but it was not working as expected.
So I transformed my list query with the following additional instructions:
Join<A,B> joinAB = critRoot.join(A_.BEntries, JoinType.LEFT);
joinAB.join(B_.cValue, JoinType.LEFT);
Ok, now the resulting SQL query contains all the needed LEFT JOIN to build the full A object eagerly... but it's still doing the other N queries on B table!
I first thought it was coming from the #OrderBy annotation I put on the Bentries parameter, but even when removed, it's still doing N+1 selects instead of 1...
Any idea why it's behaving like this?... and even why it's not doing a LEFT JOIN by default on eagerly fetched collections in entities?

Categories

Resources