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

如何使用Hibernate Criteria投影至含List字段的POJO

解决Hibernate Criteria获取包含集合字段的BillEntity问题

你现在的问题是用Hibernate Criteria的投影(Projection)来获取包含paymentIds列表的BillEntity,但默认的投影只处理单值属性,集合字段需要特殊处理。下面给你几种可行的方案:

方案1:放弃投影,直接通过Fetch加载完整实体

如果你的主要目的是获取完整的BillEntity(包括集合字段),其实没必要用投影——只要你的实体映射配置正确(比如@ElementCollection或@OneToMany关联paymentIds),可以直接通过设置Fetch模式来加载集合:

DetachedCriteria criteria = DetachedCriteria.forClass(BillEntity.class);
// 假设paymentIds是集合属性,设置LEFT JOIN来加载集合,避免N+1查询
criteria.setFetchMode("paymentIds", FetchMode.JOIN);
// 如果有其他查询条件,在这里添加
// criteria.add(Restrictions.eq("xxx", value));

// 执行查询,直接得到包含paymentIds的BillEntity列表
List<BillEntity> bills = criteria.getExecutableCriteria(session).list();

这种方式最简单,Hibernate会自动帮你处理集合的加载,不需要手动处理投影和结果转换。

方案2:用SQL聚合函数+自定义ResultTransformer处理投影

如果你因为业务需求必须使用投影(比如有复杂的Join或字段筛选),可以通过数据库的聚合函数(比如MySQL的GROUP_CONCAT、PostgreSQL的STRING_AGG)把集合字段拼接成字符串,再通过自定义ResultTransformer把字符串转回List<Integer>:

示例代码(MySQL环境):

DetachedCriteria criteria = DetachedCriteria.forClass(BillEntity.class);
// 左连接到paymentIds对应的关联表,别名设为payments
criteria.createAlias("paymentIds", "payments", JoinType.LEFT_OUTER_JOIN);

// 构建投影列表,注意要按Bill的id分组
ProjectionList projectionList = Projections.projectionList()
    .add(Projections.groupProperty("id"), "id") // 按id分组,确保每个Bill只返回一行
    .add(Projections.groupProperty("amount"), "amount")
    .add(Projections.sqlProjection(
        "GROUP_CONCAT(DISTINCT payments.id SEPARATOR ',') as paymentIds", // 聚合paymentId为字符串
        new String[]{"paymentIds"},
        new Type[]{StandardBasicTypes.STRING}
    ), "paymentIds");

criteria.setProjection(projectionList);

// 自定义ResultTransformer,把拼接的字符串转成List<Integer>
criteria.setResultTransformer(new AliasToBeanResultTransformer(BillEntity.class) {
    @Override
    public Object transformTuple(Object[] tuple, String[] aliases) {
        BillEntity bill = (BillEntity) super.transformTuple(tuple, aliases);
        String paymentIdsStr = (String) tuple[2]; // 对应投影中paymentIds的位置
        if (paymentIdsStr != null && !paymentIdsStr.isEmpty()) {
            // 分割字符串并转成Integer列表
            List<Integer> paymentIds = Arrays.stream(paymentIdsStr.split(","))
                .map(Integer::parseInt)
                .collect(Collectors.toList());
            bill.setPaymentIds(paymentIds);
        } else {
            bill.setPaymentIds(new ArrayList<>()); // 空集合处理
        }
        return bill;
    }
});

// 执行查询得到结果
List<BillEntity> bills = criteria.getExecutableCriteria(session).list();

注意事项:

  • 如果是PostgreSQL,把GROUP_CONCAT换成STRING_AGG(payments.id, ',')
  • 必须用groupProperty按Bill的主键分组,否则会返回重复的Bill行
  • 要处理paymentIds为空的情况,避免空指针异常

方案3:先投影单值,再手动合并集合结果

如果不想用SQL聚合函数,也可以先投影出每个Bill和对应的单个paymentId,再通过代码手动把同一个Bill的paymentId合并成列表:

步骤1:创建临时DTO类

public class BillDTO {
    private int id;
    private double amount;
    private Integer paymentId;

    // 构造函数、getter、setter省略
}

步骤2:投影并转换为DTO,再合并成BillEntity

DetachedCriteria criteria = DetachedCriteria.forClass(BillEntity.class);
criteria.createAlias("paymentIds", "payments", JoinType.LEFT_OUTER_JOIN);

ProjectionList projectionList = Projections.projectionList()
    .add(Projections.property("id"), "id")
    .add(Projections.property("amount"), "amount")
    .add(Projections.property("payments.id"), "paymentId");

criteria.setProjection(projectionList);
// 用Transformers把结果转成BillDTO
criteria.setResultTransformer(Transformers.aliasToBean(BillDTO.class));

List<BillDTO> dtoList = criteria.getExecutableCriteria(session).list();

// 手动合并DTO为BillEntity
Map<Integer, BillEntity> billMap = new HashMap<>();
for (BillDTO dto : dtoList) {
    BillEntity bill = billMap.get(dto.getId());
    if (bill == null) {
        bill = new BillEntity();
        bill.setId(dto.getId());
        bill.setAmount(dto.getAmount());
        bill.setPaymentIds(new ArrayList<>());
        billMap.put(dto.getId(), bill);
    }
    if (dto.getPaymentId() != null) {
        bill.getPaymentIds().add(dto.getPaymentId());
    }
}

// 最终得到包含完整paymentIds的BillEntity列表
List<BillEntity> bills = new ArrayList<>(billMap.values());

这种方式不需要依赖数据库的聚合函数,兼容性更好,但需要额外写代码处理合并逻辑。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.26 08:25:29