JPA: compare LocalDateTime attribute to builder.currentTimestamp() - java

Given:
#Entity
public class Paramter() {
#Id
private Long id;
private LocalDateTime startDate;
// getters & setters
}
// Extract from repository/dao method that grabs parameters:
...
final CriteriaBuilder builder = entityManager.getCriteriaBuilder();
final CriteriaQuery<Parameter> query = builder.createQuery(Parameter.class);
final Root<Parameter> param = query.from(Parameter);
// The line below is generates following compilation error due to mismatch
// of the types of LocalDateTime and Timestamp parameters:
// java: no suitable method found for greaterThanOrEqualTo(javax.persistence.criteria.Path<java.time.LocalDateTime>,javax.persistence.criteria.Expression<java.sql.Timestamp>)
query.where(builder.greaterThanOrEqualTo(param.get(Parameter_.startDate), builder.currentTimestamp())); ...
How to properly use java 8 date-time api (more specifically LocalDateTime property) with jpa criteria builder to achive: "select * from parameter where start_date >= sysdate"?
Please consider:
The solution can be Oracle specific but should use JPA criteria builder
I do not want to create a select query to grab the sysdate and than use it in another select query to grab the valid parameters. It should be done in one query like in the expected query example above.
The time from the server is not reliable - database time should be used
Using Hibernate 5.2.16 which seems to include JPA 2.1. Changing to JPA 2.2 doesn't seem to make a difference.

Related

Unable to use OffsetDateTime as a query parameter using JPA Data/JPQL

