In my Grails application, I am creating a lot of individual DomainObject
during a single call of a save link:
import grails.converters.JSON
class SaveController {
def saveService
def save() {
println (new Date()) + " Enter SaveController.save()."
Map status = saveService.save(params)
println (new Date()) + " Finished calling saveService.save()."
render status as JSON
}
}
The link calls a service to persist each of its content to the database:
import org.codehaus.groovy.grails.web.servlet.mvc.GrailsParameterMap
class SaveService {
Map save(GrailsParameterMap params) {
List<DomainObject> dos = new ArrayList()
println (new Date()) + " Iterate list and create DomainObject per element."
params.list.each { l ->
dos.push(new DomainObject(l))
}
println (new Date()) + " Started saving all DomainObjects."
dos.each { d ->
d.save()
}
println (new Date()) + " Finished saving all ${dos.size()} DomainObjects."
return [done: true]
}
}
The problem is it chokes on a certain part of the process, evident to the prints I have placed:
Fri Jun 01 21:47:55 SGT 2018 Enter SaveController.save().
Fri Jun 01 21:47:55 SGT 2018 Iterate list and create DomainObject per element.
Fri Jun 01 21:47:55 SGT 2018 Started saving all DomainObject.
Fri Jun 01 21:48:13 SGT 2018 Finished saving all 4316 DomainObject.
// Waits from some minutes before reaching the next println.
Fri Jun 01 21:52:02 SGT 2018 Finished calling saveService.save().
My guess is that it takes it time persisting/committing each individual .save()
call to the database. My question is, is there any way that I can modify the code so that it will commit all of the elements of List<DomainObject> dos
once as a whole, thus eliminating the long time needed before exiting saveService.save()
.
I had tried changing d.save()
into d.save(flush: true)
but the speed remains the same. I am looking for maybe a groovy Closure
that will automatically commit the transactions inside it when it's done, something like this (I don't know if there's an actual Closure
like this):
commitOnceDone { session ->
dos.each { d ->
d.save()
}
}
// Once reached here, will fire a single COMMIT; that will affect
// all transactions that happened inside the Closure
You need to batch the inserts. You need two things to do that :
Enable batching at Hibernate / JDBC level : in application.yml, set hibernate.jdbc.batch_size: 50
Do not let the database generate the primary key, else you won't be able to batch inserts. See primary key generation strategy : http://docs.grails.org/3.1.1/ref/Database%20Mapping/id.html and http://docs.jboss.org/hibernate/core/3.6/reference/en-US/html/mapping.html#mapping-declaration-id-generator
For really simple usage / testing, you can use this in application.groovy :
grails.gorm.default.mapping = {
'id'(generator: 'increment') // The application instance will generate the ID, so it cannot be clustered
}
Fri Jun 01 21:48:13 SGT 2018 Finished saving all 4316 DomainObject.
Note that at the time you are printing this, nothing is saved in the DB since you haven't flushed the Hibernate session yet. To batch, do not put 'flush: true' in the .save() call so Hibernate will batch the inserts if you setup your application to batch.
There are other optimizations possible, but this one should be the biggest one.