Spring Boot: JPA QueryDSL: How to Make a Named Delete? - java

In the QueryDSL repository, I can make a named query in this way:
public interface HistoricalDataRepository
extends PagingAndSortingRepository<HistoricalData, Long>,
QueryDslPredicateExecutor<HistoricalData> {
List<HistoricalData> findAll(Predicate predicate);
HistoricalData findByKeyAndDate(String key, String date);
#Query(value = "SELECT * FROM historical_data h WHERE "
+ " h.key = :key "
+ " AND h.date <= :date "
+ " order by date desc"
+ " limit 1"
, nativeQuery = true)
HistoricalData myFindByKeyLowerOrEqualToDate(
#Param("key") String key
, #Param("date") Date date
);
How can I perform a
DELETE FROM HISTORICAL_DATA;
If I use the same syntax as above (with the #Queryannotation), I get an exception:
org.springframework.orm.jpa.JpaSystemException:
could not extract ResultSet; nested exception is
org.hibernate.exception.GenericJDBCException: could not extract ResultSet
Background: I do not want to use a
historicalDataRepository.deleteAll();
as this has a VERY low performance.
EDIT:
My syntax would look like the following:
#Query(value = "DELETE FROM HISTORICAL_DATA", nativeQuery = true)
void myDeleteAll();

Looks like, you are combining both JPA query as well as native query. For the syntax you have given, you have to specify it as native as shown below
#Query(value = "DELETE FROM HISTORICAL_DATA", nativeQuery = true)

Related

JPA parameter outside a where clause

I would like to know if the is a way to build a JPA query with parameter outside of the where clause. This query works fine in my database manager.
There is my query :
#Query(value = "SELECT q.quote, q.author, q.display_at, count(ql.*) AS like, (SELECT '{:userUUID}'::uuid[] && ARRAY_AGG(ql.user_uuid)::uuid[]) AS liked " +
"FROM quotes q " +
"LEFT JOIN quotes_likes ql ON ql.quote_uuid = q.uuid " +
"WHERE display_at = :date " +
"GROUP BY q.quote, q.author, q.display_at;", nativeQuery = true)
Optional<QuoteOfTheDay> getQuoteOfTheDay(UUID userUUID, LocalDate date);
I have the following error when the query is called : ERROR: syntax error at or near ":"
By default, Spring Data JPA uses position-based parameter binding. We can also use named parameter with #Param annotation to give a method parameter a concrete name and bind the name in the query. As you are trying to use the named parameter try using the below snippet with #Param annotations.
Optional<QuoteOfTheDay> getQuoteOfTheDay(#Param("userUUID") UUID userUUID, #Param("date") LocalDate date);
ERROR: syntax error at or near means that you need to escape casting colons.
Every : needs to be replaced by \\:
(SELECT ARRAY[:userUUID]'\\:\\:uuid[] && ARRAY_AGG(ql.user_uuid)\\:\\:uuid[]) AS liked
OR use double colons
(SELECT ARRAY[:userUUID]::::uuid[] && ARRAY_AGG(ql.user_uuid)::::uuid[]) AS liked
OR use Cast instead of colons
(SELECT cast(ARRAY[:userUUID] as uuid[]) && cast(ARRAY_AGG(ql.user_uuid) as uuid[])) AS liked
The second problem is that you need to create an array with a query parameter.
Try ARRAY[:userUUID] instead of {:userUUID}
Full query example:
#Query(value = "SELECT q.quote, q.author, q.display_at, count(ql.*) AS like, (SELECT ARRAY[:userUUID]\\:\\:uuid[] && ARRAY_AGG(ql.user_uuid)\\:\\:uuid[]) AS liked " +
"FROM quotes q " +
"LEFT JOIN quotes_likes ql ON ql.quote_uuid = q.uuid " +
"WHERE display_at = :date " +
"GROUP BY q.quote, q.author, q.display_at;", nativeQuery = true)
Optional<QuoteOfTheDay> getQuoteOfTheDay(UUID userUUID, LocalDate date);
As you are trying to pass method parameters to the query using named parameters, We define these using the #Param annotation inside our repository method declaration.
Each parameter annotated with #Param must have a value string matching the corresponding JPQL or SQL query parameter name.
#Query(value = "SELECT q.quote, q.author, q.display_at, count(ql.*) AS like, (SELECT '{:userUUID}'::uuid[] && ARRAY_AGG(ql.user_uuid)::uuid[]) AS liked " +
"FROM quotes q " +
"LEFT JOIN quotes_likes ql ON ql.quote_uuid = q.uuid " +
"WHERE display_at = :date " +
"GROUP BY q.quote, q.author, q.display_at;", nativeQuery = true)
Optional<QuoteOfTheDay> getQuoteOfTheDay(#Param("userUUID") UUID userUUID,#Param("date") LocalDate date);
Refer this for more details on how to use the #Query annotation.

JPA Repository nativeQuery with pageable, join table not working when pageable.size = 1

I have this sql query that works fine
SELECT ppd.userId, AVG(Coalesce(sm.score, 0)) AS avgScore
FROM Table1 ppd
LEFT JOIN Table2 sm ON ppd.userId = sm.userId AND sm.created BETWEEN start AND end
WHERE ppd.someId IN (listOfIds)
GROUP BY ppd.userId ORDER BY avgScore DESC LIMIT 1 OFFSET 0;
I have written a query for a method in a JPARepository that looks like the following
#Query(value = "SELECT ppd.userId, AVG(Coalesce(sm.score, 0)) AS avgScore " +
" FROM Table1 ppd " +
" LEFT JOIN Table2 sm ON sm.userId = ppd.userId AND sm.created BETWEEN :start AND :end " +
" WHERE ppd.someId IN (:IdList) GROUP BY ppd.userId",
nativeQuery = true)
Page<MyDtoProjection> getAvg(#Param("IdList") List<String> IdList,
#Param("start") Long start,
#Param("end") Long end,
Pageable pageable);
Which works just as expected except when the pageable size is 1 (SQL query with Limit 1 works fine).
For the pageable parameter the service sends the following pageable object
Sort sort = JpaSort.unsafe(Sort.Direction.ASC, "(avgScore)");
Pageable pageable = PageRequest.of(offset, limit, sort);
The request sets offset=0 and limit=1
The error I'm receiving from request using repository code is this one:
Unknown column 'ppd' in 'field list'\nQuery is: select count(ppd)..., I don't know why it is taking the alias ppd as a column name, also, I read from another post that this way of executing native queries performs a count (which can be seen in the error message, I did not specify that count query), and that it can be specified by setting countQuery parameter, but I'm not sure what to write there.
Solve it>
Just added the countQuery to the #Query annotation, I was not so sure what to place there, but it was simple
#Query(value = "SELECT ppd.userId, AVG(Coalesce(sm.score, 0)) AS avgScore " +
" FROM Table1 ppd " +
" LEFT JOIN Table2 sm ON sm.userId = ppd.userId AND sm.created BETWEEN :start AND :end " +
" WHERE ppd.someId IN (:IdList) GROUP BY ppd.userId",
countQuery = "SELECT COUNT(ppd.userId) FROM Table1 ppd " +
"WHERE ppd.userId IN (:IdList) GROUP BY ppd.userId",
nativeQuery = true)
Page<MyDtoProjection> getAvg(#Param("IdList") List<String> IdList,
#Param("start") Long start,
#Param("end") Long end,
Pageable pageable);
This is the only post I found that mentions the countQuery and how it works in one of the answers, go check it out if someone is going through the same
Spring Data and Native Query with pagination

