Как преодолеть исключение StaleObjectStateException в Grails Service
Я ввел TransactionService, который я использую в своих контроллерах для выполнения оптимистичных транзакций. Должно
- попытаться выполнить данную транзакцию (= закрытие)
- откатить его обратно, если он не работает и
- попробуйте еще раз, если не получится
В основном это выглядит так:
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()
}
}
}
}
}
Это немного сложнее, например, он использует разные Thread.sleeps перед повторной попыткой и прерывает работу на каком-то этапе, но здесь это не имеет значения. Это вызвано от контроллеров, которые передают транзакцию, чтобы быть безопасно выполненным как закрытие.
Моя проблема: когда служба обнаруживает исключение org.hibernate.StaleObjectStateException из-за одновременных обновлений, она продолжает пытаться снова, но исключение никогда не исчезает.
Я уже пробовал разные вещи, такие как повторное присоединение классов домена в транзакции, передаваемой контроллером, очистка сеанса в службе или в контроллере, но это не помогло. Что мне не хватает?
Я должен отметить, что я получил ошибку, что мой "Диспетчер транзакций не разрешает вложенные транзакции", когда я пытался вставить savePoint до вызова транзакции () с использованием status.createSavepoint(). Я попробовал это, потому что я также подозревал, что ошибка существует, потому что транзакция передается от контроллера к службе и что мне нужно было запустить новую / вложенную транзакцию, чтобы избежать ее, но, как показывает ошибка, это невозможно в моем случае.
Или, может быть, проблема заключается в передаче транзакции?
Я предполагаю, что класс домена, используемый до.withTransaction, не имеет значения, или это так?
1 ответ
Это не закрытие, но я верю transaction
имеет некоторую устаревшую ссылку на переменную внутри. Что если вы попытаетесь передать только замыкания, которые перечитывают свои объекты при выполнении? подобно
executeOptimisticTransaction {
Something some = Something.get(id)
some.properties = aMap
some.save()
}
Я не думаю, что можно "обновить" объект, не перечитав его в Hibernate.
Да, не имеет значения, в каком классе вы вызываете.withTransaction().
Для примера обновления вычисленных итогов / рейтингов это дублирование данных, которое само по себе является проблемой. Я бы предпочел либо:
- создать (Quartz) задание, которое будет обновлять оценки на основе некоторого "грязного" флага - это может сэкономить некоторый ЦП БД за счет затрат времени на обновление;
- или сделать это в SQL или HQL, как
Book.executeQuery('update Rating set rating=xxx')
что будет использовать последний рейтинг. Если вы оптимизируете для большой нагрузки, вы все равно не будете делать все Groovy-way. Не сохраняйте объекты рейтинга в Grails, только читайте их.