QueryDSL, Spring Data: Select datetime between constant and database value with Predicate - spring-data

Hi I would like to generate a Predicate for spring data QueryDSLRepository that would generate following query (or equivalent):
SELECT * FROM USER user JOIN PASSWORD_POLICY policy
ON
user.password_policy_oid = policy.password_policy_oid
WHERE
user.password_expiration_date BETWEEN CURRENT TIMESTAMP - (SELECT EXPIRATION_WARN_PERIOD_IN_DAYS FROM PASSWORD_POLICY subQueryPolicy WHERE subQueryPolicy.password_policy_oid = policy.password_policy_oid) DAYS AND CURRENT TIMESTAMP
The meaning of this query would be:
Get me all users that password is about to expire
and by about to expire I mean - password expiration date is between now and (now - EXPIRATION_WARN_PERIOD_IN_DAYS from PASSWORD_POLICY table)
Is it possible?

This probably it can't be done by using only Spring Data predicate runner. AFAIK jpql does not support such datetime manipulation (add days etc). So what you can do if you still want use Querydsl is to use native JPASQLQuery. Unfortunately joining mapped entities is not easy, another drawback is that datetime manipulation capabilities are also not so nice in Querydsl. But I was able to manage your problem.
Assumptions:
User contains #ManyToOne PassworPolicy field, which is mapped by PASSWORD_POLICY_OID column.
DB2 datatabase
import static model.QPasswordPolicy.passwordPolicy;
import static model.QUser.user;
import static com.mysema.query.sql.SQLExpressions.datediff;
import static com.mysema.query.types.expr.DateTimeExpression.currentTimestamp;
...
NumberPath<Integer> userPasswordPolicyOidPath = new NumberPath<>(Integer.class, QUser.user, "PASSWORD_POLICY_OID");
QPasswordPolicy subQueryPolicy = new QPasswordPolicy("subQueryPolicy");
List<Integer> userIds =
new JPASQLQuery(entityManager, new DB2Templates())
.from(user)
.join(passwordPolicy).on(passwordPolicy.passwordPolicyOid.eq(userPasswordPolicyOidPath))
.where(datediff(DatePart.day, currentTimestamp(DateTime.class), user.passwordExpirationDate)
.lt(new JPASubQuery().from(subQueryPolicy)
.where(passwordPolicy.passwordPolicyOid.eq(subQueryPolicy.passwordPolicyOid))
.unique(passwordPolicy.expirationPeriodInDays)))
.list(user.userOid);
probably some one more condition expirationDate < currentTimeStamp is needed to satisfy the logic, but I will leave it to you :)
PS
userPasswordPolicyOidPath is ugly but I don't have idea how to get rid of this :(

Related

DynamoDB column with tilde and query using JPA

i have table column with tilde value like below
vendorAndDate - Column name
Chipotle~08-26-2020 - column value
I want to query for month "vendorAndPurchaseDate like '%~08%2020'" and for year ends with 2020 "vendorAndPurchaseDate like '%2020'". I am using Spring Data JPA to query the values. I have not worked on column with tilde values before. Please point me in a right direction or some examples
You cannot.
If vendorAndPurchaseDate is your partition key , you need to pass the whole value.
If vendorAndPurchaseDate is range key , you can only perform
= ,>,<>=,<=,between and begins_with operation along with a partition key
reference : https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Query.html
DynamoDB does not support this type of wildcard query.
Let's consider a more DynamoDB way of handling this type of query. It sounds like you want to support 2 access patterns:
Get Item by month
Get Item by year
You don't describe your Primary Keys (Partition Key/Sort Key), so I'm going to make some assumptions to illustrate one way to address these access patterns.
Your attribute appears to be a composite key, consisting of <vendor>~<date>, where the date is expressed by MM-DD-YYYY. I would recommend storing your date fields in YYYY-MM-DD format, which would allow you to exploit the sort-ability of the date field. An example will make this much clearer. Imagine your table looked like this:
I'm calling your vendorAndDate attribute SK, since I'm using it as a Sort Key in this example. This table structure allows me to implement your two access patterns by executing the following queries (in pseudocode to remain language agnostic):
Access Pattern 1: Fetch all Chipotle records for August 2020
query from MyTable where PK = "Vendors" and SK between Chipotle~2020-08-00 and Chipotle~2020-08-31
Access Pattern 2: Fetch all Chipotle records for 2020
query from MyTable where PK = "Vendors" and SK between Chipotle~2020-01-01 and Chipotle~2020-12-31
Because dates stored in ISO8601 format (e.g. YYYY-MM-DD...) are lexicographically sortable, you can perform range queries in DynamoDB in this way.
Again, I've made some assumptions about your data and access patterns for the purpose of illustrating the technique of using lexicographically sortable timestamps to implement range queries.

Spring JPA repository and QueryDsl how to force left join

