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));