2015-02-10 25 views
0

我有大量的數據,我希望使用GORM加載到數據庫中。Grails DuplicateKeyException/NonUniqueObjectException異步承諾內批量加載時

class DbLoadingService { 

    static transactional = false  
    // these are used to expedite the batch loading process 
    def sessionFactory 
    def propertyInstanceMap = org.codehaus.groovy.grails.plugins.DomainClassGrailsPlugin.PROPERTY_INSTANCE_MAP 

    // these are example services that will assist in the parsing of the input data  
    def auxLoadingServiceA 
    def auxLoadingServiceB 

    def handleInputFile(String filename) { 
     def inputFile = new File(filename) 
     // parse each line and process according to record type 
     inputFile.eachLine { line, lineNumber -> 
      this.handleLine(line, lineNumber) 
     } 
    } 


    @Transactional 
    def handleLine(String line, int lineNumber) { 
     // do some further parsing of the line, based on its content 
     // example here is based on 1st 2 chars of line 
     switch (line[0..1]) { 
      case 'AA': 
       auxLoadingServiceA.doSomethingWithLine(line) 
       break; 

      case 'BB': 
       auxLoadingServiceB.doSomethingElseWithLine(line) 
       break; 

      default: 
       break; 

     } 
     if (lineNumber % 100 == 0) cleanUpGorm() 
    } 

    def cleanUpGorm() { 
     def session = sessionFactory.getCurrentSession() 
     session.flush() 
     session.clear() 
     propertyInstanceMap.get().clear() 
    } 

} 

class AuxLoadingServiceA { 
    static transactional = false 

    doSomethingWithLine(String line) { 
     // do something here 
    } 
} 

class AuxLoadingServiceB { 
    static transactional = false 

    doSomethingElseWithLine(String line) { 
     // do something else here 
    } 
} 

我故意只對每一行的負載做了頂級服務transactional。實際上在頂層下有很多級別的服務,而不僅僅是所示的單個Aux A服務層。因此,我不希望產生多層事務的開銷:我認爲我應該只需要1.

加載到數據庫中的數據模型包含一對具有hasMany/belongsTo關係的域對象。與域對象的這種交互是在子層內完成的,並且不會顯示在我的代碼中以保持示例的可管理性。

,這似乎是引起該問題的域對象類似於此:

class Parent { 
    static hasMany = [children: Child] 
    static mapping = { 
     children lazy: false 
     cache true 
    } 
} 

class Child { 
    String someValue 
    // also contains some other sub-objects 

    static belongsTo = [parent : Parent] 

    static mapping = { 
     parent index: 'parent_idx' 
     cache true 
    } 
} 

需要所示的cleanupGorm()方法,否則服務研磨到大量的線路後完全停止。當我移動加載到一個異步過程,這樣一旦

// Called from with a service/controller 
dbLoadingService.handleInputFile("someFile.txt") 

然而,:

def promise = task { 
    dbLoadingService.handleInputFile("someFile.txt") 
} 

我得到

當我啓動數據庫負載,一切工作完全按預期一個DuplicateKeyException/NonUniqueObjectException:

error details: org.springframework.dao.DuplicateKeyException: A different object with the same identifier value was already associated with the session : [com.example.SampleDomainObject#1]; nested exception is org.hibernate.NonUniqueObjectException: A different object with the same identifier value was already associated with the session : [com.example.SampleDomainObject#1] 

所以,我的問題是,什麼是關於將大量數據異步加載到Grails DB中的最佳實踐?爲了確保內存中的對象在會話中保持一致,是否需要執行刷新/清除會話的操作?緩存對象時是否需要完成某些操作?

+0

首先,你不應該做這樣的重批處理。使用像Spring Batch這樣的真正的批處理框架。但是,您是否嘗試過爲每個任務使用新的休眠會話?這可能有幫助。 – 2015-02-10 19:37:38

+0

新的hibernate會話,意味着這裏有一個新的hibernate會話嗎? inputFile.eachLine {line,lineNumber - > this.handleLine(line,lineNumber) } – John 2015-02-10 19:42:30

+0

是的,使用這個:http://grails.github.io/grails-doc/latest/ref/Domain%20Classes/withNewSession。 html – 2015-02-10 19:45:06

回答

0

解決方案是按照@JoshuaMoore的建議進行操作,並使用新會話。此外,還有一個對象是從一個事務之外引用的域對象的引用,然後在新會話中沒有對其調用merge(),從而導致錯誤。

def obj = DomainObject.findBySomeProperty('xyz') 

// now start new session 

obj.someProperty // causes exception 
obj = obj.merge() 
obj.someProperty // doesn't cause an exception 

約書亞的評論促使我深入到文檔的Hibernate(https://docs.jboss.org/hibernate/orm/3.6/reference/en-US/html/transactions.html

具體而言,從第13章:

一個SessionFactory是昂貴的,TO-創建,線程安全對象, 打算由所有應用程序線程共享。它通常在應用程序啓動時從配置實例創建一次, 。

會話是一個便宜的非線程安全對象,應該使用 一次,然後丟棄:單個請求,對話或單個工作單元。除非需要,否則會話將不會獲得JDBC連接或數據源。在使用前它不會消耗任何資源 。

可能會感興趣的人那是什麼我已經看到在隨物體的數目該批式負載的性能逐漸降解被解析,甚至伯特貝克威思here建議的性能優化:和解釋Ted Naleid here的更多詳細信息。

因此,使用文檔中的提示,性能問題的答案不是嘗試使用會話進行所有處理 - 而是使用它進行少量處理,然後將其丟棄並創建一個新的一個。

當我刪除了我的問題的cleanupGorm()方法,並以下列取代它,我得到了一個6倍的性能增加,絕對沒有與批量大小增加了加載時間,甚至數百萬10S後記錄被解析:

// somewhere in the service method that is doing the batch parse 
def currentSession = sessionFactory.openSession() 

// start some form of batch parse, perhaps in a loop 

    // do work here 
    // periodically, perhaps in the %N way shown above 
    currentSession.flush() 
    currentSession.close() 
    currentSession = sessionFactory.openSession() 

// end of loop 

當我需要包裹起來的東西在跨越服務交易,我做了以下內容:

currentSession = sessionFactory.openSession() 
currentSession.beginTransaction() 

// start loop 
// do work 

// when we want to commit 
def tx = currentSession?.getTransaction() 
if (tx?.isActive()) tx.commit() 
currentSession?.close() 

// if we're in a loop and we need a new transaction 
currentSession = sessionFactory.openSession() 
currentSession.beginTransaction() 

雖然我接受的是使用類似SPR ing批可能會更好,它會涉及扔掉大量的代碼,否則按預期工作。我將在下次需要這樣做時考慮這一點,但同時,希望這可能對需要使用Grails進行大規模批處理的其他人有用,並且發現批處理大小的性能會降低。

約書亞記錄:非常感謝你的幫助,非常感謝!