以编程方式处理 Grails 事务

Posted

技术标签:

【中文标题】以编程方式处理 Grails 事务【英文标题】:Handling Grails transactions programmatically 【发布时间】:2014-03-15 09:07:42 【问题描述】:

当我需要保存一个对象列表,并且每个对象都应该保存在它自己的事务中(这样如果一个失败,它们不会全部失败),我会这样做:

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

我使用Book.withNewSession,因为如果一本书保存失败并且事务回滚,会话将无效,这将阻止后续书籍保存。但是,这种方法存在几个问题:

    有点冗长 将始终为每本书创建一个新会话,即使前一本书成功了

有没有更好的方法?我想到的一种可能性是依赖注入 Hibernate 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....?
  

【问题讨论】:

为什么需要为每次迭代创建一个新会话?恕我直言,在每次迭代中只执行 withTransaction 块就足够了,但所有这些都可以在一个会话中发生.. @lukelazarovic 如果发生回滚,我只需要一个新会话。我遇到的问题是我不知道如何在 catch 块中创建一个新会话 【参考方案1】:

应该这样做:

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

回滚会话不是无效的,它只是被清除。因此,任何访问从数据库读取的实体的尝试都会失败,但写入尚未持久化的实体会很好。但是,您确实需要使用单独的事务来防止一次失败回滚所有内容,因此需要使用 withNewTransaction。

【讨论】:

知道withNewTransactionwithTransaction 之间的区别是什么吗?前者没有记录。 Difference mentioned here 和 plan is to move this to documents in 2.4。寻找 Graeme 的 cmets。我想知道为什么它没有做成文件。 Burt 在他的书中介绍了这种方法。 @唐 如果您转储 TransactionStatus 对象,则有一个名为 newTransaction 的布尔属性。显然对于 withTransaction,这设置为 false。并且基于行为(仅使用任意 setRollbackOnly 来模拟失败),它似乎确实使用单个事务,除非您使用 withNewTransaction。 另外,我看到你之前***.com/questions/21638706/… 也问过类似的问题,似乎更详细。您是否因为试图访问以前从数据库中读取的实体而得到延迟初始化异常,因为您试图更新它们?如果是这样,也许您可​​以检索每个现有记录作为事务的一部分,而不是在开始时一次全部检索,以确保在写入失败时它们不会全部被清除?【参考方案2】:

您可以尝试先验证它们,然后保存所有通过的吗?我不确定它是否更高效,但它可能会更干净一些。比如:

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

虽然我不确定.validate() 是否承诺保存不会因其他原因而失败,以及数据是否独立(即,唯一的约束通过,直到下一本书也尝试保存)。

【讨论】:

我不认为这是可靠的,因为正如您所指出的,无法保证经过验证的对象会实际保存,因为数据库的状态在对象被验证时和它被保存了。【参考方案3】:

也许您可以使用 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
    

当然必须执行一些检查(例如列表包含域类等)。您还可以将特定参数传递给闭包以使其更灵活(例如flushfailOnErrordeepValidate 等)

【讨论】:

以上是关于以编程方式处理 Grails 事务的主要内容,如果未能解决你的问题,请参考以下文章

作为管理员,如何使用 Grails Spring Security 以编程方式更改用户密码?

Grails 2.3 IntegrationSpec 不能是事务性错误

Grails:在控制器中设置啥以达到 URL 映射中的错误处理?

Grails下如何防止异常导致事务回滚?

如何绕过 Grails 异常处理以使用 Spring Oauth2?

mysql上的Grails事务setSavePoint方法导致异常