سؤال

I'm trying to implement exception handling for Optimistic lock type exceptions that are thrown by Hibernate but I've encountered a strange issue. It seems I'm unable to catch any Gorm exceptions.

For example I have this code in my service:

try {
  User user = User.get(1);
  Thread.sleep(10000);
  user.viewedAt(new Date());
  user.save(flush:true);
} catch (OptimisticLockingException ex) {
  log.error("Optimistic lock exception");
} catch (StaleObjectStateException ex) {
  log.error("Optimistic lock exception");
}

When I hit this block with two threads, it blows up and the exception propagates to Grails' standard exception handler. The catch blocks are never invoked even though the reported exception is StaleObjectStateException.

I've noticed that I can catch the exception if I let it propagate to the controller and catch it there, but it seems I can't implement exception handling in the service which is weird.

What am I missing?

هل كانت مفيدة؟

المحلول

I got to the bottom of this and I'm posting it in case anyone else runs into this. The issue occurred because the try/catch block was in a transactional service. Although grails reported that the exception was thrown during the save() call, in reality it was called AFTER the entire method, when the transaction was committed.

So it seems that:

  1. flush: true has no effect on transactional services
  2. It's not possible to catch GORM related exceptions in transactional services, at least not without some work

I finally worked around this by manually managing the transaction myself i.e.

try {
  User.withNewTransaction {
    User user = User.get(id); // Must reload object
    .. // do stuff
    user.save(flush:true)
  }
} catch (OptimisticLockingException ex) {
  ...
}

I hope this is of use to someone else!

نصائح أخرى

I spent some time working on this problem and have written a more complete solution to handle the case of an optimistic locking exception in Grails.

Firstly, though the exception reported in the stack trace is StaleObjectStateException, the actual exception that gets thrown is HibernateOptimisticLockingFailureException (not "OptimisticLockingException"). Secondly, if you want to generalize this to handle arbitrary closures which modify domain objects, you need to rethrow exceptions thrown inside the closure.

The following static function will take an object and a closure that operates on the object, save it, and if it fails, retry again until it succeeds:

public static retryUpdate(Object o, Closure c) throws Exception {
    def retVal
    int retryCount = 0
    while (retryCount < 5) {
        try {
            Model.withTransaction { status ->
                retVal = c(status)
                o.save()
            }
            return retVal
        } catch (HibernateOptimisticLockingFailureException e) {
            log.warn "Stale exception caught saving " + o
            if (++retryCount >= 3) { // if retry has failed three times, pause before reloading
                Thread.sleep(1000)
            }
            o.refresh()
        } catch (UndeclaredThrowableException e2) {
            // rethrow exceptions thrown inside transaction
            throw e2.getCause()
        }
    }

    return null
}

Model in this case is any GORM model class, doesn't matter which one. In particular it doesn't matter if it is the class of the passed-in object.

Example of use:

AnotherModelClass object = AnotherModelClass.get(id)
retryUpdate(object) {
    object.setField("new value")
}
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top