2014-02-16 4 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. новая сессия всегда будет создаваться для каждой книги, даже если предыдущая книга удалась

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

Зачем вам нужно создавать новый сеанс для каждой итерации? IMHO достаточно для выполнения только с блокомTransaction на каждой итерации, но все может произойти за один сеанс. – lukelazarovic

+0

@lukelazarovic Мне нужен только новый сеанс, если произойдет откат. Проблема, с которой я сталкиваюсь, заключается в том, что я не знаю, как создать новый сеанс в блоке catch. –

ответ

10

Это следует сделать это:

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

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

Возможно, вы сможете использовать метафорическое метаопределение & grails dynamic domain methods?

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

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

Смежные вопросы