2014-02-16 102 views
10

当我需要保存一个对象列表,并且每个对象都应该保存在它自己的事务中时(这样如果一个失败,它们不会全部失败),我这样做:以编程方式处理Grails交易

List<Book> books = createSomeBooks() 
books.each { book -> 
    Book.withNewSession { 
    Book.withTransaction {TransactionStatus status -> 
     try { 
     book.save(failOnError: true) 
     } catch (ex) { 
     status.setRollbackOnly() 
     } 
    } 
    } 
} 

我使用Book.withNewSession,因为如果一本书无法保存并且事务回滚,则会话将无效,这将阻止后续书籍的保存。然而,有一对夫妇的问题,这种方法:

  1. 这是一个有点冗长
  2. 一个新的会话将始终为每本书创建的,即使前一本书成功

有没有更好的方法?这发生在我身上的一种可能是依赖项注入休眠SessionFactory和做到这一点,而不是

List<Book> books = createSomeBooks() 
books.each { book -> 
    try { 
    Book.withTransaction { 
     book.save(failOnError: true) 
    } 
    } catch (ex) { 
    // use the sessionFactory to create a new session, but how....? 
    } 
} 
+0

为什么你需要为每个迭代创建一个新的会话?恕我直言,足以在每次迭代只执行交易块,但所有可能发生在一个会话中.. – lukelazarovic

+0

@lukelazarovic我只需要一个新的会话,如果发生回滚。我遇到的问题是我不知道如何在catch块中创建新会话 –

回答

10

这应该这样做:如果你回滚

​​

会话没有失效,它只是被清除。因此,任何尝试访问从数据库读取的实体都会失败,但写入尚未保留的实体将会很好。但是,您确实需要使用单独的事务来防止一切后退,因此需要使用withNewTransaction。

+0

任何想法'withNewTransaction'和'withTransaction'之间的区别是什么?前者没有记录。 –

+1

[这里提到的区别](http://stackoverflow.com/a/17994623/2051952)和[计划是将它移到2.4中的文档](http://jira.grails。组织/浏览/ GRAILS-7093)。寻找格雷姆的评论。我想知道为什么它没有提供给文件。伯特在他的书中介绍了这种方法。 @Don – dmahapatro

+1

如果转储TransactionStatus对象,则会有一个名为newTransaction的布尔属性。显然,对于withTransaction,这被设置为false。并且基于行为(只使用任意setRollbackOnly来模拟失败),它似乎使用单个事务,除非使用withNewTransaction。 – jrob

0

你能先尝试验证它们,然后保存途经的所有的呢?我不确定它是否更高性能,但它可能会更清洁一些。喜欢的东西:

List<Book> books = createSomeBooks() 
List<Book> validatedBooks = books.findAll { it.validate() } 
validatedBooks*.save() 

虽然我不知道,如果.validate()承诺保存不会由于其他原因失败,而如果数据是独立的(即唯一约束传递,直到下一本书试图保存以及)。

+0

我不认为这是可靠的,因为正如您指出的那样,不能保证验证的对象实际上会保存,因为对象验证时间和保存时间之间的数据库状态可能不同。 –

0

也许你可以使用groovy元编程& grails动态域方法?

在自举:

def grailsApplication 

    def init = { 

    List.metaClass.saveCollection = { 
     ApplicationContext context = (ApplicationContext) ServletContextHolder.getServletContext().getAttribute(GrailsApplicationAttributes.APPLICATION_CONTEXT); 
     SessionFactory sf = context.getBean('sessionFactory') 
     Session hsession = sf.openSession() 
     def notSaved = [] 
     delegate.each { 
      if(!it.trySave()) { 
       notSaved << it 
       hsession.close() 
       hsession = sf.openSession() 
      } 
     } 
     hsession.close() 
     return notSaved 
    } 

    grailsApplication.getArtefacts("Domain")*.clazz.each { clazz -> 
     def meta = clazz.metaClass 
     meta.trySave = { 
      def instance = delegate 
      def success = false 
      clazz.withTransaction { TransactionStatus status -> 
       try { 
        instance.save(failOnError: true) // ', flush: true' ? 
        success = true 
       } catch (ex) { 
        status.setRollbackOnly() 
       } 
      } 
      return success 
     } 
    } 
    } 

然后:

class TheController { 
    def index() { 
     List<Book> books = createSomeBooks() 

     def notSaved = books.saveCollection() 
     books.retainAll { !notSaved.contains(it) } 

     println "SAVED: " + books 
     println "NOT SAVED: " + notSaved 
    } 
} 

当然必须有进行一些检查(例如列表中包含的域类,等等)。您也可以通过工厂倒闭具体PARAMS使这种更灵活(如flushfailOnErrordeepValidate等)