Обработка транзакций 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, потому что, если одна книга не сохраняется и транзакция откатывается, сеанс будет недействительным, что предотвратит сохранение последующих книг. Однако есть несколько проблем с этим подходом:

  • Это немного подробный
  • Новый сеанс всегда будет создан для каждой книги, даже если предыдущая книга выполнена успешно.

Есть ли лучший способ? Одна из возможностей, которая возникла для меня, - это вложение-слияние 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....?
  }
}

Ответ 1

Это должно сделать это:

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

Сессия недействительна, если вы откатываете ее, она просто очищается. Таким образом, любые попытки доступа к объектам, считываемым из БД, потерпят неудачу, но записи о еще не сохранившихся объектах будут в порядке. Но вам нужно использовать отдельные транзакции, чтобы один отказ не сворачивал все, следовательно, с помощью NewTransaction.

Ответ 2

Не могли бы вы сначала проверить их, а затем сохранить все те, которые прошли? Я не уверен, что это более результативно, но может быть немного чище. Что-то вроде:

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

Хотя я не уверен, что .validate() promises сохранение не будет работать по другим причинам, и если данные независимы (т.е. уникальное ограничение проходит до тех пор, пока следующая книга не попытается сохранить также).

Ответ 3

Возможно, вы могли бы использовать groovy метапрограммирование и методы динамического домена grails?

В Bootstrap:

    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
    }
}

Конечно, должны выполняться некоторые проверки (например, список содержит классы домена и т.д.). Вы также можете перейти к закрытию определенных параметров, чтобы сделать его более гибким (например, flush, failOnError, deepValidate и т.д.)