HQL Expects java.time.duration even though method-head and usage suggest LocalDateTime - spring

I am writing a query within a JpaRepository which takes a String and 3 LocalDateTimes as parameters. Within the query I first compare the String like an Id and afterwards I use a different Column of the corresponding Entity to create LocalDateTimes using appropriate operators for Hibernate 6.
The Application starts up normal but when i call the query I get the following Error:
Argument [2023-01-23T11:43:59] of type [java.time.LocalDateTime] did not match parameter type [java.time.Duration (n/a)]
The Argument obviously got parsed correctly by the Restcontroller but Hibernate does not seem to create the query as expected.
The following is the Code for the Repository and the query in question:
public interface ExchangePairRepository extends JpaRepository<MyEntity, Long> {
#Query("SELECT ep.unit FROM MyEntity ep WHERE ep.id= :id AND ((ep.context = 'start' AND ((ep.unit = 'day' AND (:start+ ep.duration day) > :now) "
+ "OR (ep.unit = 'month' AND (:start+ ep.duration month) > :now))) OR (ep.context = 'end' AND ((ep.unit = 'day' AND (:now + ep.duration day) > :end) "
+ "OR (ep.unit = 'month' AND (:now + ep.duration month) > :end)) ))")
List<String> findViable(#Param("matNrOrig") String id, #Param("start") LocalDateTime start, #Param("end") LocalDateTime end,
#Param("now") LocalDateTime now);
}
Below is the Entity which i being used for the query:
#Entity
#Table(name = "my_entity")
#Data
public class MyEntity{
(...)
#Column(name = "id_orig")
private String idOrig;
´
#Column(name = "id_target")
private String idTarget;
#Column(name = "context")
private String context;
#Column(name = "duration")
private int duration;
#Column(name = "unit")
private String unit;
}
Is this a bug or am I doing something wrong? Any help is much appreciated.
I am using Hibernate 6.1.6.Final and Spring Boot 3.0.1 with Java 17
Edit:
Casting the parameters within the query solved the problem for now, though it does not look very pretty. I will just wait for the bug being fixed in one of the next releases.

This is a bug in the new parameter type inference logic. I can reproduce with just this query:
session.createQuery("select :dt + 1 day")
.setParameter("dt", LocalDateTime.now())
.getSingleResult();
I have opened an issue for you here: https://hibernate.atlassian.net/browse/HHH-16102
UPDATE
The following workaround is very ugly, but works:
session.createQuery("select cast(:dt as LocalDateTime) + 1 day")
.setParameter("dt", LocalDateTime.now())
.getSingleResult();

Related

JdbcBatchItemWriter - setSql - How to fix timestamp type mismatch

