我正在尝试通过引入并行编程来加快Grails应用程序中的进程。这个特定的过程需要筛选数千个文档,从中收集必要的数据并将其导出到Excel文件。
经过数小时尝试跟踪为什么此过程如此缓慢的原因,我确定该过程必须做很多工作,以从每个域对象收集数据的特定部分。(例如:域对象在其中包含数据列表,并且此过程在这些列表中获取每个索引,并将其附加到带有逗号的字符串中,以在Excel表的单元格中制作一个漂亮的外观,排序的列表。还有更多示例但是这些不应该很重要。)
因此,任何不简单的数据访问(document.id.id,document.name等)都导致此过程需要很长时间。
我的想法是为每个文档使用线程以异步获取所有这些数据,当每个线程收集数据时,它可以返回主线程并将其放置在Excel表中,现在全部都具有简单的数据访问,因为该线程已经收集了所有数据。
这似乎在起作用,但是我有一个错误的域对象和线程。每个线程都以其相应的文档域对象传递,但是无论出于何种原因,文档域对象都会随机将其数据的一部分更改为null。
例如:在文档传递到线程中之前,域对象的一个部分将具有像这样的列表:[[我们,英格兰,威尔士],随机随机,该列表在线程:[我们,null,威尔士]。这在任何随机时间都发生在域对象的任何随机部分。
生成线程:
def docThreadPool = Executors.newFixedThreadPool(1)
def docThreadsResults = new Future<Map>[filteredDocs.size()]
filteredDocs.each {
def final document = it
def future = docThreadPool.submit(new DocumentExportCallable(document))
docThreadsResults[docCount] = future
docCount++
}
从线程中获取数据:
filteredDocs.each {
def data = docThreadsResults[count].get()
build excel spreadsheet...
}
Document ExportCallable类:
class DocumentExportCallable implements Callable {
def final document
DocumentExportCallable(document) {
this.document = document
}
Map call() {
def data = [:]
code to get all the data...
return data
}
}
编辑:如下所示,如果我可以向您展示域对象,那将很有用。但是我无法做到这一点。但是,你们问我有关域对象的事实使我认为这可能是问题所在的地方。事实证明,域对象的每个部分在线程中随机混乱的域中是"映射"内部域对象中的一个变量,该变量使用SQL JONINS来获取这些变量的数据。我刚刚意识到懒惰与渴望在圣杯中拿来的东西。我想知道这可能是问题所在的地方...默认情况下,它设置为懒惰的提取,因此每个线程对DB的持续访问可能是问题出现的地方。我相信找到一种将其更改为渴望获得的方法可能会解决问题。
我有答案,说明为什么这些空值随机出现。一切似乎现在都在起作用,我的实现现在的执行速度比以前的实现更快!
事实证明,当您访问这些字段时,即使您获得对象本身,我并不知道具有1-M关系的Grail域对象可以单独访问SQL调用。这一定会导致这些线程进行未透射安全的SQL调用,从而创建了这些随机空值。在此特定情况下,将这些1-M的属性设置为急切地提取的问题。
对于以后阅读的任何人,您都需要阅读懒惰与渴望获取以获得更好的理解。
至于代码:
这些是我域对象中问题的1-M变量:
static hasMany = [propertyOne : OtherDomainObject, propertyTwo : OtherDomainObject, propertyThree : OtherDomainObject]
我在我的数据库调用中添加了一个标志,该标志将为此特定情况启用此代码,因为我不希望这些属性始终在整个应用程序中始终被获取:
if (isEager) {
fetchMode 'propertyOne', FetchMode.JOIN
fetchMode 'propertyTwo', FetchMode.JOIN
fetchMode 'propertyThree', FetchMode.JOIN
setResultTransformer Criteria.DISTINCT_ROOT_ENTITY
}
我的歉意,但是目前我还不记得为什么我必须将" setResultTransFormer"放在上面的代码中,但是没有问题。也许以后有人可以解释这一点,否则我确信Google搜索会解释。
发生了什么事是您的Grails域对象从Hibernate会话分离,因此当您的线程尝试加载Lazy属性时,请点击LazyInitiationException。
切换到急切的获取对您有用,但这可能不是每个人的选择。您还可以做的是使用Grails async任务框架,而是它在会话处理中构建的。请参阅https://async.grails.org/latest/guide/index.html
但是,即使使用Grails Async任务传递对象之间的对象,也似乎将其分离,因为新线程将具有新的绑定会话。我在新线程上找到.attach()
或.merge()
的解决方案将其与调用线程上的会话绑定。
我相信最佳解决方案是让Hibernate在新线程上加载对象,这意味着您的代码段中您将在会话中传递文档ID和Document.get(id)
。