Hibernate criteria filter by related entity - java

I have an entity which has a collection of related entities.
public class Student{
#OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JoinColumn(name = "COURSE_STUDENT_ID" , insertable=false,updatable=false)
private Set <Course> courses;
I want to filter students by course names and student class id. For now I have worked it out how to filter by class id but I have no idea how to filter by courseId given that Student entity has a set of courses and the tables are related. I have read some articles but no code matches the one I have already.
CriteriaBuilder criteriaBuilder = persistenceStore.createCriteriaBuilder();
CriteriaQuery<Object> criteria = criteriaBuilder.createQuery();
Root<Student> root = criteria.from(Student.class);
List<Predicate> params = new ArrayList<Predicate>();
params.add(criteriaBuilder.equal(root.get("classId"),classId));
Predicate[] predicates = new Predicate[params.size()];
params.toArray(predicates);
criteria.select(root);
criteria.where(criteriaBuilder.and(predicates));
Query query = persistenceStore.createQuery(criteria);
List<Student> resultList = query.getResultList();

First of all, there is an error in your Entity: the JoinColumn annotation applies to the entity on the inverse side of the relationship, Course in your case.
So, if Course entity has a property student, Student has a property like:
#OneToMany(cascade = CascadeType.ALL, mappedBy = "student")
private Set<Course> courses;
and in Course entity you have (here it also states that in the db the table course has a field called "student":
#JoinColumn(name = "student", referencedColumnName = "id")
#ManyToOne(optional = false)
private Student student;
Read this link for an entry-level explaination on how to map entity relationships.
Regarding the Criteria Query, since you want to retrieve a List of StudentS, you can define your CriteriaQuery in a more type safe way:
CriteriaQuery<Student> criteria = criteriaBuilder.createQuery(Student.class);
Regarding the question, you have to join the tables in this way:
SetJoin<Student, Course> courses = root.join("courses");
or, using MetaModel:
SetJoin<Student, Course> courses = root.join(Student_.courses);
(had the OneToMany property been defined as a List or a Collection, you'd have had to use the corresponding ListJoin and CollectionJoin classes).
on the courses you can apply the desired Predicate conditions (supposing that Course entity has a string property called courseName):
Predicate p = criteriaBuilder.equal(courses.get("courseName"), "name-to-look-for");
or, using Metamodel:
Predicate p = criteriaBuilder.equal(courses.get(Course_.courseName), "name-to-look-for");
Finally, in order to concatenate correctly a list of predicates, you can use (at least) two techniques:
Predicate p1 = ...;
Predicate p2 = ...;
criteria.where(criteriaBuilder.and(p1, p2));
or
List<Predicate> conditions = new ArrayList<Predicate> ();
conditions.add(p1);
conditions.add(p2);
criteria.where(conditions.toArray(new Predicate[] {}));
See also this excellent article.

type mismatch: cannot convert from cascadetype to cascadetype[]
Answer is:
import javax.persistence.CascadeType;
import this line

Related

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

Hibernate entity with more than two OneToMany fields

I have the following hibernate entity:
#Entity
public class Customer {
#OneToMany(mappedBy = "customer", cascade = CascadeType.ALL, fetch = FetchType.EAGER)
private List<Address> addresses = new ArrayList<>();
#OneToMany(mappedBy = "customer", cascade = CascadeType.ALL, fetch = FetchType.EAGER)
private List<Contact> contacts = new ArrayList<>();
#OneToMany(mappedBy = "customer", cascade = CascadeType.ALL, fetch = FetchType.EAGER)
private List<Name> names = new ArrayList<>();
// Many more, including a primary key
}
Starting the application, I got the following exception:
org.hibernate.loader.MultipleBagFetchException: cannot simultaneously fetch multiple bags
If I remove one arbitary OneToMany association, or if I add a #Fetch(value = FetchMode.JOIN) to an arbitary OneToMany association, everything works fine.
Is this a hibernate bug, a hibernate limitation, or is there anything wrong with my entity? TIA!
It is not a bug. It is because of Hibernate uses a one select with a join to fetch all the data. Hibernate can join three and more tables, but the result of joining will have duplicates of, for example, Address columns. Hibernate needs to remove duplicates — it is a reason why Set works.
Possible workarounds:
Use Set<Address> instead of List<Address>. You should use Set for all collections.
Use lazy fetching fetch = FetchType.LAZY
Use #Fetch(value = FetchMode.SUBSELECT)
Some additional reading:
A beginner’s guide to Hibernate Set and List behavior
Hibernate does not return distinct results for a query with outer join fetching enabled for a collection (even if I use the distinct keyword)?
Try to use:
#OneToMany
#LazyCollection(value=LazyCollectionOption.TRUE)
private Collection<Name> names = new ArrayList<>();

Hibernate criteria query for parent-childs relationship where parent is null (get root entries)

I have a simple parent-child relationship witin the same class. I like to get all instances where no parent exists (= NULL).Means the "root" categories.
I need to use criteria query and can not use HQL or SQL. Due an issue with Relationship and "isNull" Operation it seems that the criteria query can not be "normally" be used as expected and require some trick. Anybody has any hints which I can try?
#Entity
public class Category {
#ManyToOne(fetch=FetchType.EAGER)
#JoinColumn(name = "parent_id")
private Category parent;
#OneToMany(cascade=CascadeType.ALL, mappedBy="parent", fetch=FetchType.EAGER)
#OrderBy(value="pos")
private List<Category> childs = new ArrayList<Category>();
...
}
If I use the following criteria query construct I get strange result back. I get multiple times the same category instances instead just the "root" ones.
Criteria c = session.createCriteria(Category.class, "c");
c.add(Restrictions.isNull("c.parent"));
List<Category> rootCategories = c.list();
for(Category category : rootCategories) {
logger.info(category);
}
I tried already some constructs with aliases but no luck. Left Join or inner join will not work ;(
Criteria c = session.createCriteria(Category.class, "c");
c.createAlias("c.parent", "p", JoinType.RIGHT_OUTER_JOIN);
c.add(Restrictions.isNull("p.id"));
List<Category> rootCategories = c.list();
for(Category category : rootCategories) {
logger.info(category);
}
In case I use HQL (which isunfortunately no option for my issue) it works perfectly
List<Category> rootCategories = session.createQuery("FROM Category c where c.parent is null order by c.pos").list();
Thank you for any hints in advance

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