Как стать автором
Обновить

Hibernate envers. Подмена ID пользователя совершившего изменение

Время на прочтение3 мин
Количество просмотров5.2K
image

Добрый день уважаемые хабровчане. Это моя первая статья, пожалуйста, сильно не ругайтесь.

Об аудировании в Hibernate написано уже немало. Я хочу рассказать о решении не совсем стандартной задачи — записи в таблицу ревизий ID любого пользователя, назначаемого непосредственно перед операцией записи сущности в базу данных. Стандартное решение, предложенное в официальной документации — использование ID пользователя, сохраненного в сессионном компоненте. Но возможна ситуация, когда ID пользователя необходимо подменить. Пример: пользователь совершает операции через взаимодействие с сервером телефонии посредством DTMF сигналов. В данном случае сессию создавать вообще не нужно. Я долго искал решение в интернете, но так ничего и не нашёл, поэтому предлагаю вашему вниманию свою версию. Возможно кому-то из новичков, вроде меня, она окажется полезной.

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

Попробуем извлечь из этого пользу. Создаем stateless компонент, в котором будет храниться статическая map c парами: ID потока — ID пользователя. Метод start добавляет в map ID пользователя (переданного параметром) и ID текущего потока, затем запускает в новой транзакции метод, выполняющий необходимые действия, дожидается окончания метода (транзакции) и удаляет ID потока и пользователя из map.

@Stateless
@TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED)
public class FakeOwnerTransaction {
    
    @Inject
    private Provider<FakeOwnerTransaction> providerFakeOwnerNewTransaction;
    
    private static ThreadLocal<Long> threadOwnerID = new ThreadLocal<>()
    
    @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
    public void newCMT(Runnable action) {
        action.run();
    }
    
    public void start(Long personID, Runnable action) {
       threadOwnerID.set(personID);
        try {
            providerFakeOwnerNewTransaction.get().newCMT(action);
        } finally {
            threadOwnerID.remove();
        }
    }
    
    public static Long getFakeChanger() {
        return threadOwnerID.get();
    }
}

Теперь посмотрим, как будет выглядеть реализация RevisionListener. Если текущий поток ассоциирован с ID пользователя, используем этот ID, иначе берем ID пользователя из сессионного компонента UserManager.

public class Audition implements RevisionListener {
    
    @Override
    public void newRevision(Revinfo revinfo) {
        Long personID = FakeOwnerTransaction.getFakeChanger();
        
        if (personID == null) {
            UserManager userManager = SystemUtils.lookup(JNDI_NAME_PREFIX, UserManager.class);
            personID = userManager.getPersonID();
        }
        revinfo.setPersonID(personID);
    }
}

Ну и напоследок попробуем сделать изменение в базе данных, используя ID указанного пользователя.

FakeOwnerTransaction fakeOwnerTransaction = SystemUtils.lookup(JNDI_NAME_PREFIX, FakeOwnerTransaction.class);
fakeOwnerTransaction.start(getPersonID(), new Runnable() {
            
            @Override
            public void run() {
                Dao dao = SystemUtils.lookup(JNDI_NAME_PREFIX, Dao.class);
                dao.add(new Person(“Smirmov”));
            }
        });


Класс SystemUtils
public class SystemUtils {
    
    public static <T> T lookup(String jndiNamePrefix, Class<T> clazz) {
        String jndiName = jndiNamePrefix;
        jndiName += clazz.getSimpleName() + "!" + clazz.getName();
        try {
            return (T) new InitialContext().lookup(jndiName);
        } catch (Exception e) {
            CoreSharedUtils.getLogger().severe("Error. Bean '" + jndiName + "' not found!");
            e.printStackTrace();
        }
        return null;
    }
    …
}



Важные замечания
Сегодня один умный человек с Хабра, который по каким-то причинам не может писать комментарии, заметил важную проблему, на которую стоит обратить внимание.
Поток может быть завершен из вне JVM, в этом случае блок finaly не будет выполнен. Гипотетически это может привести к утечке памяти, но меня данный вариант устраивает.


На этом у меня всё. Если у кого-то есть более интересные решения или критика — добро пожаловать в комментарии.
Теги:
Хабы:
+7
Комментарии8

Публикации

Истории

Работа

Java разработчик
358 вакансий

Ближайшие события