Блокировка JPA в веб-приложении

Я пытаюсь реализовать блокировку приложения JPA/Hibernate, чтобы определить, когда клиент пытается внести изменения в устаревшую версию объекта.

Я решил выставить dto (представляющий подмножество домена) через службы REST. На данный момент я могу легко обнаруживать обновления параллельных транзакций, но я не могу заставить его работать для обнаружения обновлений «старых» сущностей. Я объясню:

  • #1 2 параллельные транзакции, управляющие одной и той же сущностью, каждая из которых подключена к своему диспетчеру сущностей, должным образом защищены от грязных чтений (последняя фиксация получает исключение OptimisticLockingException)
  • #2 2 одновременно работающих пользователя, манипулирующих одним и тем же объектом с помощью внешнего интерфейса и совершающих две разные непараллельные транзакции, НЕ получают никаких исключений блокировки. Это потому, что при использовании DTO часть кода обновления выглядит примерно так:

    • start a transaction
    • получить постоянный объект для обновления от менеджера
    • скопируйте то, что имеет значение из dto в этот объект
    • совершить

... но ничто (JPA, Hibernate или что-то еще) никогда не проверяет согласованность между версией dto и версией объекта... (ps: попытка установить поле @Version с версией, указанной в dto, как указано JPA , привести к странным результатам)

Основываясь на том, что я видел, и на большом количестве прочитанной документации и отладки, я написал такой код:

abstract class AbstractBusinessService {
    private static final Logger LOG = LoggerFactory.getLogger(AbstractBusinessService.class);

    protected final void checkEntityVersion(Long givenVersion, VersionedEntity<?> entity) {
        if (givenVersion == null) {
            LOG.warn("no version have been provided. unable to check for concurrent modification");
        } else if (entity == null) {
            LOG.warn("the given entity is null");
        } else if (entity.getVersion() != givenVersion.longValue()) {
            throw new LockingException("The persistent entity " + entity.getClass().getName() + "#" + entity.getId()
                    + " has a newer version than expected (" + givenVersion + " vs. " + entity.getVersion() + ")");
        }
    }
}

... вызывается перед каждой операцией "обновления"... Это, очевидно, работает как шарм, но добавляет некоторую сложность на бизнес-уровень, для темы, которая связана исключительно с сохранением и не должна быть видна на бизнес-уровне...

Прав ли я в том смысле, что это самостоятельная вещь, и нет ничего готового для реализации #2? Я что-то упускаю?? Как вы решаете эту проблему в своем развитии?

Большое спасибо за чтение,

SP

EDIT: как указано в комментариях, лучший способ, кажется,

  1. получить постоянную сущность из контекста (A)
  2. создать новую, чистую (отдельную/переходную) сущность (B)
  3. скопируйте все свойства (A) в (B)
  4. установите все свойства в (B) с тем, что исходит из DTO (может потребоваться преобразование)
  5. слияние (B) --> получить LockException, если @Version не соответствует

person spi    schedule 18.12.2015    source источник
comment
Мне кажется, что установка поля @Version с версией, указанной в dto, является правильным решением... если вы упорядочиваете и неупорядочиваете данные из службы RESTful, это произойдет в любом случае, независимо от того, использовать DTO или сами сущности. Так что мне любопытно, какие странные результаты вы видите?   -  person dcsohl    schedule 18.12.2015
comment
Вы не должны этого делать, JPA (Hibernate) должен позаботиться об этом. Вам нужен дополнительный шаг, прежде чем вы сохраните отсоединенную сущность. Вам нужно загрузить последнюю версию из em или db и объединить сущность, поступающую из пользовательского интерфейса, а затем сохранить. Это автоматически определяет версию. Вы можете прочитать о сохранении отдельного объекта.   -  person RP-    schedule 18.12.2015
comment
Еще одна веская причина избегать анти-шаблона DTO.   -  person duffymo    schedule 18.12.2015
comment
@dcsohl JPA 2.0 Final Spec, раздел 3.4.2: Объект может получить доступ к состоянию своего поля или свойства версии или экспортировать метод для использования приложением для доступа к версии, но не должен изменять значение версии. За исключением, отмеченным в разделе 4.10, только поставщику постоянства разрешено устанавливать или обновлять значение атрибута версии в объекте.   -  person spi    schedule 18.12.2015
comment
Если это фактически управляемая сущность, это верно. Типичным вариантом использования здесь будет 1) Создать новый неуправляемый объект. 2) Скопируйте все атрибуты из вашего DTO в неуправляемый объект, включая (что наиболее важно) поле @Id и поле @Version. 3) Позвоните em.merge(obj). 4) Прибыль! Как я уже сказал, даже если бы вы не использовали DTO, это происходило бы под прикрытием Jackson/JAXB/MOxy/всего, что вы используете.   -  person dcsohl    schedule 18.12.2015
comment
Кажется, это работает (блокировка исключения и последовательно), но поскольку мой dto содержит только подмножество свойств объектов, я получаю много нулей... Я думаю, что мне следует сделать 1) создать неуправляемый объект 2) получить сохраняемый объект 3) скопировать свойства управляемого объекта в неуправляемый 4) скопировать значения dto в неуправляемый 5) объединить?   -  person spi    schedule 18.12.2015
comment
Да, так намного лучше :-) спасибо всем, но, кстати, как вы могли не использовать dto, если хотите контролировать формат и содержимое ваших остальных ресурсов? Я имею в виду, я не хочу показывать все до единого области моей настойчивости остальным? я также не хочу влиять на остальные API всякий раз, когда я добавляю/удаляю/изменяю столбец в БД   -  person spi    schedule 18.12.2015