Spring Data JPA sort column not in table/entity - spring-data-jpa

I have query like that:
#Query(value = "SELECT new com.domain.ActivityStatistic( " +
"adm.id, " +
"adm.fullName, " +
"COUNT(CASE WHEN (act.action = 'APPROVE') THEN act.action END) AS approved, " +
"max(act.actionTime) AS lastActionTime) " +
"FROM Actions act, Admins adm LEFT JOIN adm.group gr " +
"WHERE adm.id = act.adminId AND act.actionTime BETWEEN ?1 AND ?2 AND gr.id = ?3 " +
"GROUP BY adm.id")
Page<ActivityStatistic> getActivityStatistics(LocalDateTime from,
LocalDateTime to,
long groupId,
Pageable pageable);
How can I sort it by the new field that I created: lastActionTime, approved ?
I can run it by native sql in postgresql: pgadmin. But in jpa, when I using sort with field name is approved, it auto become act.approved in JPA query.
I used to read this post Spring Data and how to sort by a column not in an Entity but it not help.

You can't apply in JPQL on a table column that isn't mapped to a property of an entity.
The reason for this is that JPA including JPQL operates on these entities.
Use a native query instead.

Related

Union in JPA and using Alias

I have 2 entites, PERSON and PEOPLE
PERSON has columns namely -> FN, LN
PEOPLE has columns -> FIRST, LAST
I want to fetch the union of both the tables together. How do I do it in JPA.
I have used the below way:
Created a new DTO -> Human having 2 fields FIRSTNAME and LASTNAME (case sensitive)
#Query(value=" SELECT FIRSTNAME, LASTNAME FROM "+
"( SELECT "+
" P.FN AS FIRSTNAME, "+
" P.LN AS LASTNAME " +
" FROM PERSON P"+
" UNION "+
" SELECT "+
" A.FIRST AS FIRSTNAME, "+
" A.LAST AS LASTNAME ' +
" FROM PEOPLE A"+
")", nativeQuery = true)
Pageable<Human> getEntireHumanRace() {
....
}
The SQL runs fine, but the ORM always forms a malformed SQL
such as "Syntax error in SQL statement SELECT COUNT(P) FROM PERSON P ...."
InvalidDataAccessResourceUsageException: could not prepare statement
Is there any suggestion on what can be done? Why does it put the count in front of the generated query?
Appreciate in advance.
Why does it put the count in front of the generated query?
Because you are trying to get data with pagination (Pageable). So for total element count Count query executing.
Is there any suggestion on what can be done?
You are using class-based projection and use List<Human>
#Query(value="SELECT FIRSTNAME, LASTNAME FROM ...")
List<Human> getEntireHumanRace();

Transforming complex postgres query into JPQL for Spring Hibernate repository

