Spring Data #Query - How to get current date + day - java

I would like to get records that have date_time column is in a week from now, in a month from now
I have a query like this
public interface BookingRepository extends JpaRepository<Booking, Long> {
#Query("SELECT b " +
"FROM BOOKING b " +
"WHERE b.date_time < NOW() + INTERVAL 7 DAY and b.date_time > NOW()")
List<Booking> getListBooking();
}
In MySQL, NOW() + INTERVAL 7 DAY is working but in JPA #Query, I don't know which function is correspond to it.
In this situation, I have to use dynamic query instead of native query. So I'd like to use dynamic query and face this problem.
Please help.
Thank you!

there is no date_add in JPA so you would have few options:
use native query https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#_native_queries
implement custom jpa function for date modifications
parametrize query
personally i would go with query parametrization as it is easier to test and maintain in the long-run.

Related

date comparison in JPQL

I have a comparison of two dates in my application code. The code is in Flutter, but is easy, so you can understand it even if you donĀ“t know Dart:
selectedDayCalendar.isAfter(item.canceledDate.add(Duration(days: 15)))
Here I want to know when the date selected in Calendar is after other date + 15 days. The problem is:
data can only be shown when date 1 is before or on the same day as date 2 (+ 15 days). If I do this validation in the application, the API will still bring data that I will not use, so I wanted to do this validation in the API itself. The API can only bring in data where:
!selectedDayCalendar.isAfter(item.canceledDate.add(Duration(days: 15)))
How I can do this validation in JPQL Query SELECT?
JPQL doesn't support adding days to a date sadly, so you'll have to use a native query:
SELECT e FROM Item e WHERE DATEADD(DAY, 15, e.canceledDate) < CURRENT_DATE
I think something like that.
But some people don't like the mix of using native and JPQL queries combined.
RESOLVED:
I put Instant.now() in a local variable, then I created another one with instant.now() + 15 days. I passed these two variables by parameter to the function JPQL Query.
it was like this:
Instant startDate = Instant.now();
Instant endDate = startDate.plus(15, ChronoUnit.DAYS);
return eventService.findByTrainerLoginCanceledDateBetween(startDate, endDate);
and JPQL:
#Query(
"SELECT e " +
"FROM Event e " +
"WHERE e.status = '' OR e.canceledDate BETWEEN :startDate AND :endDate"
)
List<Event> findEventByLoginAndCanceledDateBetween(Instant startDate, Instant endDate);

problem with spring-data-jpa JPQL query with optional Timestamp filter