I got a syntax error when trying to call JPA function

When Im trying to call JPA function this statement I got an error: syntax error at or near ":"
public interface BcaTestRepository extends CrudRepository<InBodyBCA, Long> {
#Query(value = "SELECT * FROM in_body_bca bca WHERE person_id = :personId " +
"AND to_timestamp(bca.datetimes::text, 'YYYYMMDDHH24MISS') BETWEEN :startRange AND :endRange",
nativeQuery = true)
List<InBodyBCA> findAllByPersonId(#Param("personId") Long personId,
#Param("startRange") LocalDateTime startRange,
#Param("endRange") LocalDateTime endRange);
But in PgAdmin the query works fine
SELECT id, to_timestamp(datetimes::text, 'YYYYMMDDHH24MISS') as dt FROM in_body_bca WHERE to_date(datetimes::text, 'YYYYMMDDHH24MISS')
BETWEEN '2018-05-07' AND '2019-05-07' ORDER BY to_date(datetimes::text, 'YYYYMMDDHH24MISS') DESC ;
You use the double colon here: bca.datetimes::text. JPA would look for text variable name.
You need to escape it:
bca.datetimes\\:\\:text

Spring Data paginate and sort a NamedNativeQuery (JPA-Hibernate-MySql)

I'm using NamedNativeQueries with SqlResultSetMappings in a Spring Data (JPA Hibernate MySQL) application, and I've been successful with the Pagination, but not with the sorting.
I've tried two forms of queries:
#NamedNativeQuery(
name = "DatasetDetails.unallocatedDetailsInDataset",
resultClass = DatasetDetails.class,
resultSetMapping = "DatasetDetails.detailsForAllocation",
query = "SELECT dd.id, fk_datasets_id, fk_domains_id, fk_sources_id, dom.name AS domain, " +
"src.name AS source " +
"FROM datasets AS d " +
"JOIN datasets_details AS dd ON dd.fk_datasets_id = d.id " +
"JOIN sources AS src ON src.id = dd.fk_sources_id " +
"JOIN domains AS dom ON dom.id = dd.fk_domains_id " +
"WHERE fk_datasets_id = :datasetId " +
"AND dd.id NOT IN (" +
"SELECT fk_datasets_details_id from allocations_datasets_details) \n/* #page */\n"),
and the second is simply using the count notation on a second query instead of using the #page notation.
#NamedNativeQuery(
name = "DatasetDetails.unallocatedDetailsInDataset.count",
resultClass = DatasetDetails.class,
resultSetMapping = "DatasetDetails.detailsForAllocation",
query = "SELECT count(*)
....
Both methods work for pagination, but the sorting is ignored.
Here is the repository:
public interface DatasetDetailsRepository extends PagingAndSortingRepository<DatasetDetails, Long> {
#Query(nativeQuery = true)
List<DatasetDetails> unallocatedDetailsInDataset(#Param("datasetId") long datasetId,
#Param("page") Pageable page);
}
And the pageable gets assembled like this:
Sort sort = Sort.by(Sort.Order.asc(DatasetDetails.DOMAIN), Sort.Order.asc(DatasetDetails.SOURCE));
Pageable page = PageRequest.of(page, limit, sort);
No errors are thrown, but the sorting simply doesn't get done and no ORDER BY is generated.
Explicitly adding something like ORDER BY #{#page} won't compile.
I encountered the same problem, where I had to dynamically filter/sort using a NamedNativeQuery by different columns and directions; apparently the Sorting was ignored. I found this workaround, which is not necessarily nice but it does the job:
For the repository:
List<MyEntity> findMyEntities(
#Param("entityId") long entityId,
#Param("sortColumn") String sortColumn,
#Param("sortDirection") String sortDirection,
Pageable page);
The native queries look like this:
#NamedNativeQueries({
#NamedNativeQuery(name = "MyEntity.findMyEntities",
query = "select e.field1, e.field2, ..." +
" from my_schema.my_entities e" +
" where condition1 and condtition2 ..." +
" order by " +
" CASE WHEN :sortColumn = 'name' and :sortDirection = 'asc' THEN e.name END ASC," +
" CASE WHEN :sortColumn = 'birthdate' and :sortDirection = 'asc' THEN e.birthdate END ASC," +
" CASE WHEN :sortColumn = 'name' and :sortDirection = 'desc' THEN e.name END DESC," +
" CASE WHEN :sortColumn = 'birthdate' and :sortDirection = 'desc' THEN e.birthdate END DESC" +
),
#NamedNativeQuery(name = "MyEntity.findMyEntities.count",
query = "select count(*) from my_schema.my_entities e" +
" where condition1 and condtition2 ..." +
" and :sortColumn = :sortColumn and :sortDirection = :sortDirection"
)
})
Notice in the count query I use the 2 redundant conditions for :sortColumn and :sortDirection, because once specified as #Param in the repository function, you need to use them in the actual query.
When calling the function, in my service I had a boolean which dictates the direction and a string that dictates the sorting column like this:
public Page<MyEntity> serviceFindFunction(Long entityId, String sortColumn, Boolean sortDirection, Integer pageNumber, Integer pageSize) {
String sortDir = (sortDirection) ? 'asc' : 'desc';
Pageable pageable = new PageRequest(pageNumber, pageSize); // Spring Data 1.0 syntax
// for Spring Data 2.0, as you were using, simply:
// Pageable pageable = PageRequest.of(pageNumber, pageSize);
return entityRepository.findMyEntities(entityId, sortColumn, sortDir, pageable)
}
The 2 things that I don't like about this are the redundant usage of the sortColumn and sortDirection params in the count query, and the way I wrote the order by statement. The reasoning for having separate CASE statements is because I had different data types for the columns that I sorted by, and if they are incompatible (e.g. nvarchar and date), the query will fail with the error:
Conversion failed when converting date and/or time from character string
I could also probably nest the conditionals, i.e. first making a case for the direction, the making an inner case for the columns, but my SQL skills only went this far.
Hope this helps! Any feedback or improvements are very welcomed.

Use postgres function timestamp in JpaRepository

I try to group by date only, column active_to is timestamp so it has time also. This query works in pgAdmin but JpaRepository seems to have problem even if it is native query. How can I modify this query to work using JpaRepository?
#Query(value = "SELECT o.active_to::timestamp::date, count(o) as sum from work_order o group by o.active_to::timestamp::date order by o.active_to::timestamp::date asc limit 7", nativeQuery = true)
I get this error:
org.postgresql.util.PSQLException: ERROR: syntax error at or near ":"
Position: 19
You cannot use : because this is the character that starts a named parameter.
You have to use cast.
#Query(value = "SELECT cast(cast(o.active_to as timestamp) as date), count(o) as sum " +
"from work_order o group by cast(cast(o.active_to as timestamp) as date) " +
"order by cast(cast(o.active_to as timestamp) as date) asc limit 7",
nativeQuery = true)
Cast and :: are similar. Read more about here:
https://www.postgresql.org/docs/current/sql-expressions.html#SQL-SYNTAX-TYPE-CASTS

Categories