Автор оригинала: Vlad Mihalcea.
Hibernate поставляется со многими дополнениями к стандартной спецификации JPA. Одним из таких примеров является аннотация @GeneratorType
, которая позволяет настроить способ автоматического создания значения свойства заданной сущности.
Если вы используете данные Spring, вы можете просто использовать @CreatedBy
и @LastModifiedBy
аннотации, и свойства аннотированной сущности будут заполнены текущим зарегистрированным пользователем.
Если вы не используете данные Spring, то вы можете легко эмулировать то же поведение, используя специфичную для гибернации @GeneratorType
аннотацию и Генератор значений
механизм обратного вызова.
Предполагая, что у нас есть следующая таблица датчик
в нашей реляционной базе данных:
Мы хотим отобразить это как объект JPA. Если имя
может быть отображено как сущность @Id
, а значение
является просто @базовым
свойством, как мы можем автоматизировать столбцы created_by
и updated_by
с использованием текущего зарегистрированного пользователя?
Для проверки предположим, что у нас есть следующая утилита ThreadLocal
, в которой хранится текущий зарегистрированный пользователь:
public class LoggedUser { private static final ThreadLocaluserHolder = new ThreadLocal<>(); public static void logIn(String user) { userHolder.set(user); } public static void logOut() { userHolder.remove(); } public static String get() { return userHolder.get(); } }
В сервлете веб-приложения Filter
метод LoggedUser.logIn
может быть вызван с использованием текущего аутентифицированного пользователя , а метод LoggedUser.logOut
вызывается сразу после возврата из внутренней цепочки Filter.doFilter
вызов.
public class LoggedUserFilter implements Filter { @Override public void init( FilterConfig filterConfig) throws ServletException { } @Override public void doFilter( ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException { try { HttpServletRequest httpServletRequest = (HttpServletRequest) request; LoggedUser.logIn( httpServletRequest.getRemoteUser() ); filterChain.doFilter(request, response); } finally { LoggedUser.logOut(); } } @Override public void destroy() { } }
Теперь мы хотим передать текущего зарегистрированного пользователя в CreatedBy
и UpdatedBy
свойства нашей Сенсорной
сущности, для этого мы создадим следующую утилиту ValueGenerator
Hibernate:
public class LoggedUserGenerator implements ValueGenerator{ @Override public String generateValue( Session session, Object owner) { return LoggedUser.get(); } }
С помощью интерфейса Генератор значений
Hibernate позволяет нам настраивать способ создания данного свойства сущности. Теперь нам нужно только указать Hibernate использовать генератор Зарегистрированных пользователей
для Созданных
и Обновленных
свойств нашей Сенсорной
сущности.
@Entity(name = "Sensor") @Table(name = "sensor") public class Sensor { @Id @Column(name = "sensor_name") private String name; @Column(name = "sensor_value") private String value; @Column(name = "created_by") @GeneratorType( type = LoggedUserGenerator.class, when = GenerationTime.INSERT ) private String createdBy; @Column(name = "updated_by") @GeneratorType( type = LoggedUserGenerator.class, when = GenerationTime.ALWAYS ) private String updatedBy; //Getters and setters omitted for brevity }
Как вы можете видеть, тип @generator
позволяет нам сопоставлять свойства CreatedBy
и UpdatedBy
, чтобы Hibernate использовал LoggedUserGenerator
для назначения свойства аннотированной сущности с использованием текущего зарегистрированного пользователя.
Атрибут when
аннотации @GeneratorType
указывает, следует ли назначать свойство сущности, когда сущность сохраняется (например, Время создания.ВСТАВИТЬ
) или изменить (например, Время создания.ВСЕГДА
).
Чтобы увидеть, как заполняется свойство CreatedBy
при сохранении сущности, рассмотрим следующий тестовый случай:
LoggedUser.logIn("Alice"); doInJPA(entityManager -> { Sensor ip = new Sensor(); ip.setName("ip"); ip.setValue("192.168.0.101"); entityManager.persist(ip); executeSync(() -> { LoggedUser.logIn("Bob"); doInJPA(_entityManager -> { Sensor temperature = new Sensor(); temperature.setName("temperature"); temperature.setValue("32"); _entityManager.persist(temperature); }); LoggedUser.logOut(); }); }); LoggedUser.logOut();
В основном потоке Алиса входит в систему и вставляет датчик ip
, в то время как в другом потоке Боб входит в систему и вставляет датчик температуры
.
При выполнении приведенного выше тестового случая Hibernate генерирует следующие инструкции SQL INSERT:
INSERT INTO sensor ( created_by, updated_by, sensor_value, sensor_name ) VALUES ( 'Bob', 'Bob', '32', 'temperature' ) INSERT INTO sensor ( created_by, updated_by, sensor_value, sensor_name ) VALUES ( 'Alice', 'Alice', '192.168.0.101', 'ip' )
Здесь мы можем сделать несколько замечаний:
- ВСТАВКА Боба выполняется первой, так как он сначала зафиксировал (и сбросил) свои изменения.
- Время
Поколение.ВСЕГДА
стратегия свойстваUpdatedBy
запускаетГенератор значений
как для ВСТАВКИ, так и для ОБНОВЛЕНИЯ.
При изменении сущностей:
LoggedUser.logIn("Alice"); doInJPA(entityManager -> { Sensor temperature = entityManager.find( Sensor.class, "temperature" ); temperature.setValue("36"); executeSync(() -> { LoggedUser.logIn("Bob"); doInJPA(_entityManager -> { Sensor ip = _entityManager.find( Sensor.class, "ip" ); ip.setValue("192.168.0.102"); }); LoggedUser.logOut(); }); }); LoggedUser.logOut();
Hibernate создает следующие инструкции обновления SQL:
UPDATE sensor SET created_by = 'Alice', updated_by = 'Bob', sensor_value = '192.168.0.102' WHERE sensor_name = 'ip' UPDATE sensor SET created_by = 'Bob', updated_by = 'Alice', sensor_value = '36' WHERE sensor_name = 'temperature'
Отлично! Записи датчика
были обновлены должным образом, и в столбце updated_by
отображается пользователь, внесший изменения.
Как было продемонстрировано, Hibernate очень гибок, позволяя настраивать способ автоматического создания свойств сущностей. Используя аннотацию @GeneratorType
и механизм обратного вызова Генератора значений
, вы можете легко заполнить столбцы created_by
и update_by
таблицы без необходимости вручную заполнять эти свойства сущности самостоятельно.
Если вы используете данные Spring, вы можете сделать то же самое с аннотациями @CreatedBy
и @LastModifiedBy
, поскольку эта функция может быть интегрирована с механизмом аутентификации пользователей, определенным Spring Security, с помощью механизма AuditorAware
.