Table Join in Hibernate - java

I have something like two table User and Transaction. A user might have multiple transaction. But I need to join the User Table and the last transaction from Transaction table.
As of now what I did is I fetched the User data and along with it came the entire Transaction as a List but I need to limit the transaction to last 1 or final transaction only. This is based on the max value of id. It is getting slow when a lazy fetch is being fetched for a user with lots of transactions. how do I fix this to get the last transaction only.
As usual I have
User{
Blah blah;
List<Transaction> transaction;
}
and am doing
Criteria criteria = session.createCriteria(User.Class);
// Other Criteria applied here
criteria.list;

I think you need to join from Child to Parent:
select t
from Transaction t
inner join fetch t.user u
where t.id = ( select max(id) from Transaction )
Hibernate cannot return partial one(many)-to-many collections. You always get all associated children, because those are going to be managed as well.
Selecting the Child and fetching the Parent is much more appropriate for this task.

This should be done with SQL instead of criteria.
select {u.*}, {t.*} from user as u inner join (select user_id, max(id) as max_t_id from transaction group by user_id) on u.id = user_id inner join transaction as t on t.id = max_t_id
Read this doc: https://docs.jboss.org/hibernate/orm/3.3/reference/en/html/querysql.html
You should setEntity("u", User.class) and setEntity("t", Transaction.class).
From the list method, you will get a list of Object[]'s on which first item is user and second its last transaction. The users will have their complete list of transactions as collection, but this can be configured to be lazily loaded.

Related

JPA & Hibernate: Eager loading performing subsequent queries to fetch all data, instead of doing it in just one query