we are facing one problem. In our code that runs for year at least, we use spring repositories with custom query methods, using #Query annotation. In these methods we tend to apply filters on queries, based on passed in #Nullable parameters. So far it was working nice, for queries like this:
#Query(value =
"select p FROM PersonOfInterestEntity p "
+ "where (?1 is null or p.upperPoiIdentifier like ?1) "
+ "and p.status in ?2 "
+ "and (?3 is null or p.createdDtm >= ?3) "
+ "and (?4 is null or p.createdDtm <= ?4) "
+ "and p.ixTenant.id=?5 ")
Page<PersonOfInterestEntity> findAllByNamesAndDateRangeAndStatusesAndTenantId(
#Nullable String identifier, List<StatusEnum> statuses, #Nullable Timestamp startDate, #Nullable Timestamp endDate, String tenantId, Pageable pageable);
As found on articles on net, that is handy way to have single dao method that can be used to query with different filters, and to skip conditions on certain columns in case that passed in values are null.
But problem is that once we upgraded to hibernate 5.4.10.Final (we used hibernate 5.2.8.Final so far), we suddenly started getting error:
[ WARN] [ org.hibernate.engine.jdbc.spi.SqlExceptionHelper] - SQL Error: 932, SQLState: 42000
[ERROR] [ org.hibernate.engine.jdbc.spi.SqlExceptionHelper] - ORA-00932: inconsistent datatypes: expected TIMESTAMP got BINARY
Trouble is that it is not only hibernate what is upgraded, but also whole bunch of dependencies that we get from some third party team - so we can't really state that Hibernate is what makes the difference here. Also, we can't decrease Hibernate version back to 5.2.8 in upgraded code, neither we can increase Hibernate version in old code that works fine with these queries, since in both case we get some startup issues -> that makes hard to isolate what actually causes that change of behavior.
Interesting is that same syntax still seem to work for parameters of type String (see ?1 directly after where keyword in query), issue seems related only to Timestamp.
My question is, should we look at change in hibernate, spring-data-jpa or Oracle driver? I couldn't find any documentation that would state such backward inconsistency. We would really like to skip need for workaround to split our methods to separate queries for each null/non null parameter, since that would really increase the mess in code since we have a lot of combinations of these optional parameters. And also would like to understand how it was working so far, and why Timestamp is that different than other types.
update:
I just realized we have also some special class that affects how Timestamps in code are serialized into database. Due to planned port to MySql, there was some need to round times in db on 3/100 of ms, so we have UserType that should be responsible to do that. This might somehow impact that query params binding, not sure... From debugging the hibernate code (that is quite hard since it does not get on thread calls stack in IDE), I noticed that PreparedStatementParameter for part of query like this:
p.createdDtm >= ?3, gets internal expectedType as CustomType, with name of that special class that was added.
part of query for parameter with same index:
?3 is null, gets resolved without expected type, that later gets resolved in binding as (sqlTypeDescriptior=VarbinaryTypeDescriptor and javaTypeDescriptor=SerializableTypeDescriptor), and later results with error that I gave upper. But also to say, that code that adjusts Timestamp values already existed in previous version of code that was working ok on hibernate 5.2.8... Maybe that custom type requires some adjustments for newer versions of hibernate?
Ok, this is fun... After quite some debug and trying to understand why it got broken, I realized that same query works if we use named parameters !?
#Query(value =
"select p FROM PersonOfInterestEntity p "
+ "where (:id is null or p.upperPoiIdentifier like :id) "
+ "and p.status in :statuses "
+ "and (:startDtm is null or p.createdDtm >= :startDtm) "
+ "and (:endDtm is null or p.createdDtm <= :endDtm ) "
+ "and p.ixTenant.id=:tenantId ")
Page<PersonOfInterestEntity> findAllByNamesAndDateRangeAndStatusesAndTenantId(
#Param("id") #Nullable String identifier,
#Param("statuses") List<StatusEnum> statuses,
#Param("startDtm") #Nullable Timestamp startDate,
#Param("endDtm") #Nullable Timestamp endDate,
#Param("tenantId") String tenantId,
Pageable pageable);
I think that it proves that it is some bug in hibernate, since it should work the same for both queries? At least, I'm happy enough with this, hope it would also save someone else time in future ;)
Cheers!

Criteria API limit results in subquery

