Grails中的多线程 - 将域对象传递到每个线程中会导致某些字段随机为null



我正在尝试通过引入并行编程来加快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)

最新更新