Spring Data JPA - Many columns in where clause - spring-data-jpa

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)

Related

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 sort column not in table/entity

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.

Sql column alias not working in Ebeans rawsql

I am facing strange issue with raw SQLs, and I need some help to figure out the best way to fix it. I could, of course, add the columnMappings, but I want to make sure it's not because I am doing something wrong.
Play Framework APP
Ebeans ORM
Postgresql 9.,4
Executing the following RawSQL against a Postgresql database fails if I don't define columnMappings, although I have an alias defined:
String sql
= " Select date_trunc('day', end_time) as theDate, "
+ " count(*) as value "
+ " From ebay_item "
+ " group by date_trunc('day', end_time) ";
RawSql rawSql =
RawSqlBuilder
.parse(sql)
.create();
Error:
016-03-25 12:05:15,303 ERROR m.c.a.e.s.p.SimpleDBPromiseService - Error executing named query
javax.persistence.PersistenceException: Property [dateTrunc('day'] not found on models.com.abiesolano.ebay.sqlpojos.RangedDateCounter
If I switch to an H2 database:
String sql
= " SELECT trunc(end_time) as theDate, "
+ " count(*) as value "
+ " From ebay_item "
+ " Group by trunc(end_time)";
RawSql rawSql =
RawSqlBuilder
.parse(sql)
.create();
it works no problems.
Any suggestion would be really appreciated.
I don't think mysql likes to group or order by functions, you should use your alias "theDate" instead.
Note that if you're mapping to a bean object, the alias must be a #Transient property of your bean to be mapped by Ebean (otherwise, you'll get an unknown property error).

JPQL Average Time Difference

I have order bills and good receive bills. Any bill can have multiple bill items. One order bill item may have none or more receive bill items.
I want to calculate the lead time, which is the average time difference between the order and the good receipt in a JPQL (Java Persistance Query Language) query in days. I use EclipseLink 2.4 as the persistence provider.
This is the query I attempted. Is there any way I can take the average lead time using JPQL ?
jpql = "Select avg(b.createdAt - rb.createdAt) "
+ " from BillItem bi "
+ " join bi.bill b "
+ " joib bi.referanceBillItem rbi "
+ " join rbi.bill rb "
+ " where b.billType in :bts "
+ " and rb.billType in :rbts "
+ " and bi.item=:amp "
+ " and b.createdAt between :fd and :td "
+ " ";

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.