I have the following Postgres SQL query, in order to get alerts with the latest date:
SELECT latest_alerts.subject_id,
latest_alerts.alertconfiguration_id_id,
latest_alerts.alert_level,
latest_alerts.maxdate
FROM (SELECT subject_id,
alertconfiguration_id_id,
alert_level,
Max(date) AS maxdate
FROM alert
WHERE subject_id IN ( 'da157532-8de5-4c0c-8608-d924e670d5db', '63b99886-77c8-4784-b8f0-7ff5310f1272' )
AND alertconfiguration_id_id IN (
'6feb6b8b-6b96-4d5d-ac58-713b3cd637a0'
)
GROUP BY subject_id,
alertconfiguration_id_id,
alert_level) AS latest_alerts
INNER JOIN alert
ON alert.date = latest_alerts.maxdate
AND alert.subject_id = latest_alerts.subject_id
AND alert.alertconfiguration_id_id =
latest_alerts.alertconfiguration_id_id
AND alert.alert_level IN ( 'WARNING' )
ORDER BY latest_alerts.maxdate DESC;
This runs well on the postgres database generated by Hibernate. Note the odd id_id construction is because of an embedded key.
But I'm struggling to transform this into a JPA/JPQL query that I can use in a Spring Boot application. So far I have this:
#Query("SELECT" +
" latest_alerts.subject," +
" latest_alerts.alertConfiguration," +
" latest_alerts.alertLevel," +
" latest_alerts.max_date" +
"FROM" +
" (SELECT" +
" subject," +
" alertConfiguration," +
" alertLevel," +
" MAX(date) AS max_date" +
" FROM" +
" alert" +
" WHERE" +
" subject.id IN (:subjectIds) AND alertConfiguration.id.id IN (:alertConfigurationIds)" +
" GROUP BY" +
" subject, alertConfiguration, alertLevel) AS latest_alerts" +
" INNER JOIN" +
" alert" +
" ON" +
" alert.date = latest_alerts.max_date" +
" AND alert.subject = latest_alerts.subject" +
" AND alert.alertConfiguration = latest_alerts.alertConfiguration" +
" AND alert.alertlevel IN (:alertLevels)" +
" ORDER BY latest_alerts.date DESC")
Page<Alert> findLatest(#Param("subjectIds") List<UUID> subjectIds,
#Param("alertConfigurationIds") List<UUID> alertConfigurationIds,
#Param("alertLevels") List<AlertLevel> alertLevels,
Pageable pageable);
But Hibernate doesn't understand what to do with this to the point where it actually throws a nullpointer while parsing this query.
Caused by: java.lang.IllegalArgumentException: Validation failed for query for method public abstract org.springframework.data.domain.Page ournamespace.sense.repository.AlertRepository.findLatest(java.util.List,java.util.List,java.util.List,org.springframework.data.domain.Pageable)!
at org.springframework.data.jpa.repository.query.SimpleJpaQuery.validateQuery(SimpleJpaQuery.java:93)
at org.springframework.data.jpa.repository.query.SimpleJpaQuery.<init>(SimpleJpaQuery.java:63)
... many more
Caused by: java.lang.NullPointerException
at org.hibernate.hql.internal.antlr.HqlBaseParser.identPrimary(HqlBaseParser.java:4355)
at org.hibernate.hql.internal.antlr.HqlBaseParser.primaryExpression(HqlBaseParser.java:993)
at org.hibernate.hql.internal.antlr.HqlBaseParser.atom(HqlBaseParser.java:3549)
Any idea if this kind of query is even possible? With a nullpointer thrown by Hibernate it's a bit hard to see what part of the query is the problem.
Cannot do it with Hibernate. From the documentation:
Note that HQL subqueries can occur only in the select or where
clauses.
But you can use native query:
#Query(
value = "SELECT latest_alerts.subject_id,
latest_alerts.alertconfiguration_id_id,
latest_alerts.alert_level,
latest_alerts.maxdate ...
FROM ...",
nativeQuery = true)
Page<Alert> findLatest(#Param("subjectIds") List<UUID> subjectIds,
#Param("alertConfigurationIds") List<UUID> alertConfigurationIds,
#Param("alertLevels") List<AlertLevel> alertLevels,
Pageable pageable);

Spring Data JPA - Many columns in where clause

