Hibernate 使用';案例…当…然后…否则…结束';在'中构造;拥有';JPA条件查询中的子句

Hibernate 使用';案例…当…然后…否则…结束';在'中构造;拥有';JPA条件查询中的子句,hibernate,jpa,criteria,criteria-api,having,Hibernate,Jpa,Criteria,Criteria Api,Having,以下条件查询计算不同产品组的平均评级 CriteriaBuilder criteriaBuilder=entityManager.getCriteriaBuilder(); CriteriaQuery<Tuple>criteriaQuery=criteriaBuilder.createQuery(Tuple.class); Metamodel metamodel=entityManager.getMetamodel(); EntityType<Product>entity

以下条件查询计算不同产品组的平均评级

CriteriaBuilder criteriaBuilder=entityManager.getCriteriaBuilder();
CriteriaQuery<Tuple>criteriaQuery=criteriaBuilder.createQuery(Tuple.class);
Metamodel metamodel=entityManager.getMetamodel();
EntityType<Product>entityType=metamodel.entity(Product.class);
Root<Product>root=criteriaQuery.from(entityType);
SetJoin<Product, Rating> join = root.join(Product_.ratingSet, JoinType.LEFT);

Expression<Number> quotExpression = criteriaBuilder.quot(criteriaBuilder.sum(join.get(Rating_.ratingNum)), criteriaBuilder.count(join.get(Rating_.ratingNum)));
Expression<Integer> roundExpression = criteriaBuilder.function("round", Integer.class, quotExpression);
Expression<Object> selectExpression = criteriaBuilder.selectCase().when(quotExpression.isNull(), 0).otherwise(roundExpression );

criteriaQuery.select(criteriaBuilder.tuple(root.get(Product_.prodId).alias("prodId"), selectExpression.alias("rating")));
criteriaQuery.groupBy(root.get(Product_.prodId));

criteriaQuery.having(criteriaBuilder.greaterThanOrEqualTo(roundExpression, 0));
criteriaQuery.orderBy(criteriaBuilder.desc(root.get(Product_.prodId)));

TypedQuery<Tuple> typedQuery = entityManager.createQuery(criteriaQuery);
List<Tuple> tuples = typedQuery.getResultList();
如果
case
子句中指定的表达式计算为
null
,则
case…when
结构将
null
值替换为
0

我需要在
having
子句中构造相同的
case…when
以便
group by
子句返回的行组可以通过将
null
替换为
case>when
构造计算的值列表中的
0
来过滤

相应地,
having
子句应该如下生成

具有
(案例
当Sum(ratingset1\u.rating\u num)/Count(ratingset1\u.rating\u num)为
空则0
ELSE四舍五入(总和(评级集1评级集数量)/计数(评级集1评级集数量))
结束)>=0
如果在
greaterThanOrEqualTo()
方法中,
selectExpression
而不是
roundExpression
是可能的,但这是不可能的。这样做会生成一个编译时错误,指示
表达式
表达式
之间的类型不匹配

那么,当
having
子句中的结构与
select
子句中的结构相同时,我如何才能拥有相同的
大小写呢

我还尝试删除表达式的泛型类型参数
Object
,如
expression selectExpression
,但这样做会导致抛出
NullPointerException


此外,可以看出,
select
子句中给出的别名(
prodId
rating
)在生成的SQL中不起作用。为什么列在这里没有别名?我错过什么了吗

如果列有别名,那么应该可以编写如下所示的
having
子句

having rating>=0
在条件查询中具有
应如下所示

criteriaQuery.having(criteriaBuilder.greaterThanOrEqualTo(join.<Integer>get("rating"), 0));

如何避免这种情况?无论如何,当
select
子句中的
case…生成的值列表中的
0
替换
null
时,应过滤
groupby
返回的行


我使用的是Hibernate4.2.7Final提供的JPA2.0


编辑:

我尝试使用以下表达式:

Expression<Integer> selectExpression = criteriaBuilder.<Integer>selectCase()
                                       .when(quotExpression.isNull(), 0)
                                       .<Integer>otherwise(roundExpression);
EclipseLink(2.3.2)中,可以在
having
子句中使用它

对于Hibernate提供程序,如果试图更改
selectCase()
的表达式类型(默认情况下返回
expression
),它将抛出
NullPoiterExcpetion


更新:


这个问题在Hibernate 5.0.5 final中仍然存在。

这不太可能是Hibernate中的错误。制造给定的条件查询时出现了技术错误。以同样的例子,但形式更简单

假设我们对生成以下SQL查询感兴趣

选择
p、 产品id,
p、 产品名称,
案例
当sum(r.rating\u num)/count(DISTINCT r.rating\u id)为空时,则为0
ELSE四舍五入(总和(r.rating_num)/计数(不同r.rating_id))
以平均额定值结束
从…起
产品p
左外连接
评级r
在p.prod\u id=r.prod\u id上
分组
p、 产品id,
p、 产品名称
有
案例
当sum(r.rating\u num)/count(DISTINCT r.rating\u id)为空时,则为0
ELSE四舍五入(总和(r.rating_num)/计数(不同r.rating_id))
结束>=1
基于MySQL中的下表

mysql> desc rating;
+-------------+---------------------+------+-----+---------+----------------+
| Field       | Type                | Null | Key | Default | Extra          |
+-------------+---------------------+------+-----+---------+----------------+
| rating_id   | bigint(20) unsigned | NO   | PRI | NULL    | auto_increment |
| prod_id     | bigint(20) unsigned | YES  | MUL | NULL    |                |
| rating_num  | int(10) unsigned    | YES  |     | NULL    |                |
| ip_address  | varchar(45)         | YES  |     | NULL    |                |
| row_version | bigint(20) unsigned | NO   |     | 0       |                |
+-------------+---------------------+------+-----+---------+----------------+
5 rows in set (0.08 sec)
此表
rating
与另一个表
product
具有明显的多对一关系(
prod\u id
是引用
product
表中主键
prod\u id
)的外键