I have an Entity CustomEntity with a data member updateDate of type OffsetDateTime.
I have defined a Repository for this Entity which has a simple method to retrieve list of records matching updateDate as
List<CustomEntity> findByUpdateDate(OffsetDateTime updateDate);
Now, when this method is called from Controller/Service bean, I can see no matching record is retrieved; however, when I execute the generated SQL in the DB, I can see matching rows available.
I can retrieve the records based on other data members of the entity; its just an issue with OffsetDateTime and LocalDateTime
I got to understand that java.time package support was not in JPA 2.1; however I am using JPA 2.3.1. Do I need to use Converters (as suggested for JPA 2.1?
Any help is much appreciable.
EDIT :-
Below is the code for Entity
#Entity
#Table(name = "SAMPLE_TABLE")
public class CustomEntity {
#Id
#GeneratedValue
private Long id;
#Column
private OffsetDateTime updateDate;
//Getters & Setters
}
I am using Microsoft SQL Server and the generated SQL query (hibernate generated) looks something like below
select sample0_.id as id1_10, sample0_.updateDate as update2_10 from sample_table sample0_ where sample0_.updateDate=?
binding parameter [1] as [TIMESTAMP] - [2021-07-27T17:22:34.597Z]

How to query with Spring JPA on jsonb columns?

I'm using spring JPA with PostgreSQL database. I have an Entity as follow:
#Entity
#TypeDef(name="json_binary", typeClass = com.vladmihalcea.hibernate.type.json.JsonBinaryType.class)
public class LiveTokens {
#Id
#GeneratedValue()
private Long id;
private String username;
#Type(type="json_binary")
#Column(columnDefinition = "jsonb")
private Token token
}
and Token:
public class Token {
private Long expireTime;
private String accessToken;
}
For saving object to column with Hibernate, I use Hibernate Types project.
Now I want to get All LiveTokens that expired. I can't do it with Spring JPA. How do I query to posgresql jsonb column with Spring data JPA?
SQL JSON functions
If you want to call a SQL function that processes the JSON column, then you just need to make sure you register the function with Hibernate prior to using it.
JSON operators
For JSON operators, like the PostgreSQL ones (e.g., ->>), the only option you have is to use a native SQL query. JPQL or HQL don't support database-specific JSON operators.
Using EclipseLink and spring data jpa, if your data in db is something like: {"expireTime":102020230201, "accessToken":"SOMETHING" }, my first question is why to use long numbers for your dates instead of timestamps (ex '2019-09-14 12:05:00'). If you use timestamps there are also options to manage timezones (either from postgresql or from you source code).
Regarding your issue you may use the FUNC JPQL keyword of EclipseLink (Hibernate may have something similar) in order to run a database specific function. In the example below I use FUNC('jsonb_extract_path_text', lt.token, 'expireTime') to get the values of the json for token.expireTime.
PostgreSql method jsonb_extract_path_text returns text, thus you cannot do a less that condition, so I cast the output of the function using JPQL CAST keyword with (CAST -data- TO -type-).
#Repository
public interface MyRepository extends JpaRepository<LiveTokens, Integer> {
#Query(value = "SELECT lt FROM LiveTokens lt WHERE CAST(FUNC('jsonb_extract_path_text', lt.token, 'expireTime') AS LongType) < :expirirationThreshold")
List<LiveTokens> findByExpireTime(#Param("expirirationThreshold") Long expirirationThreshold);
}
Again, this is not tested.
This is how a native query using postgres JSON query operators would look like, incoorporating your example:
#Query(value="SELECT t.* FROM LiveTokens t WHERE CAST(t.token ->> 'expireTime' AS LONG) < now()", native=true)
Assuming your real tablename is LiveTokens, native queries do no longer use the JPA translations, and the tablename has to match the one in the DB. (You may also need to specify it's schema.)
Try it:
Service class:
Long currentTime = new Date().getTime();
Repository:
#Query("SELECT lt FROM LiveTokens lt WHERE lt.token.expireTime <= :currentTime")
List<LiveTokens> findExpiredLiveTokens(#Param("currentTime") long currentTime)

JPQL not working with dates

I'm not able to get the data from database using the dates in the query...
I'm working on Web application which is using Spring Data JPA and Oracle Database. I was using #RepositoryRestResource annotation in interface where I was just declaring some query methods with named parameters using #Param and #Query annotations. Today I needed to add a new entity with the dates. In database both columns are type of DATE and it is used in the query. But I also have the other one which is type of TIMESTAMP and maybe I would need to use in a future. And below the Java representation of this two columns only, of course with all setters and getters, but it has more fields so just adding this:
#Temporal(TemporalType.TIMESTAMP)
#Column(name = "INIT_DATE")
private Calendar initDate;
#Temporal(TemporalType.TIMESTAMP)
#Column(name = "AGG_DATE")
private Calendar aggDate;
I also created new interface for case, the same way as always:
#RepositoryRestResource(collectionResourceRel = "customer", path = "customer")
public interface ICustomerRepository extends PagingAndSortingRepository<Customer, Long> {
#Query("SELECT c FROM Customer c where c.initDate <= TO_DATE(:currentDate, 'yyyy-MM-dd') AND c.aggDate >= TO_DATE(:currentDate, 'yyyy-MM-dd')")
public List<Customer> filterByDate(#Param("currentDate") #DateTimeFormat(pattern = "yyyy-MM-dd") Calendar currentDate);
}
And I'm receiving this error:
org.springframework.dao.DataIntegrityViolationException: could not extract ResultSet; SQL [n/a]; nested exception is org.hibernate.exception.DataException: could not extract ResultSet
ORA-01858: a non-numeric character was found where a numeric was expected
I'm trying to get this data from database using this http request:
http://localhost/webApp/customer/search/filterByDate?currentDate=2017-07-10
In SQL Developer the query works fine.
I read somewhere that in JPQL there is no date function, but in log I can see the query and parameter which looks like this:
select
customer0_.customerId as col_0_0_,
customer0_.customerName as col_0_1_,
customer0_.aggDate as col_0_2_,
customer0_.initDate as col_0_3_,
from
customer customer0_
where
and customer0_.aggDate>=to_date(?, 'yyyy-MM-dd')
and customer0_.initDate<=to_date(?, 'yyyy-MM-dd')
2017-07-25 11:55:22.550 TRACE 12252 --- [ (self-tuning)'] o.h.type.descriptor.sql.BasicBinder : binding parameter [8] as [TIMESTAMP] - [java.util.GregorianCalendar[time=1499637600000,areFieldsSet=true,areAllFieldsSet=true,lenient=true,zone=sun.util.calendar.ZoneInfo[id="Europe/Berlin",offset=3600000,dstSavings=3600000,useDaylight=true,transitions=143,lastRule=java.util.SimpleTimeZone[id=Europe/Berlin,offset=3600000,dstSavings=3600000,useDaylight=true,startYear=0,startMode=2,startMonth=2,startDay=-1,startDayOfWeek=1,startTime=3600000,startTimeMode=2,endMode=2,endMonth=9,endDay=-1,endDayOfWeek=1,endTime=3600000,endTimeMode=2]],firstDayOfWeek=1,minimalDaysInFirstWeek=1,ERA=1,YEAR=2017,MONTH=6,WEEK_OF_YEAR=28,WEEK_OF_MONTH=3,DAY_OF_MONTH=10,DAY_OF_YEAR=191,DAY_OF_WEEK=2,DAY_OF_WEEK_IN_MONTH=2,AM_PM=0,HOUR=0,HOUR_OF_DAY=0,MINUTE=0,SECOND=0,MILLISECOND=0,ZONE_OFFSET=3600000,DST_OFFSET=3600000]]
2017-07-25 11:55:22.550 TRACE 12252 --- [ (self-tuning)'] o.h.type.descriptor.sql.BasicBinder : binding parameter [9] as [TIMESTAMP] - [java.util.GregorianCalendar[time=1499637600000,areFieldsSet=true,areAllFieldsSet=true,lenient=true,zone=sun.util.calendar.ZoneInfo[id="Europe/Berlin",offset=3600000,dstSavings=3600000,useDaylight=true,transitions=143,lastRule=java.util.SimpleTimeZone[id=Europe/Berlin,offset=3600000,dstSavings=3600000,useDaylight=true,startYear=0,startMode=2,startMonth=2,startDay=-1,startDayOfWeek=1,startTime=3600000,startTimeMode=2,endMode=2,endMonth=9,endDay=-1,endDayOfWeek=1,endTime=3600000,endTimeMode=2]],firstDayOfWeek=1,minimalDaysInFirstWeek=1,ERA=1,YEAR=2017,MONTH=6,WEEK_OF_YEAR=28,WEEK_OF_MONTH=3,DAY_OF_MONTH=10,DAY_OF_YEAR=191,DAY_OF_WEEK=2,DAY_OF_WEEK_IN_MONTH=2,AM_PM=0,HOUR=0,HOUR_OF_DAY=0,MINUTE=0,SECOND=0,MILLISECOND=0,ZONE_OFFSET=3600000,DST_OFFSET=3600000]]
And to be honest, I have no idea what is the problem here... The format date in the database is yy/MM/DD, but it also wasn't working for me... Could you tell me what I'm missing or doing wrong??
EDIT [answer to Gusti Arya]:
I tried two things. Before your update I just changed the type and left the #DateTimeFormat. Then I had the same error as with Calendar type.
After removing the #DateTimeFormat, so having the same after your update I get this error:
org.springframework.data.repository.support.QueryMethodParameterConversionException: Failed to convert 2017-07-10 into java.util.Date!
Caused by: org.springframework.core.convert.ConversionFailedException: Failed to convert from type [java.lang.String] to type [org.springframework.data.repository.query.Param java.util.Date] for value '2017-07-10'; nested exception is java.lang.IllegalArgumentException
What is more interesting, I have the second query which is almost the same, but without TO_DATE function and now I have the same error as above. Previously I had:
Persistent Entity Must not be null
EDIT 2 [related with the query in the log]:
I just noticed that the query which I posted here is not the same as I see in the log... My entity contains EmbeddedId in which is customerId and customerName. And those two columns appears thrice... So here is the valid log of the query:
select
customer0_.customerId as col_0_0_,
customer0_.customerName as col_0_1_,
customer0_.customerId as col_1_0_,
customer0_.customerName as col_1_1_,
customer0_.customerId as customerId1_6_,
customer0_.customerName as customerName2_6_,
customer0_.aggDate as aggDate3_6_,
customer0_.initDate as initDate4_6_,
from
customer customer0_
where
and customer0_.aggDate>=to_date(?, 'yyyy-MM-dd')
and customer0_.initDate<=to_date(?, 'yyyy-MM-dd')
**EDIT [response to Brian]: **
And also the types in Entity should be Calendar, right? I also put two different Temporal annotation for those two fields. One points to TemporalType.TIMESTAMP and the other to TemporalType.DATE. But then is the problem with the passing calendar value as http parameter. I tried four versions of URL:
1. http://localhost/webApp/customer/search/filterByDate?currentDate=2017-07-10
2. http://localhost/webApp/customer/search/filterByDate?currentDate=2017-07-10 13:08:24.000+0000
3. http://localhost/webApp/customer/search/filterByDate?currentDate=2017-07-10T13:08:24.000+0000
4. http://localhost/webApp/customer/search/filterByDate?currentDate=1499692104
But none of this is not working...
Without further digging into detail, I think you just need to change
#Query("SELECT c FROM Customer c where c.initDate <= TO_DATE(:currentDate, 'yyyy-MM-dd') AND c.aggDate >= TO_DATE(:currentDate, 'yyyy-MM-dd')")
to
#Query("SELECT c FROM Customer c where c.initDate <= :currentDate AND c.aggDate >= :currentDate")
Why? Because the fields initDate and aggDate are of type java.util.Calendar and so is the parameter currentDate. Everything matches and Spring Data as well as any JPA provider binds java.util.Calendar to an SQL timestamp as you can see in your log
...binding parameter [8] as [TIMESTAMP]...
I made huge mistake... The problem is not with the dates, but with the query and the entity... In my real application I have entity with #EmbeddedId and I was sure that the query without filter on date works fine... So I didn't mention about it. Unfortunately, the problem is this #EmbeddedId... I thought that I can use query with c.customerId, c which will return everything, but is not working and without c.customerId returns only Entity without EmbeddedId.
So as #Brian mentioned in his comment:
The ID should be part of the self URL in your JSON response. Including the ID in the JSON object would be redundant. Nonetheless you can expose it via config. See this question and answer for details.
To sum up, query works without TO_DATE function, with #DateTimeFormat annotation for Calendar parameter and #Temporal annotation for the fields with the dates in the entity.
Thank you for all your help and sorry for this mistake.
try changing your code in model class into this :
#Temporal(TemporalType.TIMESTAMP)
#Column(name = "INIT_DATE")
private java.util.Date initDate;
#Temporal(TemporalType.TIMESTAMP)
#Column(name = "AGG_DATE")
private java.util.Date aggDate;
and also in JPQL not defining TO_DATE function, try changing your jqpl to this :
#Query("SELECT c FROM Customer c where c.initDate <= :currentDate AND c.aggDate >= :currentDate"
public List<Customer> filterByDate(#Param("currentDate") Date currentDate);
There is no TO_DATE function in JPQL. You shall either use native sql query, or wrap native database function into JPQL FUNCTION function :)
I believe it should be FUNCTION(TO_DATE('2000-01-01', 'YYYY-MON-DD'))

insert into select with jpa error

I'm trying to do an insert into select with Jpa.
The Entity on which I try to do it is like this:
#Entity
public class A {
private String fieldOne;
private String fieldTwo;
private String fieldThree;
private B fieldFour;
#Id
public String getFieldOne(){...}
#Id
public String getFieldTwo(){...}
#Id
#OneToOne
public B getFieldThree(){...}
public String getFieldFour(){...}
....
#Entity
public class B {
private CompositeId id;
....
#EmbeddedId
public CompositeId getId(){
return MyUUIDGenerator.generateCompositeId();
}
....
The insert I'm trying to is very simple:
insert into A (fieldOne, fieldTwo, fieldThree, fieldFour)
select 'staticValueOne', 'staticValueTwo', B.id, 'staticValueFour' from B
where ....
The 'staticValueX' are values calculated by the application that I need to be all equals for a given set of B elements.
During execution the application return the exception:
java.lang.IllegalArgumentException: org.hibernate.QueryException: can
only generate ids as part of bulk insert with either sequence or
post-insert style generators [insert into ...
I don't understand why, because I don't have any generated value in A, I give to the insert all the values it need.
Does anyone has a suggestion to understand this behaviour?
Thanks!
EDIT: a little update...
I changed the class A with only a field of String type marked as #Id, but hibernate makes errors in building correctly the query: the association of tables alias with fields name miss some fields.
From JPA 2.0 specification, chapter 4.2 Statement Types:
A Java Persistence query language statement may be either a select
statement, an update statement, or a delete statement. (...)
In BNF syntax, a query language statement is defined as:
QL_statement :: = select_statement | update_statement | delete_statement
Instead of SELECT statement which is not supported in JPA (either in JPQL or Criteria API) use ElementManager.persist on an entity within a transaction. When transaction commits the entity is written to the database (SQL INSERT will be done implicitly by Hibernate which acts as the persistence provider).
EDIT: In case of a large number of insertions you may take a closer look at Hibernate's batch inserts. Another option is to give up with JPA and use JDBC's batch insertion (PreparedStatement) directly.

JPA Query For Exists In

I have the following Entities (reduced and renamed for this example)
#Entity
public class Case {
#Id
private Long id;
#ManyToOne(optional=false)
private CourtConfiguration courtConfiguration;
#ElementCollection(fetch=FetchType.EAGER)
private List<String> caseNumbers;
}
Second Entity
#Entity
public class CourtConfiguration {
#Id
private Long id;
String countyId;
String referenceId;
....
}
I am trying to search using JPQL for all Cases that have a certain courtConfiguration countyId and have caseNumbers containing all of a provided set of important caseNumbers.
So my query needs the countyId and set of caseNumbers as parameters. Called countyId and importantCaseNumbers respectively.
I have tried and failed to get it to work.
My query looks like this
String query = "SELECT case FROM Case case JOIN case.caseNumbers caseNumbers WHERE ";
query += "case.caseConfiguration.countyId = :countyId ";
The bit above works until I add my caseNumber conditions.
I have tried a foreach importantNumbers to extend the query and as soon as the list of important numbers goes above one it doesn't work. No values get returned.
for (String importantCaseNum : importantCaseNumbers) {
query += " AND '"+importantCaseNum+"' in (caseNumbers)";
}
Any suggestions/pointers appreciated. I guess what I am looking for is a case.caseNumbers contains (importantNumbers) clause.
Update I have reverted to native SQL for my query as I didn't want to tie myself into hibernate by using HQL. Thanks to #soulcheck and #mikko for helping me out. I'll post up when the hibernate JPA fix is available.
Thanks
Paul
Syntactically correct way to build this JPQL query is with MEMBER OF. But because of problem reported in HHH-5209 it doesn't work with old Hibernate versions (fix version 4.1.8, 4.3.0.Beta1). According bug report HQL version of this query works, so your options includes at least:
Using JPQL query and switching to some other JPA implementation
Using HQL instead and sticking with Hibernate:
query += " AND '"+importantCaseNum+"' in elements(caseNumbers)";

Categories