使用带有JPA标准查询的TIMESTAMPDIFF和hibernate作为提供程序

sc4hvdpw  于 5个月前  发布在  其他
关注(0)|答案(5)|浏览(51)

我有一个包含setup和release列的数据表,这两个列都包含时间戳。我的目标是使用CriteriaQuery创建一个与下面的SQL查询等效的查询。
SQL查询:SELECT TIMESTAMPDIFF(SECOND, setup, released)) as sum_duration FROM calls
CriteriaBuilder#diff()函数显然不起作用,因为它需要Numbers参数,所以我尝试使用CriteriaBuilder#函数:

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。

qzlgjiam

qzlgjiam1#

下面是对等价的JPA Criteria Query的解释,
SELECT * 来自TIMESTAMPDIFF(SECOND,setup,released)< 3600的调用;
首先,您必须创建单元表达式并从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);


它起作用了。

deikduxw

deikduxw2#

我遇到了同样的问题: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();

字符串
这给了我同样的结果。

iaqfqrcu

iaqfqrcu3#

CASE WHEN 1=1 THEN TIMESTAMPDIFF(SECOND,startDatetime,endDatetime) ELSE 0 END

字符串
用案例陈述来模拟它。

c6ubokkw

c6ubokkw4#

这里有一个比卡里德·沙阿的更一般的解决方案。
基本上,我们需要的是一个自定义的“opaque literal”Expression类,它能够将任意token表达式(如MONTHYEARINTERVAL 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());

7z5jn7bk

7z5jn7bk5#

我在这里遇到了一个类似的问题,但有一个变化- Hibernate 6已经改变了很多,以至于“OpaqueLiteralExpression”答案所建议的内部API不再适用。
我设法解决了这个问题,通过使用原始的Hibernate标准生成器

cb as HibernateCriteriaBuilder
val duration = cb.durationBetween(startTime, endTime)
val overlap = cb.greaterThanOrEqualTo(cb.durationByUnit(TemporalUnit.MINUTE, duration), 10)

字符串

相关问题