I have a data table with columns setup and release, both of which hold timestamps. My goal is to create an equivalent of the SQL query below using CriteriaQuery.
SQL Query:SELECT TIMESTAMPDIFF(SECOND, setup, released)) as sum_duration FROM calls
The CriteriaBuilder#diff() function clearly does not work as it requires parameters that are Numbers, so I tried using CriteriaBuilder#function:
EntityManager entityManager = emProvider.get();
CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
CriteriaQuery<Integer> query = criteriaBuilder.createQuery();
Root<Thingy> thingyRoot = query.from(Thingy.class);
Path<DateTime> setup = root.get("setup");
Path<DateTime> release = root.get("release");
Expression secondLiteral = criteriaBuilder.literal("SECOND");
Expression func = criteriaBuilder.function("TIMESTAMPDIFF", Integer.class, secondLiteral, setup, release);
entityManager.createQuery(query).getResultList();
However, when I tried running this code it threw an exception; it appears that the literal was not rendered as a constant, but rather as a parameter:
java.lang.IllegalStateException: No data type for node: org.hibernate.hql.internal.ast.tree.MethodNode
\-[METHOD_CALL] MethodNode: '('
+-[METHOD_NAME] IdentNode: 'TIMESTAMPDIFF' {originalText=TIMESTAMPDIFF}
\-[EXPR_LIST] SqlNode: 'exprList'
+-[NAMED_PARAM] ParameterNode: '?' {name=param0, expectedType=null}
+-[DOT] DotNode: 'cdr0_.setup' {propertyName=setup,dereferenceType=ALL,propertyPath=setup,path=generatedAlias0.setup,tableAlias=cdr0_,className=com.vtsl.domain.CDR,classAlias=generatedAlias0}
| +-[ALIAS_REF] IdentNode: 'cdr0_.id' {alias=generatedAlias0, className=com.vtsl.domain.CDR, tableAlias=cdr0_}
| \-[IDENT] IdentNode: 'setup' {originalText=setup}
\-[DOT] DotNode: 'cdr0_.release' {propertyName=release,dereferenceType=ALL,propertyPath=release,path=generatedAlias0.release,tableAlias=cdr0_,className=com.vtsl.domain.CDR,classAlias=generatedAlias0}
+-[ALIAS_REF] IdentNode: 'cdr0_.id' {alias=generatedAlias0, className=com.vtsl.domain.CDR, tableAlias=cdr0_}
\-[IDENT] IdentNode: 'release' {originalText=release}
So I tried anonymously overriding the LiteralExpression#render to directly return the string I supply to the method, however that thew this exception.
java.lang.IllegalStateException: No data type for node: org.hibernate.hql.internal.ast.tree.MethodNode
\-[METHOD_CALL] MethodNode: '('
+-[METHOD_NAME] IdentNode: 'TIMESTAMPDIFF' {originalText=TIMESTAMPDIFF}
\-[EXPR_LIST] SqlNode: 'exprList'
+-[IDENT] IdentNode: 'SECOND' {originalText=SECOND}
+-[DOT] DotNode: 'cdr0_.setup' {propertyName=setup,dereferenceType=ALL,propertyPath=setup,path=generatedAlias0.setup,tableAlias=cdr0_,className=com.vtsl.domain.CDR,classAlias=generatedAlias0}
| +-[ALIAS_REF] IdentNode: 'cdr0_.id' {alias=generatedAlias0, className=com.vtsl.domain.CDR, tableAlias=cdr0_}
| \-[IDENT] IdentNode: 'setup' {originalText=setup}
\-[DOT] DotNode: 'cdr0_.release' {propertyName=release,dereferenceType=ALL,propertyPath=release,path=generatedAlias0.release,tableAlias=cdr0_,className=com.vtsl.domain.CDR,classAlias=generatedAlias0}
+-[ALIAS_REF] IdentNode: 'cdr0_.id' {alias=generatedAlias0, className=com.vtsl.domain.CDR, tableAlias=cdr0_}
\-[IDENT] IdentNode: 'release' {originalText=release}
So the question is: How can I either fix this operation I'm trying to do, or achieve the original goal?
I'm using Hibernate, my database is MySQL.
Here is explanation of equivalent JPA Criteria Query of
SELECT * from calls where TIMESTAMPDIFF(SECOND, setup, released) < 3600;
First you have to create unit expression and extend it from BasicFunctionExpression
for which take "SECOND" parameter as a unit and override its rendor(RenderingContext renderingContext)
method only.
import java.io.Serializable;
import org.hibernate.query.criteria.internal.CriteriaBuilderImpl;
import org.hibernate.query.criteria.internal.compile.RenderingContext;
import org.hibernate.query.criteria.internal.expression.function.BasicFunctionExpression;
public class UnitExpression extends BasicFunctionExpression<String> implements Serializable {
public UnitExpression(CriteriaBuilderImpl criteriaBuilder, Class<String> javaType,
String functionName) {
super(criteriaBuilder, javaType, functionName);
}
@Override
public String render(RenderingContext renderingContext) {
return getFunctionName();
}
}
then you use this unit expression in your JPA criteria Query.
EntityManager entityManager = emProvider.get();
CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
Root<Calls> thingyRoot = query.from(Calls.class);
Expression<String> second = new UnitExpression(null, String.class, "SECOND");
Expression<Integer> timeInSec = cb.function(
"TIMESTAMPDIFF",
Integer.class,
second ,
root.<Timestamp>get("setup"),
root.<Timestamp>get("release"));
List<Predicate> conditions = new ArrayList<>();
conditions.add(cb.lessThan(timeInSec, 3600));
cq.where(conditions.toArray(new Predicate[]{}));
return session.createQuery(cq);
It is working.
I bumped into the same problem: the SECOND
will be surrounded by apostrophes and the query will throw an exception.
I worked around it by the following code:
CriteriaBuilder builder = em.getCriteriaBuilder();
CriteriaQuery<MyEntity> cq = builder.createQuery( MyEntity.class );
Root<MyEntity> root = cq.from( MyEntity.class );
javax.persistence.criteria.Expression<java.sql.Time> timeDiff = builder.function(
"TIMEDIFF",
java.sql.Time.class,
root.<Date>get( "endDate" ),
root.<Date>get( "startDate" ) );
javax.persistence.criteria.Expression<Integer> timeToSec = builder.function(
"TIME_TO_SEC",
Integer.class,
timeDiff );
//lessThanOrEqualTo 60 minutes
cq.where( builder.lessThanOrEqualTo( timeToSec, 3600 ) );
return em.createQuery( cq ).getResultList();
And this gives me the same result.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With