【问题标题】:Handling Grails transactions programmatically以编程方式处理 Grails 事务
【发布时间】: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,因为如果一本书保存失败并且事务回滚,会话将无效,这将阻止后续书籍保存。但是,这种方法存在几个问题:

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

有没有更好的方法?我想到的一种可能性是依赖注入 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 块中创建一个新会话

标签: hibernate grails transactions grails-orm


【解决方案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 hereplan is to move this to documents in 2.4。寻找 Graeme 的 cmets。我想知道为什么它没有做成文件。 Burt 在他的书中介绍了这种方法。 @唐
  • 如果您转储 TransactionStatus 对象,则有一个名为 newTransaction 的布尔属性。显然对于 withTransaction,这设置为 false。并且基于行为(仅使用任意 setRollbackOnly 来模拟失败),它似乎确实使用单个事务,除非您使用 withNewTransaction。
  • 另外,我看到你之前stackoverflow.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 等)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2014-03-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多