I am using JdbcBatchItemWriter in my Springboot application to import data from a CSV file into an Oracle DB, but I’m not able to import in timestamps. I get the error Failed to convert property value of type 'java.lang.String' to required type 'java.time.LocalDateTime
I understand the error – I just don’t understand how to fix it. Is there a way to mark the insert statement so that it reads it as a timestamp?
I did try:
INSERT INTO HB357(START_DATE) VALUES (TO_DATE(:START_DATE, YYYY-MM-DD, HH:mm:SS)
And I tried
TO_TIMESTAMP(:START_DATE, YYYY-MM-DD HH24:mm:SS)
but it didn't work
I also have tried changing the entity type to String instead of LocalDataTime, but this results in not a valid month error.
So I then tried altering the session before the insert (with the entity type still as string):
itemWriter.setSql("ALTER SESSION SET NLS_DATE_FORMAT = 'YYYY-MM-DD HH24:MI:SS' NLS_TIMESTAMP_FORMAT = 'YYYY-MM-DD HH24:MI:SS'");
And then running the insert state but also got the not a valid month error
ENTITY:
#Entity
#NamedQuery(name ="HB357.getListOfResponseIds", query = "SELECT RESPONSE_ID from HB357")
public class HB357 {
#Id
private String RESPONSE_ID;
#JsonFormat(pattern="yyyy-MM-dd#HH:mm:ss")
private LocalDateTime START_DATE;
private String STOVE_OPTION;
private String FIRST_NAME;
private String LAST_NAME;
private String ADDRESS;
private String CITY;
private String ZIPCODE;
private String COUNTY;
//Getters and Setters left out for brevity
Relevant Insert Function
#Bean
public JdbcBatchItemWriter<HB357> writer() {
JdbcBatchItemWriter<HB357> itemWriter = new JdbcBatchItemWriter<>();
itemWriter.setDataSource(appProperties.dataSource());
itemWriter.setSql("INSERT INTO HB357 (" +
"START_DATE, RESPONSE_ID, STOVE_OPTION, FIRST_NAME,LAST_NAME,ADDRESS,CITY,ZIPCODE,COUNTY,PHONE_NUMBER,EMAIL," +
"STOVE_LOCATION,EXEMPTIONS,AGI,INCENTIVE,INCENTIVE_AMT) " +
"VALUES (:START_DATE, :RESPONSE_ID, :STOVE_OPTION, :FIRST_NAME, :LAST_NAME, :ADDRESS, :CITY," +
":ZIPCODE, :COUNTY, :PHONE_NUMBER, :EMAIL, :STOVE_LOCATION, :EXEMPTIONS, :AGI," +
":INCENTIVE, :INCENTIVE_AMT)" );
itemWriter.setItemSqlParameterSourceProvider(new BeanPropertyItemSqlParameterSourceProvider<>());
return itemWriter;
}
create class eg . public class HB357ParameterSourceProvider implements ItemSqlParameterSourceProvider and implement/do transformation for datatype inside it.
eg MapSqlParameterSource params = new MapSqlParameterSource();
params.addValue("start time", <>")
return param
pojo -> use instant start time conver date into timestamp(in case of sql server) and set
ItemSqlParameterSourceProvider valueSetter = new HB357ParameterSourceProvider ()
writer.setItemSqlParameterSourceProvider(valueSetter)

Spring Data JPA (JPQL) problem: Using TIMESTAMPDIFF function in JPA query

I have the following MySQL query:
select p.name, sum(timestampdiff(MINUTE, r.start_time, r.end_time))
from ride r inner join person p on r.driver_id=p.id
group by p.id
I want to write this in a JPA Query. The code below throws a NullPointerException, it does not work:
#RestResource
public interface SomeRepository extends CrudRepository<Ride, Long> {
#Query("SELECT new com.sample.dto.MyDTO(d.name, SUM(FUNCTION('TIMESTAMPDIFF', 'MINUTE', r.startTime, r.endTime))) " +
"FROM Ride r JOIN r.driver d WHERE r.startTime BETWEEN ?1 AND ?2 " +
"GROUP BY d.id" )
List<MyDTO> findSomething(#Param("startTime") LocalDateTime startTime,
#Param("endTime") LocalDateTime endTime);
}
If I use DATEDIFF function, it works.
FUNCTION('DATEDIFF', r.startTime, r.endTime)
But I need the exact difference in minutes. TIMESTAMPDIFF satisfies my needs.
After failure of finding how to do this with JPA, I returned to native query but this is not good. Any alternatives?
I use the latest spring-data-jpa (2.1.3), hibernate-core:5.3.7
public class Ride {
#Column(name = "start_time")
private LocalDateTime startTime;
#Column(name = "end_time")
private LocalDateTime endTime;
}
The goal is to find total difference between startTime and endTime (in minutes). Is there a way to do this with standard (portable) JPA?
Actually, there is no TIMESTAMPDIFF in JPQL.
Reference: timestampdiff equivalent in JPQL (without using criteria)
So, What you can do is that you can use TIME_TO_SEC(TIMEDIFF(r.startTime, r.endTime)) / 60 instead of using FUNCTION('TIMESTAMPDIFF', 'MINUTE', r.startTime, r.endTime)
There is no registerFunction('TIMESTAMPDIFF') in org.hibernate.dialect.MySQLDialect.
But registerFunction('DATEDIFF') exists.
So you cant use TIMESTAMPDIFF in jpa.
You can add custom function or use custom Dialect.
But I suggest using unix_timestamp() instead.
(function('unix_timestamp', startTime)-function('unix_timestamp', endTime))/60

Pagination error in SpringBoot "Incorrect syntax near '#P0'" [duplicate]

I'm using Spring Data JPA, and when I use #Query to to define a query WITHOUT Pageable, it works:
public interface UrnMappingRepository extends JpaRepository<UrnMapping, Long> {
#Query(value = "select * from internal_uddi where urn like %?1% or contact like %?1%",
nativeQuery = true)
List<UrnMapping> fullTextSearch(String text);
}
But if I add the second param Pageable, the #Query will NOT work, and Spring will parse the method's name, then throw the exception No property full found. Is this a bug?
public interface UrnMappingRepository extends JpaRepository<UrnMapping, Long> {
#Query(value = "select * from internal_uddi where urn like %?1% or contact like %?1%",
nativeQuery = true)
Page<UrnMapping> fullTextSearch(String text, Pageable pageable);
}
You can use pagination with a native query. It is documented here: Spring Data JPA - Reference Documentation
"You can however use native queries for pagination by specifying the count query yourself:
Example 59. Declare native count queries for pagination at the query method using #Query"
public interface UserRepository extends JpaRepository<User, Long> {
#Query(value = "SELECT * FROM USERS WHERE LASTNAME = ?1",
countQuery = "SELECT count(*) FROM USERS WHERE LASTNAME = ?1",
nativeQuery = true)
Page<User> findByLastname(String lastname, Pageable pageable);
}
A similar question was asked on the Spring forums, where it was pointed out that to apply pagination, a second subquery must be derived. Because the subquery is referring to the same fields, you need to ensure that your query uses aliases for the entities/tables it refers to. This means that where you wrote:
select * from internal_uddi where urn like
You should instead have:
select * from internal_uddi iu where iu.urn like ...
Considering that the UrnMapping class is mapped to the internal_uddi table, I would suggest this:
#Repository
public interface UrnMappingRepository extends JpaRepository<UrnMapping, Long> {
#Query(value = "select iu from UrnMapping iu where iu.urn like %:text% or iu.contact like %:text%")
Page<UrnMapping> fullTextSearch(#Param("text") String text, Pageable pageable);
}
Please note that you might have to turn off native queries with dynamic requests.
With #Query , we can use pagination as well where you need to pass object of Pageable class at end of JPA method
For example:
Pageable pageableRequest = new PageRequest(page, size, Sort.Direction.DESC, rollNo);
Where,
page = index of page (index start from zero)
size = No. of records
Sort.Direction = Sorting as per rollNo
rollNo = Field in User class
UserRepository repo
repo.findByFirstname("John", pageableRequest);
public interface UserRepository extends JpaRepository<User, Long> {
#Query(value = "SELECT * FROM USER WHERE FIRSTNAME = :firstname)
Page<User> findByLastname(#Param("firstname") String firstname, Pageable pageable);
}
Please reference :Spring Data JPA #Query, if you are using Spring Data JPA version 2.0.4 and later. Sample like below:
#Query(value = "SELECT u FROM User u ORDER BY id")
Page<User> findAllUsersWithPagination(Pageable pageable);
Declare native count queries for pagination at the query method by using #Query
public interface UserRepository extends JpaRepository<User, Long> {
#Query(value = "SELECT * FROM USERS WHERE LASTNAME = ?1",
countQuery = "SELECT count(*) FROM USERS WHERE LASTNAME = ?1",
nativeQuery = true)
Page<User> findByLastname(String lastname, Pageable pageable);
}
Hope this helps
https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#jpa.query-methods
Rewrite your query to:
select iu from internal_uddi iu where iu.urn....
description: http://forum.spring.io/forum/spring-projects/data/126415-is-it-possible-to-use-query-and-pageable?p=611398#post611398
I found it works different among different jpa versions, for debug, you'd better add this configurations to show generated sql, it will save your time a lot !
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.format_sql=true
for spring boot 2.1.6.RELEASE, it works good!
Sort sort = new Sort(Sort.Direction.DESC, "column_name");
int pageNumber = 3, pageSize = 5;
Pageable pageable = PageRequest.of(pageNumber - 1, pageSize, sort);
#Query(value = "select * from integrity_score_view " +
"where (?1 is null or data_hour >= ?1 ) " +
"and (?2 is null or data_hour <= ?2 ) " +
"and (?3 is null or ?3 = '' or park_no = ?3 ) " +
"group by park_name, data_hour ",
countQuery = "select count(*) from integrity_score_view " +
"where (?1 is null or data_hour >= ?1 ) " +
"and (?2 is null or data_hour <= ?2 ) " +
"and (?3 is null or ?3 = '' or park_no = ?3 ) " +
"group by park_name, data_hour",
nativeQuery = true
)
Page<IntegrityScoreView> queryParkView(Date from, Date to, String parkNo, Pageable pageable);
you DO NOT write order by and limit, it generates the right sql
I had the same issue - without Pageable method works fine.
When added as method parameter - doesn't work.
After playing with DB console and native query support came up to decision that method works like it should. However, only for upper case letters.
Logic of my application was that all names of entity starts from upper case letters.
Playing a little bit with it. And discover that IgnoreCase at method name do the "magic" and here is working solution:
public interface EmployeeRepository
extends PagingAndSortingRepository<Employee, Integer> {
Page<Employee> findAllByNameIgnoreCaseStartsWith(String name, Pageable pageable);
}
Where entity looks like:
#Data
#Entity
#Table(name = "tblEmployees")
public class Employee {
#Id
#Column(name = "empID")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
#NotEmpty
#Size(min = 2, max = 20)
#Column(name = "empName", length = 25)
private String name;
#Column(name = "empActive")
private Boolean active;
#ManyToOne
#JoinColumn(name = "emp_dpID")
private Department department;
}
When using nativeQuery that is having (nativeQuery = true), you may do the pagination yourself in the query by adding (LIMIT :sizeValue OFFSET :page)
Note:
Your page value passed to this method should be offset * size
Example
#Query(value = "SELECT * FROM person " +
"LIMIT ?1 OFFSET ?2", nativeQuery = true)
Optional<List<TDriverJob>> findPersons(int size, int page);
I tried all above solution and non worked , finally I removed the Sorting from Pagination and it worked
the following tutorial helped me
-> https://www.baeldung.com/spring-data-jpa-query
At this point 4.3. Spring Data JPA Versions Prior to 2.0.4
VERY IMPORTANT to add \ n-- #pageable \ n
Without this I was wrong
Also the pagination setting must be without ordering
PageRequest paginaConf = new PageRequest ((param1 - 1)
, param2);
Finally to convert the Page <Object []>
Page <Object []> list = myQueryofRepo ();
List <XXXModel> lstReturn = myConversor (list.getContent ());
Page <XXXModel> ret = new PageImpl <XXXModel> (lstReturn, pageConf, param2);
This bugged me for a while but I managed with a very smooth solution.
The challenge is JPA did not automatically detect the count query so I resolved to use the countName which according JPA docs Returns the name of the javax.persistence.NamedQuery to be used to execute count queries when pagination is used. Will default to the named query name configured suffixed by .count.
So I created a named query
#NamedNativeQuery(
name = "[queryName].count",
query = [your count query],
resultSetMapping = "[query resultSetMapping name]"
)
}
As indicated, the count query should be suffixed with .count
Count query returns Long so add the resultSetMapping as
#SqlResultSetMapping(
name="[query resultSetMapping name]",
columns={#ColumnResult(name="count", type = Long.class)})
Then in your repository, use the count query as indicated below
#Query(countName ="[queryName].count" , nativeQuery = true)
Page<Object> [mainQuery](...params)
Hope this helps!

Spring JPA Repository generating incorrect SQL

I'm using Spring JPA Repository with Hibernate below towards a MySQL Database. On that environment, I've the following Entity:
#Entity
#Table(name="cod__postales")
public class CodigoPostal {
#Id
#GeneratedValue
private Long registro;
#Column(name = "`POST_Nº_CODIGO`", length = 6, nullable = false)
private String codigo = " ";
#Column(name = "POST_DESCRIP", length = 30, nullable = false)
private String descripcion = " ";
#Column(name = "POST_ZONA_ASIG", length = 2, nullable = false)
private String zona = " ";
Also I've the following Spring JPA Repository:
public interface CodigoPostalRepository extends JpaRepository<CodigoPostal, Long> {
CodigoPostal findOneByCodigo(String codigo);
}
As you can see, there isan special character on the first column name 'º'. The problem es that, when I call to repo.findOneByCodigo() method, the following SQL is generated
select codigopost0_.registro as registro1_2_,
codigopost0_.`post_nº_codigo` as post_nº_2_2_,
codigopost0_.post_descrip as post_des3_2_,
codigopost0_.post_zona_asig as post_zon4_2_
from cod__postales codigopost0_
where codigopost0_.`post_nº_codigo`=?
The problem is that sql gives an error because of the name spring/hibernate assing to that column: as post_nº_2_2_.
How can I avoid this?
This is a first thought regarding this query and my suggestion is to introduce a native query which will help you to change 'as post_nº_2_2_.' in something like: 'as post_n_2_2_.'.
Example:
#Query(value = " select codigopost0_.registro as registro1_2_,
codigopost0_.`post_nº_codigo` as post_n_2_2_,
codigopost0_.post_descrip as post_des3_2_,
codigopost0_.post_zona_asig as post_zon4_2_
from cod__postales codigopost0_
where codigopost0_.`post_nº_codigo`=?", nativeQuery = true)
public interface CodigoPostalRepository extends JpaRepository<CodigoPostal, Long> {
CodigoPostal findOneByCodigo(String codigo);
}
More information on this link.
At the end I've been able to solve this by adding the characterEncoding parameter to the url string:
spring.datasource.url=jdbc:mysql://localhost/madr?characterEncoding=UTF-8
Anyway, I've opened an issue to Hibernate because maybe this is not a valid option for everyone https://hibernate.atlassian.net/browse/HHH-10493
Change
#Column(name = "`POST_Nº_CODIGO`", length = 6, nullable = false)
to
#Column(name = "`POST_N_CODIGO`", length = 6, nullable = false)

Hibernate tuple criteria queries

I am trying to create a query using hibernate following the example given in section 9.2 of chapter 9
The difference with my attempt is I am using spring MVC 3.0. Here is my Address class along with the method i created.
#RooJavaBean
#RooToString
#RooEntity
#RooJson
public class Address {
#NotNull
#Size(min = 1)
private String street1;
#Size(max = 100)
private String street2;
private String postalcode;
private String zipcode;
#NotNull
#ManyToOne
private City city;
#NotNull
#ManyToOne
private Country country;
#ManyToOne
private AddressType addressType;
#Transient
public static List<Tuple> jqgridAddresses(Long pID){
CriteriaBuilder builder = Address.entityManager().getCriteriaBuilder();
CriteriaQuery<Tuple> criteria = builder.createTupleQuery();
Root<Address> addressRoot = criteria.from( Address.class );
criteria.multiselect(addressRoot.get("id"), addressRoot.get("street1"), addressRoot.get("street2"));
criteria.where(builder.equal(addressRoot.<Set<Long>>get("id"), pID));
return Address.entityManager().createQuery( criteria ).getResultList();
}
}
The method called jqgridAddresses above is the focus. I opted not to use the "Path" because when I say something like Path idPath = addressRoot.get( Address_.id ); as in section 9.2 of the documentation, the PathAddress_.id stuff produces a compilation error.
The method above returns an empty list of type Tuple as its size is zero even when it should contain something. This suggests that the query failed. Can someone please advise me.
OK so i made some minor adjustments to my logic which is specific to my project, however, the following approach worked perfectly. Hope it hepls someone in need !
#Transient
public static List<Tuple> jqgridPersons(Boolean isStudent, String column, String orderType, int limitStart, int limitAmount){
CriteriaBuilder builder = Person.entityManager().getCriteriaBuilder();
CriteriaQuery<Tuple> criteria = builder.createTupleQuery();
Root<Person> personRoot = criteria.from(Person.class );
criteria.select(builder.tuple(personRoot.get("id"), personRoot.get("firstName"), personRoot.get("lastName"), personRoot.get("dateOfBirth"), personRoot.get("gender"), personRoot.get("maritalStatus")));
criteria.where(builder.equal( personRoot.get("isStudent"), true));
if(orderType.equals("desc")){
criteria.orderBy(builder.desc(personRoot.get(column)));
}else{
criteria.orderBy(builder.asc(personRoot.get(column)));
}
return Address.entityManager().createQuery( criteria ).setFirstResult(limitStart).setMaxResults(limitAmount).getResultList();
}

Resources