Grails 6 中如何替代 Hibernate Criteria 的 scroll()?
结论
Toto.where { ... } 返回 GORM 的 DetachedCriteria,没有 scroll() 方法。因此,调用 .where { ... }.scroll() 会报 No signature of method ... scroll()。处理大量记录时,可以按唯一且有序的键分批查询,每次读取并处理一批。
这里涉及两种 API:Grails 6.2 文档仍列有 GORM createCriteria() 的 scroll 方法,而 Hibernate 日志中的警告针对的是 legacy org.hibernate.Criteria API。调用 createCriteria().scroll() 仍可能涉及这个已弃用的 API。(Grails 6.2 createCriteria 文档)
用唯一键分批处理
下面的示例假设 Toto.id 是可排序的唯一键,并且扫描期间数据范围不会改变。每批最多加载 500 条,后续批次从上一批最后一条记录的 ID 之后开始:
def lastId = null
final int batchSize = 500
while (true) {
def query = lastId == null
? Toto.where { exemple == XXX }
: Toto.where { exemple == XXX && id > lastId }
def batch = query.list(
max: batchSize,
sort: 'id',
order: 'asc'
)
if (!batch) {
break
}
batch.each { Toto toto ->
// 处理当前记录
}
lastId = batch[-1].id
}
GORM 的 where 查询可以通过 list 参数设置 max、sort 和 order,用来限制每批的大小并保持查询顺序。(GORM for Hibernate 查询文档)
如果 id 不适合排序或比较,可以改用稳定、唯一且可比较的属性;必要时组合多个字段来排序和确定续查条件。如果扫描期间数据可能新增或变化,还需按业务要求决定这些记录是否纳入本次处理。
性能与会话注意事项
分批查询可以限制每次返回的结果数量。对于大表,优先使用索引支持的续查条件,例如 id > lastId。偏移量很大时,反复增大 offset 的分页方式可能会变慢。如果处理逻辑会修改实体,应按事务和业务需求定期 flush;处理时间较长时,也要留意 Hibernate session 持有的实体数量。
如果必须使用数据库游标式读取,应根据项目实际使用的 Hibernate 版本,通过 Hibernate 查询 API 评估游标方案,并确认事务、游标关闭和 session 生命周期。GORM where 返回的 DetachedCriteria 不提供 scroll() 方法;where 查询和 createCriteria().scroll() 使用的是不同 API。
备注:内容仅供参考。
