Grails使用Criteria分页查询无法用listDistinct去重的解决方案求助
Grails Criteria查询带分页时的去重解决方案
你当前使用的写法存在的核心问题是:Hibernate处理Criteria的distinct和listDistinct时,默认会先拉取所有符合条件的全量数据,在内存中完成去重后再应用max、offset分页参数,会导致分页结果大小不符合预期、性能损耗过高。
可选用以下3种可行方案解决:
方案1:使用group by分组在数据库层面完成去重
直接在数据库层面对目标去重列分组,先去重再执行分页逻辑,分页参数可直接生效。如果需要获取完整Domain对象可以搭配结果转换器使用。
代码示例:
注意:如果数据库开启了Domain.createCriteria().list(max: pageSize, offset: offset) { projections { // 按需要去重的列分组,重复数据仅保留一条 groupProperty('需要去重的列名') // 补充其他需要返回的列,非分组列可按需搭配聚合函数(max/min/first)指定取重复项中的哪条 min('id') property('其他需要返回的字段1') property('其他需要返回的字段2') } // 其他查询条件、排序规则正常编写 order('id', 'asc') // 如果需要返回Domain对象而非投影数组,加上以下转换器 resultTransformer = org.hibernate.transform.Transformers.aliasToBean(Domain.class) }ONLY_FULL_GROUP_BY校验,所有非分组的返回列都需要包裹聚合函数,避免SQL执行报错方案2:子查询先筛选去重后的ID再分页
适合需要返回完整Domain对象、不想处理分组校验的场景,先通过子查询拿到去重后的数据主键ID列表,再外层查询用ID筛选后分页,分页逻辑完全生效。
代码示例:// 子查询:按目标列去重,获取所有符合条件的唯一数据ID def distinctIds = Domain.createCriteria().list() { projections { // 重复数据取ID最小的一条,可按需替换为max(id)取最新的 min('id') groupProperty('需要去重的列名') } // 公共查询条件写在这里 }*.get(0) // 外层查询:用ID筛选后分页 def result = Domain.createCriteria().list(max: pageSize, offset: offset) { 'in'('id', distinctIds) // 其他查询条件、排序规则 order('id', 'asc') }方案3:配置Hibernate参数让distinct透传到数据库
如果你使用的Hibernate版本≥5.2,可以直接配置全局参数让distinct直接传递到数据库执行,数据库层面先去重再分页,原有代码几乎不需要修改。
在grails-app/conf/application.yml中添加以下配置:
配置生效后你原有代码的grails: hibernate: config: hibernate.query.pass_distinct_through: truedistinct('需要去重的列')即可直接生效,分页参数正常生效。
内容的提问来源于stack exchange,提问作者Matin Ghiasvand
相关产品推荐
相关产品推荐