I have the following doubt. I would like to know why when using JPA and Hibernate, when performing an Eager loading in a ManyToOne or OneToMany relationship, it calls the DB in order to obtain the Entity information but additionally, produces subsequent queries to fetch every child.
On the other side, when using a query with JOIN FETCH it performs the query as I would expect it to be, taking the information all at once, since the fetchType is denoted as "EAGER".
Here it is a simple example:
I have the Class Student which has ManyToOne relationship with the Class Classroom.
#Entity
#Table(name = "STUDENT")
public class Student {
#ManyToOne(optional = true, fetch = FetchType.EAGER)
#JoinColumn(name = "ClassroomID")
private Classroom mainClass;
In the other side there is the class named Classroom as follows:
#Entity
public class Classroom {
#OneToMany(cascade = CascadeType.ALL, mappedBy = "mainClass", fetch = FetchType.EAGER)
private List<Student> studentsList;
When obtaining the Classroom object, it performs one query to obtain the information from itself and subsequent queries to obtain the information of each student contained in the studentsList for every classRoom object.
First query:
Hibernate:
/* SELECT
r
FROM
Classroom r
LEFT JOIN
r.classStudents */
select
classroom0_.id as id1_0_,
classroom0_.number as number2_0_
from
Classroom classroom0_
left outer join
STUDENT classstude1_
on classroom0_.id=classstude1_.ClassroomID
And then it performs the next query as many times as Students assigned to each classroom.
Hibernate:
/* load one-to-many com.hw.access.Classroom.classStudents */
select
classstude0_.ClassroomID as Classroo4_0_1_,
classstude0_.id as id1_1_1_,
classstude0_.id as id1_1_0_,
classstude0_.FIRST_NAME as FIRST_NA2_1_0_,
classstude0_.LAST_NAME as LAST_NAM3_1_0_,
classstude0_.ClassroomID as Classroo4_1_0_
from
STUDENT classstude0_
where
classstude0_.ClassroomID=?
The question is: Why doesn't it takes the Information all at once? Why doesn't it takes the information in just one query? As it is already performing the Join clause there.
Why just when adding Fetch explicitly in the query, it does what it is requested?
For example:
SELECT
r
FROM
Classroom r
LEFT JOIN FETCH
r.classStudents */
And then, the output query does takes all the information in just one query:
Hibernate:
select
classroom0_.id as id1_0_0_,
classstude1_.id as id1_1_1_,
classroom0_.number as number2_0_0_,
classstude1_.FIRST_NAME as FIRST_NA2_1_1_,
classstude1_.LAST_NAME as LAST_NAM3_1_1_,
classstude1_.ClassroomID as Classroo4_1_1_,
classstude1_.ClassroomID as Classroo4_0_0__,
classstude1_.id as id1_1_0__
from
Classroom classroom0_
left outer join
STUDENT classstude1_
on classroom0_.id=classstude1_.ClassroomID
As you have a OneToMany relationship from Classroom to Student, using a single query would cause the Classroom fields to be repeated for each line.
Now imagine you have a second OneToMany relationship from Classroom to, say Course; if, for a given Classroom you have N Students and M Courses, you would have a query returning N+M rows, each containing the same fields of class Classroom.
I found it described in https://vladmihalcea.com/eager-fetching-is-a-code-smell/
under EAGER fetching inconsistencies:
Both JPQL and Criteria queries default to select fetching, therefore issuing a secondary select for each individual EAGER association. The larger the associations’ number, the more additional individual SELECTS, the more it will affect our application performance.
Also, note that Hibernate similarly ignoreg fetching annotations for HQL queries:
https://developer.jboss.org/wiki/HibernateFAQ-AdvancedProblems#jive_content_id_Hibernate_ignores_my_outerjointrue_or_fetchjoin_setting_and_fetches_an_association_lazily_using_n1_selects
Hibernate ignores my outer-join="true" or fetch="join" setting and fetches an association lazily, using n+1 selects!
HQL queries always ignore the setting for outer-join or fetch="join" defined in mapping metadata. This setting applies only to associations fetched using get() or load(), Criteria queries, and graph navigation. If you need to enable eager fetching for a HQL query, use an explicit LEFT JOIN FETCH.
By default fetchtype is lazy, that mean if you dont ask for the List in your request Hibernate will not collect it.
In first request you ask for all attribute of Classroom r including the Student list so Hibernate will load them lazily (after finding out that you need them).
But when fetchtype is set to eager hibernate collect it even if you dont ask it.

Map sql query result to java object(in Non-Entity class) using spring jpa

I want to assign SQL query result to Java object which is in non-entity class.
My query is counting the number of records in Table A mapped to another Table B.
#Query(value="select count(a.id) from table1 a join table2 b on a.id=b.id group by a.id", nativeQuery=true)
Non-Entity class
public class Sample {
//assign query result to count variable
private long count;
// getters and setters
}
A and B are Entity class, I'm selecting specified columns of Entity A and B and including that columns in Sample.class and sending data as JSON on REST call.
Now my question is to assign count result to count variable.
Thanks in advance
How to do a JPQL query using a "group by" into a projection (Non-Entity-Class)?
Scenario you have two tables: User and User_Role and you want to know how many users in your system has the "public" role and how many have the "admin" role (Any other roles too if present).
For example: I want a query that will let me know there are two users that have "public" role and one user has the "admin" role.
Simplest Example:
#Query("SELECT ur.roleName, count(u.id) from User u left join u.userRole ur group by ur.roleName")
List<Object[]> getCounts();
In this case dealing with the result is more complicated then you typically would want. You would have to iterate over both the list and array of Objects.
Query into a projection Example:
#Query("SELECT new com.skjenco.hibernateSandbox.bean.GroupResultBean(ur.roleName, count(u.id)) from User u left join u.userRole ur group by ur.roleName")
List<GroupResultBean> getCountsToBean();
This would give you a List that is much better to work with.
Code Example: https://github.com/skjenco/hibernateSandbox/blob/master/src/test/java/com/skjenco/hibernateSandbox/repository/UserProjectionExampleTest.java

Complicated query with jparepository. Three tables join

I want to filter entities with next way:
get distinct 10 top disciplines, which are ordered by students count(attendance) for sessions between some period of time.
Discipline has List<Session> sessions;//bidirectional
Every Session has its own List<Student> students;//bidirectional
So, Student didn't know anything about Discipline, only via Session.
And I want to write using Spring JpaRepository something like:
List<Discipline> findTop10DisciplinesDistinctOrderBySessionsStudentsCountAndBySessionsBetween(Date from, Date to);
but there are some restrictions. It didn't work for me
No property count found for type Student! Traversed path:
Discipline.sessions.students.
Query looks like this(mysql):
select d.*
from session as ses
inner join
(select st.*
from student as st
inner join session as ses on ses.id = st.session_id
where ses.time > (curdate() - interval 30 day)
group by st.session_id
order by count(st.id) desc
limit 10) as st2
on ses.id = st2.session_id
inner join discipline as d on ses.discipline_id = d.id
But how to add this query to jpa repository?
Derived queries, those that get created from the method name are not suitable for use cases as yours. Even if it would work it would result in an unwieldy method name. Use an #Query with JPQL or SQL query instead.

Hibernate Criteria query doesnt query the many in one to many

I am trying to create a one to many query. the join seems to be working because the set gets populated but when i add a query to the set it doesn't seem to do anything
Here is one class:
#OneToMany(fetch=FetchType.LAZY, mappedBy="parent")
public Set<Child> getChildren() {
return this.children;
}
Here is the other
#ManyToOne(fetch=FetchType.LAZY)
#JoinColumn(name="PARENT_ID")
public Parent getParent() {
return this.parent;
}
and here is the not working query
Criteria criteria = session.createCriteria(Parent.class,"p")
.setFetchMode("p.children", FetchMode.JOIN)
.createAlias("p.children", "c")
.add(Restrictions.like("p.name", "%" + nameQuery + "%").ignoreCase())
.add(Restrictions.eq("c.gender", "boy"));
and this query gets me all my parents by name correnctly and their all children, but i only want the boy children and yet it gives me all the girls too. anyone see what i am doing wrong?
As per the docs,
Join fetching: Hibernate retrieves the associated instance or collection
in the same SELECT, using an OUTER JOIN.
In an outer join, you will get all the records in both the tables irrespective of the join condition, so change your fetching strategy to SELECT and it should fetch only the results matching the restriction. Also I'd recommend to enable batch fetching if you're not doing that already.

How to build HQL query, thats joins subtables marked LAZY, automatically?

I have some entity:
public class Album extends GenericAuditedEntity {
#OneToMany(fetch = FetchType.LAZY)
private Set<Item> itemSet = new HashSet<Item>();
}
And when i run HQL like this:
em.createQuery("select a from Album a").getResults()
it produses many SQL queries:
One for select data from Album's table. Smth like this: select .... from Album_table;
And one query for each fetched row, for selecting items. Smth like this:
select .... from Item_table iwhere i.Album_id = :Album_id;
But when i run em.createQuery("
select a.id, b.id
from Album a
left join Item i
").getResults()
it produses one SQL query. But it's result is list of some parameters, that i need put into the entities manually.
How can i build HQL with joins automatically and automatically put the results to the entities? Is it possible?
You need to use join fetch:
em.createQuery("select a.id, b.id from Album a left join fetch Item i ").getResults();
Note that there are certain side effects to that, described in detail the above link.
If you are using join fetch then you don't need the IDs, you can retrieve the Entity as Hibernate will also populate the association in it's first-level cache
em.createQuery("select a from Album a left join fetch a.itemSet").getResultList();
However if you are retrieving the IDs but want populated Objects/Entities then consider using a Constructor
em.createQuery("select new com.xxx.AlbumItem(a.id, b.id) from Album a left join fetch a.itemSet b").getResultList();
dont use lazy fetching. set fetch type to eager

Categories

Resources