在这个问题中,我们只对
HAVING
子句中的
CASE
构造感兴趣

下面的条件查询

CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
CriteriaQuery<Tuple> criteriaQuery = criteriaBuilder.createTupleQuery();
Root<Product> root = criteriaQuery.from(entityManager.getMetamodel().entity(Product.class));
ListJoin<Product, Rating> prodRatingJoin = root.join(Product_.ratingList, JoinType.LEFT);

List<Expression<?>> expressions = new ArrayList<Expression<?>>();
expressions.add(root.get(Product_.prodId));
expressions.add(root.get(Product_.prodName));

Expression<Integer> sum = criteriaBuilder.sum(prodRatingJoin.get(Rating_.ratingNum));
Expression<Long> count = criteriaBuilder.countDistinct(prodRatingJoin.get(Rating_.ratingId));

Expression<Number> quotExpression = criteriaBuilder.quot(sum, count);
Expression<Integer> roundExpression = criteriaBuilder.function("round", Integer.class, quotExpression);
Expression<Integer> selectExpression = criteriaBuilder.<Integer>selectCase().when(quotExpression.isNull(), criteriaBuilder.literal(0)).otherwise(roundExpression);
expressions.add(selectExpression);

criteriaQuery.multiselect(expressions.toArray(new Expression[0]));
expressions.remove(expressions.size() - 1);

criteriaQuery.groupBy(expressions.toArray(new Expression[0]));
criteriaQuery.having(criteriaBuilder.greaterThanOrEqualTo(selectExpression, criteriaBuilder.literal(1)));

List<Tuple> list = entityManager.createQuery(criteriaQuery).getResultList();

for (Tuple tuple : list) {
    System.out.println(tuple.get(0) + " : " + tuple.get(1) + " : " + tuple.get(2));
}
对于技术透视图,请查看上述条件查询中的以下行

criteriaQuery.having(criteriaBuilder.greaterThanOrEqualTo(selectExpression, criteriaBuilder.literal(1)));
它在问题中的类似行是这样写的

createQuery.having(criteriaBuilder.greaterThanOrEqualTo(selectExpression, 1));
参见问题中的原始表达,做完全相同的事情:

Expression<Integer> selectExpression = criteriaBuilder.<Integer>selectCase()
                                       .when(quotExpression.isNull(), 0)
                                       .<Integer>otherwise(roundExpression);
请特别注意上面
greaterThanOrEqualTo()
的第二个参数。它是
0
。它应该是
criteriaBuilder.literal(0)
,因此是问题中提到的异常

因此,在使用
CriteriaBuilder\selectCase()
构造中的表达式时,始终坚持在必要时使用
CriteriaBuilder\literal(T值)
作为文本值


在Hibernate 4.3.6最终版和Hibernate 5.0.5最终版上进行测试。稍后我将尝试在EclipseLink(2.6.1最终版)上运行相同的查询。应该不再有怪癖了


EclipseLink对于修改后的查询版本没有任何问题,除了它需要构造函数参数(形式参数)的
对象
类型参数,前提是使用构造函数表达式代替
元组
,而这个问题与此无关。这是EclipseLink中一个长期存在的bug,仍有待修复。

@Tinay CriteriaBuilder的quot()方法只返回商。如何得到分数部分呢?
CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
CriteriaQuery<Tuple> criteriaQuery = criteriaBuilder.createTupleQuery();
Root<Product> root = criteriaQuery.from(entityManager.getMetamodel().entity(Product.class));
ListJoin<Product, Rating> prodRatingJoin = root.join(Product_.ratingList, JoinType.LEFT);

List<Expression<?>> expressions = new ArrayList<Expression<?>>();
expressions.add(root.get(Product_.prodId));
expressions.add(root.get(Product_.prodName));

Expression<Integer> sum = criteriaBuilder.sum(prodRatingJoin.get(Rating_.ratingNum));
Expression<Long> count = criteriaBuilder.countDistinct(prodRatingJoin.get(Rating_.ratingId));

Expression<Number> quotExpression = criteriaBuilder.quot(sum, count);
Expression<Integer> roundExpression = criteriaBuilder.function("round", Integer.class, quotExpression);
Expression<Integer> selectExpression = criteriaBuilder.<Integer>selectCase().when(quotExpression.isNull(), criteriaBuilder.literal(0)).otherwise(roundExpression);
expressions.add(selectExpression);

criteriaQuery.multiselect(expressions.toArray(new Expression[0]));
expressions.remove(expressions.size() - 1);

criteriaQuery.groupBy(expressions.toArray(new Expression[0]));
criteriaQuery.having(criteriaBuilder.greaterThanOrEqualTo(selectExpression, criteriaBuilder.literal(1)));

List<Tuple> list = entityManager.createQuery(criteriaQuery).getResultList();

for (Tuple tuple : list) {
    System.out.println(tuple.get(0) + " : " + tuple.get(1) + " : " + tuple.get(2));
}
criteriaQuery.having(criteriaBuilder.greaterThanOrEqualTo(selectExpression, criteriaBuilder.literal(1)));
createQuery.having(criteriaBuilder.greaterThanOrEqualTo(selectExpression, 1));
Expression<Integer> selectExpression = criteriaBuilder.<Integer>selectCase()
                                       .when(quotExpression.isNull(), 0)
                                       .<Integer>otherwise(roundExpression);
criteriaQuery.having(criteriaBuilder.greaterThanOrEqualTo(selectExpression, 0));