I'm trying to write a query similar to
select * from Table a
where a.parent_id in
(select b.id from Table b
where b.state_cd = ?
and rownum < 100)
using the Criteria API. I can achieve the query without the rownum limitation on the subquery fine using similar code to https://stackoverflow.com/a/4668015/597419 but I cannot seem to figure out how to appose a limit on the Subquery
In Hibernate, you can add the actual SQL restriction, but it is worth noting this will be Oracle-specific. If you switched over to PostgreSQL, this would break and you'd need LIMIT 100 instead.
DetachedCriteria criteria = DetachedCriteria.forClass(Domain.class)
.add(Restrictions.sqlRestriction("rownum < 100"));
In the JPA API, the short answer is that you can't... In your question you proposed using the Criteria API (along with a SubQuery). However it is not until you actually call EntityManager.createQuery(criteriaQuery) that you'll get a TypedQuery where you can specify the maxResult value.
That said, you could break it into 2 queries, the first where you get the inner-select results (max 100) and then a 2nd Criteria where you take the resulting list in an in():
// inner query
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<YourClass> innerCriteriaQuery = cb.createQuery(YourClass.class);
Root<YourClass> yourClass = innerCriteriaQuery.from(YourClass.class);
innerCriteriaQuery.select(yourClass).where(
cb.equal(yourClass.get(YourClass_.stateCode), someStateValue));
// list of 100 parent ids
List<YourClass> list = em.createQuery(innerCriteriaQuery).setMaxResults(100).getResultList();
// outer query
CriteriaQuery<YourClass> criteriaQuery = cb.createQuery(YourClass.class);
Root<YourClass> yourClass = criteriaQuery.from(YourClass.class);
criteriaQuery.select(yourClass).where(
cb.in(yourClass.get(YourClass_.parentId)).value(list);
return em.createQuery(criteriaQuery).getResultList();
There is no JPA Criteria solution for this. You could make use of a custom SQL function that runs during SQL query generation time. All JPA providers support something like that in one way or another.
If you don't want to implement that yourself or even want a proper API for constructing such queries, I can only recommend you the library I implemented called Blaze-Persistence.
Here is the documentation showcasing the limit/offset use case with subqueries: https://persistence.blazebit.com/documentation/core/manual/en_US/index.html#pagination
Your query could look like this with the query builder API:
criteriaBuilderFactory.create(entityManager, SomeEntity.class)
.where("id").in()
.select("subEntity.id")
.from(SomeEntity.class, "subEntity")
.where("subEntity.state").eq(someValue)
.orderByAsc("subEntity.id")
.setMaxResults(100)
.end()
It essentially boils down to using the LIMIT SQL function that is registered by Blaze-Persistence. So when you bootstrap Blaze-Persistence with your EntityManagerFactory, you should even be able to use it like this
entityManager.createQuery(
"select * from SomeEntity where id IN(LIMIT((" +
" select id " +
" from SomeEntity subEntity " +
" where subEntity.state = :someParam " +
" order by subEntity.id asc" +
"),1)) "
)
or something like
criteriaQuery.where(
cb.in(yourClass.get(YourClass_.parentId)).value(cb.function("LIMIT", subquery));
If you are using EclipseLink the calling convention of such functions looks like OPERATOR('LIMIT', ...).

How to use date_format when using JPQL/JPA

I am doing Java EE with MySQL as database and implementing JPA on my codes.
I have no problem retrieving the data from MySQL Workbench but when I change my syntax to JPQL's it does not work.
For e.g. in MySQL - it works
SELECT date_format(s.date,'%Y, %m, %d') from Transactions s;
in JPQL - it does not
SELECT date_format(s.date,'%Y, %m, %d') from TransactionEntity s;
How do i modify to suit the JPA query?
Note:
in JPQL the following works
SELECT s.date from TransactionEntity s;
SQL function date_format is not part of JPQL, as any documentation would tell you, so don't see the point in just pushing SQL into JPQL and expecting it to work.
What you can do with JPA 2.1 is invoke it as follows
function("date_format", s.date, '%Y, %m, %d')
where function is a way to invoke any native SQL function. This clearly means you lose database independence because that function is not valid on all datastores.
Sql function is available in JPQL. My JPA version is 1.11.9. Sample group by day query:
#Query(value = "SELECT count(ac) as count, function('date_format', max(ac.subscriptionStartDate), '%Y, %m, %d') as date FROM MyTable ac " +
"WHERE ac.subscriptionStartDate BETWEEN :startDate AND :endDate GROUP BY function('date_format', ac.subscriptionStartDate, '%Y, %m, %d')")
public List<Map<String,Object>> findRegisteredCustomersHistory(#Param("startDate") Date startDate, #Param("endDate") Date endDate);
The result of the query is the list that records the number of records grouped by days in formatted form.
Sample rows:
count: 3, date: 2019, 09, 10
count: 1, date: 2019, 09, 11
for your question, try this in JPQL:
#Query(value = "SELECT function('date_format', s.date, '%Y, %m, %d') as date from Transactions s;")

JPA query with filter on current time stamp - seconds for Oracle

I am using JPA, Eclipse link, Oracle.
I am trying to introduce a condition in my JPA query equivalent to following oracle expression
current_timestamp - NUMTODSINTERVAL(10, 'SECOND')
I have tried several queries similar to following
select u from User u where (current_timestamp - FUNC('NUMTODSINTERVAL', :offset, 'SECOND')) > u.birth_date
but to no avail. All result in syntax errors. I want to avoid native query as well as calculating values using java.
I found following on some oracle website.
Use the add_months function in Oracle, and use simple math to convert the month value to seconds.
If above is possible then I'll be able to use following
Query query = entityManager.createQuery("select u"
+ " from User u"
+ " where"
+ " FUNC('add_months', CURRENT_TIMESTAMP, :offset) > u.birth_date "//)"//
);
query.setParameter("offset", getOffSet());
Edit: I need some Eclipse link JPA way of doing this or Oracle way using which add_months can be used to add/deduct seconds.
(Standard JPA 2.1) JPQL allows "FUNCTION" to be included in WHERE clauses. Nowhere is there a "FUNC", except maybe in some vendor extension, and people are always advised to avoid those particularly where there is a standard

Categories

Resources