Обработка транзакций 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
и т.д.)