You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

能否通过Hibernate Criteria API操作变量?复杂动态SQL(含子查询、HAVING子句)的实现疑问

使用Hibernate Criteria API实现复杂动态SQL查询

首先直接给你核心疑问的答案:完全可以! Hibernate Criteria API支持通过Case表达式模拟SQL的IF逻辑,也能轻松操作自定义聚合变量和HAVING子句。下面我会一步步帮你实现需求里的复杂查询,包括你卡住的HAVING逻辑和自定义变量返回。

核心技巧:用CriteriaBuilder.selectCase()模拟SQL的IF函数

SQL里的IF(condition, trueVal, falseVal)在Criteria API中可以通过CriteriaBuilder.selectCase()来实现,结合聚合函数(比如max())就能完成你需要的自定义聚合逻辑。

比如你需要的MAX(IF(ba.term = 'device', 1, 0)) > 0,用Criteria API写出来就是:

// 构建Case表达式:当term等于'device'时返回1,否则返回0
Case<Integer> deviceCase = cb.selectCase()
    .when(cb.equal(bookAvailabilityJoin.get("term"), "device"), 1)
    .otherwise(0);
// 包裹成MAX聚合,然后判断大于0
Predicate deviceHaving = cb.gt(cb.max(deviceCase), 0);

完整实现:对应第二种SQL的Criteria代码

我会基于你的实体类,实现包含动态条件、关联查询、分组、HAVING子句的完整逻辑,同时兼顾结果转换的需求:

CriteriaBuilder cb = entityManager.getCriteriaBuilder();
// 用Tuple来接收多列结果,比Object[]更清晰
CriteriaQuery<Tuple> query = cb.createTupleQuery();

// 主表Root:BookFilter
Root<BookFilter> bookFilterRoot = query.from(BookFilter.class);
// 关联BookAvailability(通过bookId关联,内连接)
Join<BookFilter, BookAvailability> bookAvailabilityJoin = bookFilterRoot.join("bookId", JoinType.INNER);

// 1. 构建WHERE条件集合
List<Predicate> wherePredicates = new ArrayList<>();

// 动态filter_id条件
if (filterIds != null && !filterIds.isEmpty()) {
    wherePredicates.add(bookFilterRoot.get("filterId").in(filterIds));
}

// 动态book_id或filter_id的OR条件(对应SQL里的(ba.book_id IN (...) OR bf.filter_id IN (...)))
if (bookIds != null && !bookIds.isEmpty() || filterIdsForOr != null && !filterIdsForOr.isEmpty()) {
    List<Predicate> orPredicates = new ArrayList<>();
    if (bookIds != null && !bookIds.isEmpty()) {
        orPredicates.add(bookAvailabilityJoin.get("bookId").in(bookIds));
    }
    if (filterIdsForOr != null && !filterIdsForOr.isEmpty()) {
        orPredicates.add(bookFilterRoot.get("filterId").in(filterIdsForOr));
    }
    wherePredicates.add(cb.or(orPredicates.toArray(new Predicate[0])));
}

// 日期范围条件(对应SQL里的date_from和date_to判断)
Instant targetDate = Instant.now(); // 替换成你的动态日期
Predicate dateFromPredicate = cb.or(
    cb.lessThan(bookAvailabilityJoin.get("dateFrom"), targetDate),
    cb.isNull(bookAvailabilityJoin.get("dateFrom"))
);
Predicate dateToPredicate = cb.or(
    cb.greaterThan(bookAvailabilityJoin.get("dateTo"), targetDate),
    cb.isNull(bookAvailabilityJoin.get("dateTo"))
);
wherePredicates.add(cb.and(dateFromPredicate, dateToPredicate));

// 2. 构建GROUP BY
query.groupBy(
    bookFilterRoot.get("bookId"),
    bookFilterRoot.get("filterId")
);

// 3. 构建HAVING子句(你卡住的部分)
// 条件1:MAX(IF(ba.term = 'device', 1, 0)) > 0
Case<Integer> deviceCase = cb.selectCase()
    .when(cb.equal(bookAvailabilityJoin.get("term"), "device"), 1)
    .otherwise(0);
Predicate deviceHaving = cb.gt(cb.max(deviceCase), 0);

// 条件2:MAX(IF(ba.term = 'country', 1, 0)) > 0
Case<Integer> countryCase = cb.selectCase()
    .when(cb.equal(bookAvailabilityJoin.get("term"), "country"), 1)
    .otherwise(0);
Predicate countryHaving = cb.gt(cb.max(countryCase), 0);

// 组合HAVING条件
query.having(cb.and(deviceHaving, countryHaving));

// 4. 定义SELECT投影(返回bookId和filterId)
query.select(
    cb.tuple(
        bookFilterRoot.get("bookId").alias("bookId"),
        bookFilterRoot.get("filterId").alias("filterId")
    )
);

// 5. 执行查询
List<Tuple> results = entityManager.createQuery(query).getResultList();

实现第一种SQL的自定义变量返回

如果你需要像第一种SQL那样返回自定义聚合变量(比如device),只需要在SELECT投影里添加对应的Case聚合即可:

// 自定义device变量:MAX(IF(ba.dimension = 'device', ba.term, null))
Case<String> deviceVarCase = cb.selectCase()
    .when(cb.equal(bookAvailabilityJoin.get("dimension"), "device"), bookAvailabilityJoin.get("term"))
    .otherwise(null);
Expression<String> deviceVar = cb.max(deviceVarCase);

// 在select里添加这个变量
query.select(
    cb.tuple(
        bookFilterRoot.get("bookId").alias("bookId"),
        bookFilterRoot.get("filterId").alias("filterId"),
        deviceVar.alias("device")
    )
);

// 后续还可以在WHERE子句(如果是子查询的话)或者结果处理中使用这个变量

结果转换为Map<Integer, HashSet<Integer>>

把查询到的Tuple列表转换成你需要的Map结构很简单:

Map<Integer, HashSet<Integer>> resultMap = new HashMap<>();
for (Tuple tuple : results) {
    Integer bookId = (Integer) tuple.get("bookId");
    Integer filterId = (Integer) tuple.get("filterId");
    
    resultMap.computeIfAbsent(filterId, k -> new HashSet<>()).add(bookId);
}

对你现有代码的优化建议

  • 避免同时使用两个Root(你之前的代码里query.from(BookFilter.class)和query.from(BookAvailability.class)),这会产生笛卡尔积,应该用Join来建立关联关系。
  • 用Tuple来接收多列结果,比直接返回实体类更灵活,尤其是当你需要自定义投影的时候。
  • 动态条件尽量用集合来管理,避免重复的if判断,代码更整洁。

内容的提问来源于stack exchange,提问作者sicio

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.29 19:04:06