Как заблокировать () объект JPA / Hibernate, когда у вас есть только этот объект (без EntityManager / Session)?

#hibernate #jpa #hibernate-5.x #jpa-2.2 #hibernate-5

#впасть в спящий режим #jpa #спящий режим-5.x #jpa-2.2 #спящий режим-5

Вопрос:

Я имею дело с некоторым устаревшим кодом, который местами не имеет чистого и надежного способа получения JPA EntityManager (или сеанса гибернации) для данного объекта, необходимого для вызова метода lock(…). По сути, у нас есть метод в объекте, который должен делать что-то вроде:

 @Entity
public class SomeEntity {
    @Column(...)
    @Basic(...)
    private String name; 

    ... 

    public void doSomething(/* arguments irrelevant */) {
         ...
         myCurrentEntityManagerOrSession.lock(...);
         ...
    }

    ...
}
 

Вопрос в том, как это сделать — как получить доступ к этому менеджеру объектов или сеансу? Есть два способа, которые мы придумали для достижения того, что нам нужно, ни один из них не желателен, хотя оба работают:

Вариант № 1 — неловкий триггер свойств

     final String originalName = getName();
    setName("modified "   originalName);
    setName(originalName);
 

… чтобы «обмануть» Hibernate (мы используем улучшение байт-кода, кстати) для выполнения самой блокировки, затем отмените изменение, но сохраните блокировку (мы бы сделали это для любого «недорогого» свойства).

Вариант № 2: Неуместное злоупотребление отражением улучшений байт-кода Hibernate и его внутреннего API

   private static final Method HIBERNATE_ENTITY_ENTRY_GETTER;
    static {
        Method getter;
        try {
            getter = SomeEntity.class.getMethod("$_hibernate_getEntityEntry");
        } catch (NoSuchMethodException e) {
            /// Oooh .. not good!
            getter = null;
        }
        HIBERNATE_ENTITY_ENTRY_GETTER = getter;
    }
    public Session getEntitySession()  {
        try {
            final MutableEntityEntry entry = (MutableEntityEntry) HIBERNATE_ENTITY_ENTRY_GETTER.invoke(this);
            return (Session) entry.getPersistenceContext().getSession();
        } catch (RuntimeException e) {
            throw e;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
    public void lock(final LockMode lockMode) {
        getEntitySession().lock(this, lockMode);
    }
    public void lock() {
        lock(LockMode.OPTIMISTIC_FORCE_INCREMENT);
    }
 

Есть ли правильный, чистый способ получить EntityManager / Session, который в настоящее время управляет / владеет определенным объектом? Мы знаем, что базовый движок, конечно, уже должен это знать… но, похоже, это хорошо скрыто.

ДОПОЛНИТЕЛЬНОЕ ПРИМЕЧАНИЕ: ThreadLocal может быть ответом в некоторых проектах, но не в этом. Не спрашивай.

Комментарии:

1. Чтобы сделать ваш код более чистым, вы должны переместить doSomething метод за пределы вашего объекта.

2. doSomething() только кажется, что не имеет к этому никакого отношения. Это так и есть. Он и так идеально чистый, и его удаление не решает проблему.

3. Код объекта не должен манипулировать с EntityManager / Session. За это отвечает код бизнес-уровня.

4. Совершенно не имеет отношения к обсуждению. Мы могли бы обсудить это в другом месте — обратите внимание, что изменение свойств объекта в любом случае манипулирует блокировками с использованием сеансов. Подумайте об этом, что метод кода бизнес-уровня имеет только ссылку на объект, ничего больше, и я не могу это исправить. Как он может заблокировать объект?