Let's say I have two entities User and Task, each user can have one task.
The issue that I'm facing is if I have one record in the user table whose email starts with a and there are no records at all in the task table.
This snippet below will return no records although I would expect users that have mail starting with a.
UserRepository in example extends QuerydslPredicateExecutor.
userRepository.findAll(
QUser.user.email.startsWith("a")
.or(QUser.user.task.text.contains("something"))
)
If I check logs, Hibernate is creating cross join with user.task_id=task.id as a part of where clauses. This type of join automatically discards users whose mails are starting with a if they don't have a task assigned.
Is there a way to force usage of left join instead of a cross join in findAll method of the repository?
I know I can do it by using JPAQuery but then I would have to reimplement paging functionality...
JPAQuery query = new JPAQuery(entityManager);
query
.from(QUser.user)
.leftJoin(QTask.task)
// ...
I am not sure if we can do that since the findAll implementation is generated for us. However we can pass a predicate in the findAll method which will help deal with issue you are encountering.
You can try to do something like this:
QUser qUser = QUser.user;
QTask qTask = QTask.task;
JPQL<UserEntity> userJpqlQuery = JPAExpressions.selectFrom(qUser)
.leftjoin(qUser.task, qTask)
.where(qUser.email...., qTask.text...);
userRepository.findAll(qUser.in(userJpqlQuery));
In the code above I have used Querydsl, which is an alternative to CriteriaBuilder and is type safe. Then I have created a subquery to make the selection I want and return the all users matching the subquery.
In the end , hibernate should generate something like this:
select * from User qUser0 where qUser0.id.in(
select qUser1.id from User qUser1
left join Task qTask0 on
qUser1.taskId = qTask0.id
where ...
);

How to persist only time using eclipseLink?

I have a timestamp attribute in my oracle 11g database table. I generated JPA entities from table and the timestamp attribute was created as a date entity. I only want to store and retrieve time values into the database. how to do this? I am using eclipseLink2.4
You can use the #Temporal(TemporalType.TIME) annotation
#Temporal(TemporalType.TIME)
private Date value;
Then only the time portion should be stored to the database.
If you're using the JPA Criteria API to create Queries, you should make sure that the Date portion is zero on existing data. I.e. on an Oracle Database if you query the data like select to_char(timecolumn,'dd.mm.yyyy hh24:mi:ss') from sometable the result should look like "01.01.1970 XX:XX:XX". Normally if you're using the JPA to store Time Values with TemporalType.TIME it will take care of that for you. If the Date portion isn't zero you may have problems comparing the time field.

JPA query for day ignoring the time

Let say you have a class with a java.util.Date field. Maybe a To do class or an Event planner class.
#Temporal(TemporalType.TIMESTAMP)
private Date startTime;
This will map to a datetime column in MySQL.
There are use cases when you want to know precisely when this Event occurs. "What is scheduled for Aug 11, 2011 at 8 PM?"
Sometimes you just want to know which events are planned for a specific date. "What is scheduled for August 11, 2011?"
If your JPAQL is:
SELECT blah blah etc. WHERE ti.startTime = :d1
and the parameter is a java.util.Date instance:
query.setParameter("d1", date, TemporalType.DATE);
your results will be restricted by the date but also the time.
This is such a common use case that I'm surprised that there is no simple way to do this even in JPA 2.0.
I'd rather not use a vendor specific hack nor play around with strings/substrings.
How have you solved this problem?
I know I'm a little late, but I've found a way to do this.
I was using a jpql like this:
select s from S s where date(s.someDate) = :param
And I set the param with:
query.setParameter("param", param, TemporalType.DATE);
The way I've found to do this with Criteria Query was:
builder.equal(
builder.function("date", Date.class, root.get(S_.someDate)),
param
);
At least, with MySql it works.
I hope it can help.
select ... where ti.startTime >= :theDay and ti.startTime < :theNextDay
is a relatively easy solution to implement, and works on any JPA implementation.
Hibernate also allows adding functions to a dialect, in order to generate custom SQL.

How to select only month in JPA

i have trouble with select only month or year in JPA
in mysql i write statement follow:
select * from table where Month(date) = 12 ;
and in entity bean i write follow:
select t from table t where Month(t.date) = 12;
but it throw Error !!
PS: sorry i can't attach my stacktrace because i'm not at home :D
sorry but you cant do this with JPA as far as i know. hibernate's hql on the otherside knows stuff like month() hour() and minute().
check this question: JPA Date Arithmetic?
hope that helped
Not part of JPA, and consequently in the role of vendor extensions. Most JPA implementations support it ... and you don't say which one you're using. DataNucleus certainly supports MONTH, YEAR, DAY, HOUR, MINUTE, SECOND.
This might help someone.
Few JPA implementations have built-in support for date/time functions.
EclipseLink
EclipseLink supports EXTRACT allowing any database supported date/time
part value to be extracted from the date/time. The EXTRACT function is
database independent, but requires database support. EXTRACT(YEAR,
model.currencyExchangeDate), but it requires EclipseLink 2.4.x
FUNC allows for a database function to be call from JPQL. It allows calling any database functions not supported directly in JPQL.
To call the DB function MyFunc(a, b, c) use FUNC('MyFunc', a, b, c).
You can try FUNC('YEAR', currencyExchangeDate) to call 'YEAR'
function.
Hibernate
In HQL, you can have date function YEAR(date), MONTH(date), DAY(date)
to extract required details. Other time functions supported are
HOUR(date), MINUTE(date), SECOND(date).
Criteria API
CriteriaBuilder#function() : Create an expression for the execution
of a database function.
CriteriaQuery<IptExchangeratelines> cq = cb.createQuery(IptExchangeratelines.class);
Root<IptExchangeratelines> exRateLine = cq.from(IptExchangeratelines.class);
cq.where(cb.equal(cb.function("year", Integer.class,
exRateLine.get(exRateLine_.currencyExchangeDate)), year)
.and(cb.equal(cb.function("month", Integer.class,
exRateLine.get(exRateLine_.currencyExchangeDate)), month)));