问题
I've introduced a TransactionService that I use in my controllers to execute optimistic transactions. It should
- try to execute a given transaction (= closure)
- roll it back if it fails and
- try it again if it fails
It basically looks like this:
class TransactionService {
transactional = false // Because withTransaction is used below anyway
def executeOptimisticTransaction(Closure transaction) {
def success = false
while (!success) {
anyDomainClass.withTransaction { status ->
try {
transaction()
success = true
} catch(Exception e) {
status.setRollbackOnly()
}
}
}
}
}
It is a little more complex, e.g. it uses different Thread.sleeps before trying again and aborts at some stage, but that doesn't matter here. It's called from controllers who pass the transaction to be safely executed as a closure.
My Problem: When the service hits a org.hibernate.StaleObjectStateException due to concurrent updates, it keeps trying again but the Exception never disappears.
I already tried different things like re-attaching domain classes in the transaction passed by the controller, clearing the session in the service or in the controller, but it didn't help. What am I missing?
I should note that I got an error that my "Transaction Manager does not allow nested transactions" when I tried to insert a savePoint before transaction() is called using status.createSavepoint(). I tried this because I also suspected that the error exists because the transaction is passed from the controller to the service and that I needed to start a new / nested transaction to avoid it, but as the error shows this is not possible in my case.
Or maybe is passing the transaction as a closure the problem?
I assume that the domain class used before the .withTransaction doesn't matter, or does it?
回答1:
It is not closure itself, but I believe transaction
has some stale variable reference inside.
What if you try to only pass closures that re-read their objects on execution? Like
executeOptimisticTransaction {
Something some = Something.get(id)
some.properties = aMap
some.save()
}
I don't think it's possbile to "refresh" an object without re-reading it in Hibernate.
Yes, it doesn't matter what class you call .withTransaction() on.
For the example updating calculated totals/ratings, that's a data duplication that's a problem itself. I'd rather either:
- create a (Quartz) job that will update ratings based on some "dirty" flag - that might save some DB CPU for a cost of update time;
- or do it in SQL or HQL, like
Book.executeQuery('update Rating set rating=xxx')
that's going to use latest Rating. If you're optimizing for heavy load, you're anyway not going to do everything Groovy-way. Don't save Rating objects in Grails, only read them.
来源:https://stackoverflow.com/questions/4375757/how-to-overcome-staleobjectstateexception-in-grails-service