JPA: исключение OptimisticLockException и каскадирование

В моем текущем проекте я использую Spring Data JPA с Hibernate, но рассматриваю это как более общий вопрос, который также должен охватывать «простой» JPA.

Я не уверен, как мне поступить с OptimisticLockException при использовании @Version.

Из-за того, как работает мое приложение, некоторые отношения имеют CascadeType.PERSIST и CascadeType.REFRESH, другие также имеют CascadeType.MERGE.

  1. Куда обращаться OptimisticLockException

Насколько я могу судить, обработка этого на уровне сервиса не будет работать, особенно с CascadeType.MERGE, потому что тогда объект-нарушитель может быть объектом, который должен обрабатываться другим сервисом (у меня есть сервис для каждого класса объектов).

Проблема в том, что я создаю фреймворк, и, следовательно, над сервисом нет уровня, поэтому я мог бы просто «делегировать» это пользователю моей фреймворка, но это кажется «слабым и ленивым».

  1. Определить объект-нарушитель и измененные поля

если возникает исключение OptimisticLockException, как мне узнать, какая сущность вызвала проблему и какие поля были изменены?

Да, я могу вызвать getEntity(), но как привести его к правильному типу, особенно если использовался CascadeType.MERGE? Сущность может быть нескольких типов, поэтому на ум приходит if/switch с instanceof, но это чертовски уродливо.

Как только у меня будет правильный тип, мне нужно будет получить все различия между версиями, за исключением определенных полей, таких как сама версия или lastModifiedDate.

В глубине души я также думаю о HTTP 409, в котором говорится, что в случае конфликта ответ должен содержать конфликтующие поля.

Есть ли для всего этого «лучший образец практики»?


person beginner_    schedule 18.04.2013    source источник


Ответы (2)


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

Как и в случае с SVN, если вы пытаетесь зафиксировать файл, а кто-то еще перед этим зафиксировал новую версию, SVN заставляет вас обновить вашу рабочую копию и разрешить потенциальные конфликты.

Поэтому я бы сделал то же самое, что и JPA: он позволяет вызывающей стороне решать, что делать, выбрасывая исключение. Это исключение должно обрабатываться на уровне представления.

person JB Nizet    schedule 18.04.2013
comment
Должен ли? HTTP 409 указывает, что ответ должен содержать информацию о том, что пошло не так: Конфликты, скорее всего, возникнут в ответ на запрос PUT. Например, если использовалось управление версиями, а размещаемая сущность включала изменения в ресурс, которые противоречат тем, которые были сделаны в результате более раннего (стороннего) запроса, сервер может использовать ответ 409, чтобы указать, что он не может выполнить запрос. . В этом случае объект ответа, скорее всего, будет содержать список различий между двумя версиями в формате, определяемом Content-Type ответа. - person beginner_; 19.04.2013

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

Я создал новое исключение, которое содержит ссылку на новейшую версию сущности, которую не удалось обновить:

public class EntityVersionConflictException {

    @Getter
    private final Object currentVersion;

    public EntityVersionConflictException(
            ObjectOptimisticLockingFailureException lockEx,
            Object currentVersion){
        super(lockEx);
        this.currentVersion = currentVersion;
    }

    public Object getConflictingVersion() {
        return ((OptimisticLockException)getCause().getCause()).getEntity();
    }

    public Class getEntityClass() {
        return getCause().getPersistentClass();
    }

    @Override
    public ObjectOptimisticLockingFailureException getCause(){
        return (ObjectOptimisticLockingFailureException)super.getCause();
    }
}

и соответствующий метод обслуживания

try {
    return getRepository().save(entity);
} catch (ObjectOptimisticLockingFailureException lockEx) {
    // should only happen when updating existing entity (eg. merging)
    // and because entites do not use CascadeType.MERGE
    // the entity causing the issue will always be the of class
    // entity.getClass()
    // NOTE: for some reason lockEx.getPersistentClass() returns null!!!
    // hence comparing by class name...
    if (lockEx.getPersistentClassName().equals(entityClass.getName())) {
        T currentVersion = getById(entity.getId());
        throw new EntityVersionConflictException(lockEx, currentVersion);
    } else {
        throw lockEx;
    }
}

Обратите внимание на комментарии. В случае CascadeType.MERGE это не будет работать, логика должна быть намного сложнее. У меня есть 1 служба для каждого типа объекта, поэтому эта служба должна содержать ссылку на все другие службы и так далее.

person beginner_    schedule 15.05.2013