2015-08-08 1 views
0

У меня есть сценарий, когда объект обновляется двумя разными потоками. Ниже приведен код в классе сервиса grails. Я могу поймать исключение StaleObject, но когда я пытаюсь извлечь его из базы данных и повторить попытку, сохраняя значение, оно не работает.Обработка StaleObjectException в службе

public long updateTimer(Long timeLeft, TestAttempted testAttempted){ 
    // Let's say testAttempted.version() is now 5 
    // It is concurrently updated by other thread, version is now 6 
    ........ 
    ............ 
    testAttempted.setTimer(someCalculatedValue) 
    try{ 
     testAttempted.save(failOnError: true,flush:true) // StaleObject exception occurs 
    }catch(OptimisticLockingFailureException e){ 
     testAttempted.refresh() 
     testAttempted.setTimer(someCalculatedValue) 
     testAttempted.save(failOnError:true) 
    } 
} 

Почему приведенный выше код не обновляет/не сохраняет значение в блоке catch? Я также попробовал метод TestAttempted.get (id) для извлечения последнего из базы данных, но он не работает.

Но когда я пытаюсь это обновляет последнее значение таймера:

В контроллере: -

try{ 
     timeLeft = assessmentService.updateTimer(timeLeft,testAttempted) 
    }catch(OptimisticLockingFailureException e){ 
     testAttempted = TestAttempted.get(session['testAttemptedId']) 
     ........ 
     testAttempted.setTimer(someCalculatedValue) 
     testAttempted.save(failOnError: true) 
    } 

В службе:

public long updateTimer(Long timeLeft, TestAttempted testAttempted){ 
    ........ 
    ................. 
    testAttempted.setTimer(someValue) 
    testAttempted.save(failOnError: true) 
    return timeLeft 
} 

Это не работает, если она брошена и обрабатываются как в контроллере/службе. Он работает, когда его бросают в эксплуатацию и обрабатывают в контроллере. Как это возможно ?

ответ

0

Проблема с повторным подходом заключается в том, сколько попыток достаточно? Попробуйте это:

class AssessmentService { 

    /* 
    * Services are transactional by default. 
    * I'm just making it explicit here. 
    */ 
    static transactional = true 

    public long updateTimer(Long timeLeft, Long testAttemptedId) { 
     def testAttempted = TestAttempted.lock(testAttemptedId) 

     testAttempted.setTimer(someCalculatedValue) 
     testAttempted.save() 
    } 
} 

Проходят ID TestAttempted вместо экземпляра, так что служба может получить экземпляр по себе, со своей собственной транзакции.

Если вы хотите передать экземпляр TestAttempted вместо этого, я считаю, что вам нужно вызвать testAttempted. merge(), прежде чем вы внесете изменения в экземпляр.

Аналогичные question.

1

Вероятно, что когда вы делаете refresh(), а затем save() в блоке catch, экземпляр testAttempted изменяется между обновлением и сохранением, и поэтому он терпит неудачу с тем же исключением, которое теперь вы не ловите, потому что он уже в блоке catch.

Метод, афайк доменов get(), кэшируется в сеансе, поэтому TestAttempted.get (id) вернет вам экземпляр из сеанса, а не db.

Merge() не требуется в этом случае, поскольку вы вручную устанавливаете значение после обновления и перед сохранением.

Использование Domain.lock() может быть решением, но это может повлиять как вы справляетесь TesttAttempted в других частях коды, потому что теперь вы можете получить CannotAcquireLock исключение в тех местах, где вы пытаетесь получить экземпляр и он заблокирован по этой части кода.

Вопрос в том, какова стратегия разрешения конфликтов? Если это «последний писатель побеждает», то просто установите version= false для домена. Или вы можете использовать TestAttemted.executeUpdate('set timer = .. where id = ..') для обновления без увеличения версии.

Сложные сценарии, проконсультируйтесь с подробным освещением проблемы Марка Палмера. http://www.anyware.co.uk/2005/2012/11/12/the-false-optimism-of-gorm-and-hibernate/

+0

Спасибо за ответ. На самом деле оба потока обновляют разные поля. Они никогда не обновляют общее поле. Поэтому я думаю, что падение поля версии должно быть более подходящим. Не так ли? –

+0

Если у вас нет другого кода, который также обновляет этот Домен и полагается на оптимистичную блокировку, тогда да - просто отключите управление версиями для домена. – Yaro

1

Дело в том, что вы всегда должны повторять всю транзакцию.Позвольте транзакции отбросить назад и повторить все в новой, потому что старый является грязным (сеанс Hibernate недействителен и могут быть некоторые незафиксированные изменения, которые уже сброшены в базу данных).

+0

Да! Это имеет смысл. Я тоже подумал. Спасибо за ответ. –