Complicated query with jparepository. Three tables join - java

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.

Related

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

Table Join in Hibernate

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.

getting result set into DTO with native SQL Query in Hibernate

I have a query like below
select f.id, s.name, ss.name
from first f
left join second s on f.id = s.id
left join second ss on f.sId = ss.id
If I could use HQL, I would have used HQL constructor syntax to directly populate DTO with the result set.
But, since hibernate doesn't allow left join without having an association in place I have to use the Native SQL Query.
Currently I am looping through the result set in JDBC style and populating DTO objects.
Is there any simpler way to achieve it?
You could maybe use a result transformer. Quoting Hibernate 3.2: Transformers for HQL and SQL:
SQL Transformers
With native sql returning non-entity
beans or Map's is often more useful
instead of basic Object[]. With
result transformers that is now
possible.
List resultWithAliasedBean = s.createSQLQuery(
"SELECT st.name as studentName, co.description as courseDescription " +
"FROM Enrolment e " +
"INNER JOIN Student st on e.studentId=st.studentId " +
"INNER JOIN Course co on e.courseCode=co.courseCode")
.addScalar("studentName")
.addScalar("courseDescription")
.setResultTransformer( Transformers.aliasToBean(StudentDTO.class))
.list();
StudentDTO dto =(StudentDTO) resultWithAliasedBean.get(0);
Tip: the addScalar() calls were
required on HSQLDB to make it match a
property name since it returns column
names in all uppercase (e.g.
"STUDENTNAME"). This could also be
solved with a custom transformer that
search the property names instead of
using exact match - maybe we should
provide a fuzzyAliasToBean() method ;)
References
Hibernate Reference Guide
16.1.5. Returning non-managed entities
Hibernate's Blog
Hibernate 3.2: Transformers for HQL and SQL

Java-Hibernate-Newbie: How do I acces the values from this list?

I have this class mapped
#Entity
#Table(name = "USERS")
public class User {
private long id;
private String userName;
}
and I make a query:
Query query = session.createQuery("select id, userName, count(userName) from User order by count(userName) desc");
return query.list();
How can I access the values returned by the query?
I mean, how should I treat the query.list()? As a User or what?
To strictly answer your question, queries that specify a property of a class in the select clause (and optionally call aggregate functions) return "scalar" results i.e. a Object[] (or a List<Object[]>). See 10.4.1.3. Scalar results.
But your current query doesn't work. You'll need something like this:
select u.userName, count(u.userName)
from User2633514 u
group by u.userName
order by count(u.userName) desc
I'm not sure how Hibernate handles aggregates and counts, but I'm not sure if your query is going to work at all. You're trying to select a aggregate (i.e. the "count(userName)"), but you don't have a "group by" clause for userName.
If the query does in fact work, and Hibernate can figure out what to do with it, the results you get back will most likely be a raw Object[], because Hibernate will not be able to map your "count(userName)" data into any field on your mapped objects.
Overall, when you get into using aggregates in queries, Hibernate can get a little more tricky, since you're no longer mapping tables/columns directly into classes/fields. It might be a good idea to read up more on how to do aggregates in Hibernate, from their documentation.

Hibernate Subquery Question

This should be a simple one I hope.
I have an invoice and that invoice has a list of payments.
Using the Criteria API I am trying to return a list of invoices and their payment total. So, in SQL I want something like this:
SELECT i.*, (SELECT SUM(PMT_AMOUNT) FROM INVOICE_PAYMENTS p WHERE p.INVOICE = i.INVOICE) FROM INVOICES i
I can't for the life of me figure out how to achieve this with the Criteria API. Doing something like:
Criteria crit = session.createCriteria(Invoice.class)
criteria.setProjection(Projections.projectionList()
.add(Projections.sum("payements.paymentAmount").as("paymentTotal"))
Simply returns 1 row with the projected payment total for all invoices, which is actually what you'd expect, but this is as close as I can get.
Any help is greatly appreciated.
There is a way with Criteria to return a list of Invoices along with the total payments for that invoice.
In theory, the answer is that you can use a grouping property on a projection query to group the result into total payment by invoice. The second part is that you could use a transient "totalPayment" value on the Invoice and select the projection into the Invoice structure using a transformer. This would be easier than dealing with an ArrayList of different properties but would depend on what you needed to use the results for.
To demonstrate this, here is the important part of a small Invoice class:
public class Invoice{
private String name;
#Transient private int totalPayments;
#OneToMany Set<Payment> payments = new HashSet<Payment>();
// getters and setters
...
}
Then this is the criteria that you could use
Criteria criteria = session.createCriteria(Invoice.class)
.createAlias("payments", "pay")
.setProjection(Projections.projectionList()
.add(Projections.groupProperty("id"))
.add(Projections.property("id"), "id")
.add(Projections.property("name"), "name")
.add(Projections.sum("pay.total").as("totalPayments")))
.setResultTransformer(Transformers.aliasToBean(Invoice.class));
List<Invoice> projected = criteria.list();
And this is the sql that is generated
Hibernate:
select this_.id as y0_,
this_.id as y1_,
this_.name as y2_,
sum(pay1_.total) as y3_
from invoice this_
inner join invoice_payment payments3_ on this_.id=payments3_.invoice_id
inner join payment pay1_ on payments3_.payments_id=pay1_.id
group by this_.id
I'm pretty sure you can't return entities in a Projection.
There are two possibles:
Run two criteria queries, one for the actual invoices and one for there totals
Use HQL to perform the query
I haven't tested this but it should go something like:
select i, (select sum(p.amount) from InvoicePayments p where p.invoice = i.invoice) from Invoice i
Will have to wait until tomorrow, I have a very similar data structure at work I should be able to test this then.
You can also use #Formula for the totalPayments field. Disadvantage is, that the "sum" is computed every time you load the entity. So, you may use LAZY #Formula - do build time enhancement or Pawel Kepka's trick: http://justonjava.blogspot.com/2010/09/lazy-one-to-one-and-one-to-many.html Disadvantage is, that is you have more LAZY #Fromula and you hit just one of them, all of them are loaded. Another solution may be to use #MappedSuperclass and more subclasses. Each subclass may have different #Formula fields. And one more solution beside DB view: Hibernate #Subselect.

Categories

Resources