Оптимистическая блокировка Grails не обнаруживает одновременное обновление?

Должна ли оптимистическая блокировка выявлять проблемы одновременного обновления?

Под одновременным обновлением я подразумеваю, что два разных пользователя пытаются обновить объект с одним и тем же номером версии. Например, если существует класс домена Person и Person с id = 1 и версией = 0, имеет name = Jack, и два разных пользователя пытаются обновить имя в версии 0, я бы ожидал, что только один из пользователей чтобы добиться успеха и изменить версию на 1. Я ожидал, что второй пользователь получит исключение Hibernate staleStateException или что-то подобное. Но этого не происходит.

Вот мой вариант использования:

Grails 3.1.5
grails generate-app person
grails create-domain-class Person
Edit Person.groovy to include String name
grails generate-all person.Person
gradle bootRun

Используйте два разных браузера для доступа к приложению, например Chrome и Firefox, чтобы убедиться, что два браузера находятся в разных сеансах. Создайте человека в одном из них, а затем откройте этого человека (версия 0) для редактирования в обоих браузерах. Оба браузера теперь должны редактировать версию 0 пользователя. Сохраните изменение имени в одном браузере, это сработает и изменит сохраненную версию объекта на 1, но второй браузер все еще редактирует версию 0. Теперь сохраните изменения во втором браузере, это также работает. Несмотря на то, что второй браузер только что сохранил изменения в уже устаревшем объекте (версия 0), исключение StaleObject или StaleStateException не генерируется. Это правильное поведение?


person DAC    schedule 28.04.2016    source источник


Ответы (2)


Да, оптимистическая блокировка Grails обнаружит одновременное обновление и выдаст исключение. Однако, судя по тому, что вы описали, вы не выполняете одновременное обновление. Давайте рассмотрим подробнее.

  1. Отправной точкой является получение обоими браузерами одной и той же версии объекта. Однако обратите внимание, что браузеры не содержат ссылки на эти объекты. Код GSP взял их из базы данных и отобразил в браузере. Другими словами, тот же объект просматривается, но не редактируется.
  2. Когда первый обозреватель обращается к контроллеру с просьбой сохранить изменения, контроллер извлекает новую копию объекта из базы данных: например. DomainClass.get(params.id). Затем изменения сохраняются.
  3. Когда второй обозреватель обращается к контроллеру с просьбой сохранить изменения, контроллер снова получает новую копию объекта из базы данных. На этот раз это версия 1, но это не имеет значения, потому что версия не имеет ничего общего с получением объекта, только при сохранении. Итак, второе сохранение выполняется успешно, потому что версия соответствует тому, что уже есть в базе данных.

Чтобы создать условие, которое вы ищете, вам придется поиграть с ним, пока между двумя сохранениями не будет достаточно долгого перекрытия, чтобы произошла такая последовательность:

def a = DomainClass.get(1)
def b = DomainClass.get(1)

/* change a */
a.save()

/* change b */
b.save() // This would throw an exception because b.version does not match what's in the database.
person Emmanuel Rosa    schedule 28.04.2016
comment
Итак, если я правильно понимаю, функция оптимистической блокировки Grails / GORM / Hibernate действительно предназначена для обнаружения проблем одновременного обновления, которые происходят в памяти на сервере. Итак, я надеюсь, что эта функциональность может быть расширена для клиента, отправив номер версии клиенту и обратно с последующими запросами на обновление, действительно выходит за рамки этой функциональности? - person DAC; 28.04.2016
comment
Верный. Не обязательно в памяти, а просто на стороне сервера. И ваша идея передать номер версии - это именно то, как вы можете достичь функциональности на стороне клиента. - person Emmanuel Rosa; 28.04.2016
comment
Но подождите, приложение, созданное Grails, уже передает номер версии клиенту, а клиент отправляет его обратно. Но когда данные запроса привязаны к объекту домена, Grails ищет объект только по идентификатору, а не по идентификатору и версии, кроме того, он затем перезаписывает версию, полученную от клиента, версией из поиска в базе данных. Он не перезаписывает обновленное свойство имени, а использует значение имени от клиента. Если бы он также просто использовал номер версии от клиента, тогда функциональность была бы расширена до клиента. - person DAC; 29.04.2016
comment
Прохладный. Я понятия не имел, что это произошло. - person Emmanuel Rosa; 29.04.2016

Я думаю, что точный ответ: Grail не реализует оптимистичную блокировку между запросами из коробки.

Путаница возникает из-за того, что scaffolding генерирует некоторый код, необходимый (но недостаточный) для этого в edit.gsp:

<g:hiddenField name="version" value="${myInstance?.version}" />

Вы должны реализовать в своем контроллере save() действие дополнительной проверки, чтобы сравнить исходную визуализированную версию вашей записи с версией базы данных в конкретный момент сохранения и сообщить пользователю, что во время параллельных операций редактирования произошло грязное чтение. . Что-то вроде:

if (new Long(params.version) != myInstance.version) {
    myInstance.errors.rejectValue("", "", "This record has been updated by a concurrent user. Please reopen it before saving again");
    myInstance.version = new Long(params.version); //the version to be re-rendered must still be the original one, just in case the user try saving again without refreshing
    return render(view: "edit", model: [myInstance: myInstance]);
}
person Cléssio Mendes    schedule 27.06.2017