[英]Using TIMESTAMPDIFF with JPA criteria query and hibernate as the provider
我有一個包含列 setup 和 release 的數據表,它們都包含時間戳。 我的目標是使用 CriteriaQuery 創建以下 SQL 查詢的等效項。
SQL 查詢: SELECT TIMESTAMPDIFF(SECOND, setup, released)) as sum_duration FROM calls
CriteriaBuilder#diff()函數顯然不起作用,因為它需要數字參數,所以我嘗試使用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();
但是,當我嘗試運行此代碼時,它拋出了異常; 似乎文字沒有呈現為常量,而是呈現為參數:
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}
所以我嘗試匿名覆蓋LiteralExpression#render以直接返回我提供給方法的字符串,但是這個異常。
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}
所以問題是:我怎樣才能修復我正在嘗試做的這個操作,或者實現最初的目標?
我正在使用 Hibernate,我的數據庫是 MySQL。
我碰到了同樣的問題: SECOND
將被撇號包圍,查詢將引發異常。
我通過以下代碼解決了這個問題:
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();
這給了我相同的結果。
這是等效的JPA標准查詢的說明
SELECT * *從TIMESTAMPDIFF(SECOND,設置,已釋放)<3600;
首先,您必須創建單元表達式並從BasicFunctionExpression
進行擴展,對於BasicFunctionExpression
,它將“ SECOND”參數作為單元並僅覆蓋其rendor(RenderingContext renderingContext)
方法。
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();
}
}
那么您可以在JPA條件查詢中使用此單位表達式。
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);
這是工作。
CASE WHEN 1=1 THEN TIMESTAMPDIFF(SECOND,startDatetime,endDatetime) ELSE 0 END
用case語句模擬它。
這是一個比 Kalid Shah 的解決方案更通用的解決方案。
基本上,我們需要的是一個自定義的“不透明文字” Expression
類,它能夠將任意標記表達式(如MONTH
、 YEAR
、 INTERVAL 1 DAY
等)作為函數參數傳遞給 MySQL:
import javax.persistence.criteria.CriteriaBuilder;
import org.hibernate.query.criteria.internal.CriteriaBuilderImpl;
import org.hibernate.query.criteria.internal.compile.RenderingContext;
import org.hibernate.query.criteria.internal.expression.LiteralExpression;
/**
* Represents an opaque literal that gets pass through JPA unaltered into SQL.
*/
@SuppressWarnings("serial")
public class OpaqueLiteralExpression extends LiteralExpression<Void> {
private final String value;
public OpaqueLiteralExpression(CriteriaBuilderImpl builder, String value) {
super(builder, Void.class, null);
if (value == null)
throw new IllegalArgumentException("null value");
this.value = value;
}
// ExpressionImpl
@Override
public String render(RenderingContext renderingContext) {
return this.value;
}
}
然后你可以像這樣使用它:
final Expression<LocalDate> birthDate = student.get(Student_.birthDate);
final Expression<Integer> age = builder.function("TIMESTAMPDIFF", Integer.class,
new OpaqueLiteralExpression(builder, "YEAR"), birthDate, LocalDate.now());
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.