Reputation: 187499
When I need to save a list of objects, and each object should be saved in it's own transaction (so that if one fails they don't all fail), I do it like this:
List<Book> books = createSomeBooks()
books.each { book ->
Book.withNewSession {
Book.withTransaction {TransactionStatus status ->
try {
book.save(failOnError: true)
} catch (ex) {
status.setRollbackOnly()
}
}
}
}
I use Book.withNewSession
because if one book fails to save and the transaction is rolled back, the session will be invalid which will prevent subsequent books from saving. However, there are a couple of problems with this approach:
Is there a better way? One possibility that occurred to me is to dependency-inject the Hibernate SessionFactory
and do this instead
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....?
}
}
Upvotes: 10
Views: 3900
Reputation: 226
This should do it:
List<Book> books = createSomeBooks()
books.each { book ->
Book.withNewTransaction {TransactionStatus status ->
try {
book.save(failOnError: true)
} catch (ex) {
status.setRollbackOnly()
}
}
}
The session isn't invalid if you rollback, it is just cleared. So any attempts to access entities read from the DB would fail, but writes of not-yet-persisted entities will be just fine. But, you do need to use separate transactions to keep one failure from rolling back everything, hence the withNewTransaction.
Upvotes: 11
Reputation: 5989
Maybe you could use groovy meta-programming & grails dynamic domain methods?
In 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
}
}
}
And then:
class TheController {
def index() {
List<Book> books = createSomeBooks()
def notSaved = books.saveCollection()
books.retainAll { !notSaved.contains(it) }
println "SAVED: " + books
println "NOT SAVED: " + notSaved
}
}
Of course there must be some checks performed (e.g. list contains domain classes, etc.). You can also pass to closures specific params to make this more flexible (e.g. flush
, failOnError
, deepValidate
, etc.)
Upvotes: 0
Reputation: 33963
Could you try validating them first, and then saving all the ones that passed? I'm not sure if it's any more performant, but it may be a little cleaner. Something like:
List<Book> books = createSomeBooks()
List<Book> validatedBooks = books.findAll { it.validate() }
validatedBooks*.save()
Although I'm not sure if .validate()
promises the save won't fail for other reasons, and if the data is independent (ie a unique constraint passes until the next book tries to save as well).
Upvotes: 0