'Calling Hibernate in background threads in grails
I am trying to create a specific type of background processing setup in a grails application.
- A fixed size thread pool exists only for the duration of the batch of jobs
- A single session is maintained by each thread
- Each job runs in a separate transaction
I am trying to start the job as follows:
int poolSize = 10
ThreadFactory factory = new MyThreadFactory (Executors.defaultThreadFactory())
ExecutorService pool = Executors.newFixedThreadPool (poolSize, factory)
(1..100).each { i ->
pool.submit {
try {
MyDomainClass.withTransaction {
doSomeWork(i)
}
} catch (Exception e) {
log.error "error in job ${i}", e
}
}
}
MyThreadFactory creates threads which have a hibernate session attached for the duration of the thread.
class MyThreadFactory implements ThreadFactory {
ThreadFactory delegate
PersistenceContextInterceptor persistenceInterceptor
MyThreadFactory (ThreadFactory delegate) {
this.delegate = delegate
ApplicationContext applicationContext = ApplicationHolder.getApplication().getMainContext()
persistenceInterceptor = applicationContext.getBean("persistenceInterceptor");
}
Thread newThread (Runnable work) {
return delegate.newThread {
persistenceInterceptor.init()
try {
work.run()
} finally {
persistenceInterceptor.flush()
persistenceInterceptor.destroy()
}
}
}
}
It seems to work, however I will get the following error the first time I run the batch job. (Subsequent jobs run without incident)
groovy.lang.MissingMethodException: No signature of method: static MyDomainClass.save() is applicable for argument types: (java.util.LinkedHashMap) values: [[flush:false]]
Possible solutions: save(), save(java.util.Map), save(java.lang.Boolean), wait(), any(), wait(long)
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at ...
I have tried replacing the persitanceInterceptor with MyDomainClass.withNewSession {}, with no effect.
It appears as though GORM methods are not being injected into my domain classes.
Can anyone see what I'm doing wrong, and why running the batch job again allows it to succeed?
@fixitagain For completeness the work takes this form:
doSomeWork = { id ->
MyDomainClass a = MyDomainClass.findById (id)
a.value = lotsOfWork()
a.save()
}
I believe the missing save is a red herring, as I tried wrapping the operating in a transaction, and then get an error saying 'DomainClass.withTransaction(Closure)' is not defined.
It looks like there might be a race condition where the first job fails to run, but all following jobs run successfully after (something?) has finished starting up.
Solution 1:[1]
Instead of trying to create your own thread, it might be advisable to use the executor plugin for Grails. It injects the necessary hibernate session to the threads you create, also it is configurable with respect to the executor it uses, number of threads etc. I use it in production with quartz jobs and other scenarios and it works just fine.
Grails Executor Plugin If you have reservations in using it, you can take a look at its code before you write your own threading strategy.
Solution 2:[2]
I can't make out from the code or the naming convention, but are you sure you are calling save on an instance of the domain class?
Solution 3:[3]
The missing method exception implies that you're calling save on the Class, not the instance.
edit: GORM has already applied the additional methods, as you can see in the suggested solution method names.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | Maicon Mauricio |
Solution 2 | bluesman |
Solution 3 | codelark |