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.
Related
In Blaze Persistence with querydsl integration, it supports subquery in join statement. So I wonder how to combine projects with CTE entity in a join-subquery condition.
let's say I have two entities named person and pet. They are defined as this:
Person
Pet
id
id
name
personId
age
Here is my test code:
blazeJPAQueryFactory.selectFrom(QPerson.person)
.leftJoin(
JPQLNextExpressions
.select(Projections.bean(
PersonPetCte.class,
QPet.pet.personId.as(QPersonPetCte.personPetCte.personId),
QPet.pet.age.sum().as(QPersonPetCte.personPetCte.ageSum)
))
.from(QPet.pet)
.groupBy(QPet.pet.personId),
QPersonPetCte.personPetCte
)
.on(QPersonPetCte.personPetCte.personId.eq(QPerson.person.id))
.where(QPersonPetCte.personPetCte.ageSum.gt(30))
.fetch();
where PersonPetCte is declared as below (getters and stters omitted for brevity):
#CTE
#Entity
public class PersonPetCte {
#Id
Long personId;
Long ageSum;
}
run this test results in the following exception:
java.lang.UnsupportedOperationException: Select statement should be bound to any CTE attribute
Basically I want to achieve this: get all persons whose sum of their pet age is above 30.
I am trying to avoid string-hardcoded constant as much as possible, which is why I come across the idea of using CTE.
Please tell me if I am totally conceptually wrong or missing someting.
You almost got the syntax right, but Projections.bean does not provide enough metadata to deduce the mapping for the CTE.
Instead you have to do:
new BlazeJPAQuery<>()
.from(QPet.pet)
.groupBy(QPet.pet.personId)
.bind(QPersonPetCte.personPetCte.personId, QPet.pet.personId)
.bind(QPersonPetCte.personPetCte.ageSum, QPet.pet.age.sum())
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
I've been trying to figure out how to take an input of a list of enums for my sql query. This is what it looks like:
#Query(value = "Select * from employees where city in :cities", nativeQuery = true)
List<Employee> findByCities(#Param("cities") List<City> cities);
I understand that for simple queries we can rely on the JPA Criteria API but I want to know if I can actually do it this way instead. Because if so, i can create more complicated queries (such as joins with another table) if I could have this flexibility of specifying the list.
Yes spring-data-jpa's #Query can take a list of enums.
This is my repository method
#Query("Select u from User u where u.userType in :types")
List<User> findByType(#Param("types") List<UserType> types);
This is my repository call
userRepository.findByType(Arrays.asList(AppConstant.UserType.PRINCIPLE)))
And this is the query logs
SELECT user0_.id AS id1_12_,
user0_.date_created AS date_created2_12_,
...
...
FROM users user0_
WHERE user0_.user_type IN ( ? )
Hope this helps.
PS: I tested this in my local machine with positive results.
Update 1
The same doesn't work with nativeQuery=true. I tested it on my local system and it doesn't seem to be working with native queries. However with JPQL it works fine as mentioned in the above answer.
May be this answer will help.
Let's say I have a List of entities:
List<SomeEntity> myEntities = new ArrayList<>();
SomeEntity.java:
#Entity
#Table(name = "entity_table")
public class SomeEntity{
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
private int score;
public SomeEntity() {}
public SomeEntity(long id, int score) {
this.id = id;
this.score = score;
}
MyEntityRepository.java:
#Repository
public interface MyEntityRepository extends JpaRepository<SomeEntity, Long> {
List<SomeEntity> findAllByScoreGreaterThan(int Score);
}
So when I run:
myEntityRepository.findAllByScoreGreaterThan(10);
Then Hibernate will load all of the records in the table into memory for me.
There are millions of records, so I don't want that. Then, in order to intersect, I need to compare each record in the result set to my List.
In native MySQL, what I would have done in this situation is:
create a temporary table and insert into it the entities' ids from the List.
join this temporary table with the "entity_table", use the score filter and then only pull the entities that are relevant to me (the ones that were in the list in the first place).
This way I gain a big performance increase, avoid any OutOfMemoryErrors and have the machine of the database do most of the work.
Is there a way to achieve such an outcome with Spring Data JPA's query methods (with hibernate as the JPA provider)? I couldn't find in the documentation or in SO any such use case.
I understand you have a set of entity_table identifiers and you want to find each entity_table whose identifier is in that subset and whose score is greater than a given score.
So the obvious question is: how did you arrive to the initial subset of entity_tables and couldn't you just add the criteria of that query to your query that also checks for "score is greater than x"?
But if we ignore that, I think there's two possible solutions. If the list of some_entity identifiers is small (what exactly is "small" depends on your database), you could just use an IN clause and define your method as:
List<SomeEntity> findByScoreGreaterThanAndIdIn(int score, Set<Long) ids)
If the number of identifiers is too large to fit in an IN clause (or you're worried about the performance of using an IN clause) and you need to use a temporary table, the recipe would be:
Create an entity that maps to your temporary table. Create a Spring Data JPA repository for it:
class TempEntity {
#Id
private Long entityId;
}
interface TempEntityRepository extends JpaRepository<TempEntity,Long> { }
Use its save method to save all the entity identifiers into the temporary table. As long as you enable insert batching this should perform all right -- how to enable differs per database and JPA provider, but for Hibernate at the very least set the hibernate.jdbc.batch_size Hibernate property to a sufficiently large value. Also flush() and clear() your entityManager regularly or all your temp table entities will accumulate in the persistence context and you'll still run out of memory. Something along the lines of:
int count = 0;
for (SomeEntity someEntity : myEntities) {
tempEntityRepository.save(new TempEntity(someEntity.getId());
if (++count == 1000) {
entityManager.flush();
entityManager.clear();
}
}
Add a find method to your SomeEntityRepository that runs a native query that does the select on entity_table and joins to the temp table:
#Query("SELECT id, score FROM entity_table t INNER JOIN temp_table tt ON t.id = tt.id WHERE t.score > ?1", nativeQuery = true)
List<SomeEntity> findByScoreGreaterThan(int score);
Make sure you run both methods in the same transaction, so create a method in a #Service class that you annotate with #Transactional(Propagation.REQUIRES_NEW) that calls both repository methods in succession. Otherwise your temp table's contents will be gone by the time the SELECT query runs and you'll get zero results.
You might be able to avoid native queries by having your temp table entity have a #ManyToOne to SomeEntity since then you can join in JPQL; I'm just not sure if you'll be able to avoid actually loading the SomeEntitys to insert them in that case (or if creating a new SomeEntity with just an ID would work). But since you say you already have a list of SomeEntity that's perhaps not a problem.
I need something similar myself, so will amend my answer as I get a working version of this.
You can:
1) Make a paginated native query via JPA (remember to add an order clause to it) and process a fixed amount of records
2) Use a StatelessSession (see the documentation)
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.