为了使用Grails(2.5.0)和MongoDB插件(3.0.2)迭代MongoDB(2.6.9)集合中的所有文档,我创建了一个这样的forEach:
class MyObjectService {
def forEach(Closure func) {
def criteria = MyObject.createCriteria()
def ids = criteria.list { projections { id() } }
ids.each { func(MyObject.get(it)) }
}
}
然后我这样做:
class AnalysisService{
def myObjectService
@Async
def analyze(){
MyObject.withStatelessSession {
myObjectService.forEach { myObject ->
doSomethingAwesome(myObject)
}
}
}
}
这很有效......直到我点击一个大的集合(> 500K文档),此时抛出了一个CommandFailureException,因为聚合结果的大小大于16MB。
Caused by CommandFailureException: { "serverUsed" : "foo.bar.com:27017" , "errmsg" : "exception: aggregation result exceeds maximum document size (16MB)" , "code" : 16389 , "ok" : 0.0}
在阅读本文时,我认为处理这种情况的一种方法是在MongoDB端运行的聚合函数中使用选项allowDiskUse
,以便16MB内存限制不会适用,我可以获得更大的聚合结果。
如何将此选项传递给条件查询?我一直在阅读Grails MongoDB插件的文档和Javadoc,但我似乎无法找到它。是否有另一种方法来处理通用问题(迭代大量域对象的所有成员)?
答案 0 :(得分:0)
对于MongoDB Grails插件的当前实现,这是不可能的。 https://github.com/grails/grails-data-mapping/blob/master/grails-datastore-gorm-mongodb/src/main/groovy/org/grails/datastore/mapping/mongo/query/MongoQuery.java#L957
如果查看上面的行,那么您将看到默认选项用于构建AggregationOptions实例,因此没有方法可以提供选项。
但是还有另一种使用Groovy的元类的hackish方法。我们这样做..: - )
在为您的服务编写标准之前,先存储builder()
方法的原始方法参考:
MetaMethod originalMethod = AggregationOptions.metaClass.static.getMetaMethod("builder", [] as Class[])
然后,替换构建器方法以提供实现。
AggregationOptions.metaClass.static.builder = { ->
def builderInstance = new AggregationOptions.Builder()
builderInstance.allowDiskUse(true) // solution to your problem
return builderInstance
}
现在,您的服务方法将使用条件查询进行调用,并且不应导致您收到的聚合错误,因为我们尚未将allowDiskUse
属性设置为true。
现在,重新恢复原始方法,使其不会影响任何其他调用(可选)。
AggregationOptions.metaClass.static.addMetaMethod(originalMethod)
希望这有帮助!
除此之外,为什么要在forEach
方法中提取所有ID,然后使用get()
方法重新获取实例?您正在浪费会影响性能的数据库查询。此外,如果您遵循此操作,则无需进行上述更改。
具有相同的示例:(更新)
class MyObjectService {
void forEach(Closure func) {
List<MyObject> instanceList = MyObject.createCriteria().list {
// Your criteria code
eq("status", "ACTIVE") // an example
}
// Don't do any of this
// println(instanceList)
// println(instanceList.size())
// *** explained below
instanceList.each { myObjectInstance ->
func(myObjectInstance)
}
}
}
(我没有添加AnalysisService
的代码,因为没有变化)
***此时的重点在于此。因此,无论何时在域类中编写任何条件(没有投影和mongo),执行条件代码后,Grails / gmongo都不会立即从数据库中获取记录,除非您调用某些方法,如toString()
,'size( )or
dump()`on。
现在,当您在该实例列表中应用each
时,您实际上并不会将所有实例加载到内存中,而是在场景后面迭代Mongo Cursor,而在MongoDB中,游标使用批量从数据库中提取记录。非常安全。因此,您可以安全地直接调用每个标准结果,除非您调用任何触发从数据库加载所有记录的方法,否则不会炸毁JVM。
每当你编写没有投影的任何标准时,你将得到一个MongoResultList
的实例,并且有一个名为initializeFully()
的方法正在toString()
和其他方法上调用。但是,您可以看到MongoResultList
正在实现迭代器,而迭代器又调用MongoDB游标方法来迭代大型集合,这又是内存安全的。
希望这有帮助!