I need to determine if an entity has already been persisted. Unfortuantely, I do not have the id, but I can determine that the entity is already persisted if the value of six other fields of the entity match a persisted entity. I'm using Spring JPA repositories and know that I can do the following:
Test findByField1AndField2And...(String field1, String field2,...)
Is there a way to do something similar to:
#Query("SELECT t "
+ "FROM Test t "
+ "WHERE "
+ "t.field1 = :testWithSomeFieldsPopulated.field1 and "
+ "t.field2 = :testWithSomeFieldsPopulated.field2 and ..." )
Test findByTest(#Param("testWithSomeFieldsPopulated") Test testWithSomeFieldsPopulated)
If you're using Spring Data JPA 1.7.0 or above, then you can accomplish this by using SpEL in your #Query definition. So something like:
#Query("SELECT t "
+ "FROM Test t "
+ "WHERE "
+ "t.field1 = :#{#testWithSomeFieldsPopulated.field1} and "
+ "t.field2 = :#{#testWithSomeFieldsPopulated.field2} and ..." )
Test findByTest(#Param("testWithSomeFieldsPopulated") Test testWithSomeFieldsPopulated)

Incomplete data while fetching through Named Query JPA2.0

I have an entity named ReqestDetail and done association and mapping as below snippet:
#ManyToOne
#JoinColumn(name="STATUS_C")
private StatusCode statusCode;
//bi-directional many-to-one association to RequestAddressDetail
#OneToMany(mappedBy="requestDetail")
private List<RequestAddressDetail> addressDetails;
I have created below Named Query to fetch the details on the basis of address.
TypedQuery<RequestDetail> query = em.createQuery( "SELECT t FROM RequestDetail t" +
" LEFT JOIN t.employeeDetail e " +
" LEFT JOIN t.addressDetails ao " +
" LEFT JOIN t.addressDetails ad " +
" WHERE (e.employeeI IN ( :employeeIds ) ) AND " +
" (ao.postI = :postO AND ao.addressTypeC = :addressTypeO " +
" AND ad.postI = :PostD AND ad.addressTypeC = :addressTypeD )" ,
RequestDetail.class );
However, when I call query.getResultList(), it is only giving me data of RequestDetail Entity but not of the mapping joins I have mentioned in that entity.
Please advise...
why do you think it should give you some joined objects? Your candidate for the query is RequestDetail, so it gives that. If you did a JOIN FETCH then you can fetch other fields, but you haven't done that (so change those LEFT JOIN to LEFT JOIN FETCH) - a JOIN (without FETCH) is simply a convenience way of forming the query.. Alternatively you could set the entity graph to fetch, or set the fetch on the field itself if you wanted those fetching

JPQL "DISTINCT" returns only one result

I am confused by DISTINCT in JPQL. I have two JPQL queries identical except for "DISTINCT" in one of them:
String getObjectsForFlow =
"SELECT " +
" se.componentID " +
"FROM " +
" StatisticsEvent se " +
"WHERE " +
" se.serverID IS NOT NULL " +
" AND se.flowID = :uuid " +
" AND se.componentID IS NOT NULL " +
"ORDER BY " +
" se.timeStamp desc ";
String getObjectsForFlowDistinct =
"SELECT DISTINCT " +
" se.componentID " +
"FROM " +
" StatisticsEvent se " +
"WHERE " +
" se.serverID IS NOT NULL " +
" AND se.flowID = :uuid " +
" AND se.componentID IS NOT NULL " +
"ORDER BY " +
" se.timeStamp desc ";
I run a little code to get the results from each query and dump them to stdout, and I get many rows with some duplicates for non-distinct, but for distinct I get only one row which is part of the non-distinct list.
NOT DISTINCT
::: 01e2e915-35c1-6cf0-9d0e-14109fdb7235
::: 01e2e915-35c1-6cf0-9d0e-14109fdb7235
::: 01e2e915-35d9-afe0-9d0e-14109fdb7235
::: 01e2e915-35d9-afe0-9d0e-14109fdb7235
::: 01e2e915-35bd-c370-9d0e-14109fdb7235
::: 01e2e915-35bd-c370-9d0e-14109fdb7235
::: 01e2e915-35aa-1460-9d0e-14109fdb7235
::: 01e2e915-35d1-2460-9d0e-14109fdb7235
::: 01e2e915-35e1-7810-9d0e-14109fdb7235
::: 01e2e915-35e1-7810-9d0e-14109fdb7235
::: 01e2e915-35d0-12f0-9d0e-14109fdb7235
::: 01e2e915-35b0-cb20-9d0e-14109fdb7235
::: 01e2e915-35a8-66b0-9d0e-14109fdb7235
::: 01e2e915-35a8-66b0-9d0e-14109fdb7235
::: 01e2e915-35e2-6270-9d0e-14109fdb7235
::: 01e2e915-357f-33d0-9d0e-14109fdb7235
DISTINCT
::: 01e2e915-35e2-6270-9d0e-14109fdb7235
Where are the other entries? I would expect a DISTINCT list containing eleven (I think) entries.
Double check equals() method on your StatisticsEvent entity class. Maybe those semantically different values returns same when equals() is called hence producing this behavior
The problem was the "ORDER BY se.timeStamp" clause. To fulfill the request, JPQL added the ORDER BY field to the SELECT DISTINCT clause.
This is like a border case in the interplay between JPQL and SQL. The JPQL syntax clearly applies the DISTINCT modifier only to se.componentID, but when translated into SQL the ORDER BY field gets inserted.
I am surprised that the ORDER BY field had to be selected at all. Some databases can return a data set ORDERed by a field not in the SELECTion. Oracle can do so. My underlying database is Derby -- could this be a limitation in Derby?
Oracle does not support SELECT DISTINCT with an order by unless the order by columns are in the SELECT. Not sure if any databases do. It will work in Oracle if the DISTINCT is not required (does not run because rows are unique), but if it needs to run you will get an error.
You will get, "ORA-01791: not a SELECTed expression"
If you are using EclipseLink this functionality is controlled by the DatabasPlatform method,
shouldSelectDistinctIncludeOrderBy()
You can extend your platform to return false if your database does not require this.
Still, I don't see how adding the TIMESTAMP will change the query results?
Both queries are incorrect JPQL queries, because ORDER BY clause refers to the item that is not on select list. JPA 2.0 specification contains example that matches to this case:
The following two queries are not legal because the orderby_item is
not reflected in the SELECT clause of the query.
SELECT p.product_name
FROM Order o JOIN o.lineItems l JOIN l.product p JOIN o.customer c
WHERE c.lastname = ‘Smith’ AND c.firstname = ‘John’
ORDER BY p.price
SELECT p.product_name
FROM Order o, IN(o.lineItems) l JOIN o.customer c
WHERE c.lastname = ‘Smith’ AND c.firstname = ‘John’
ORDER BY
o.quantity
Of course it would be nicer if if implementation could give clear error message instead of trying to guess what is expected result of incorrect query.