Рубрики
Без рубрики

Как эмулировать @CreatedBy и @LastModifiedBy из данных Spring с помощью аннотации @GeneratorType Hibernate

Автор оригинала: Vlad Mihalcea.

Hibernate поставляется со многими дополнениями к стандартной спецификации JPA. Одним из таких примеров является аннотация @GeneratorType , которая позволяет настроить способ автоматического создания значения свойства заданной сущности.

Если вы используете данные Spring, вы можете просто использовать @CreatedBy и @LastModifiedBy аннотации, и свойства аннотированной сущности будут заполнены текущим зарегистрированным пользователем.

Если вы не используете данные Spring, то вы можете легко эмулировать то же поведение, используя специфичную для гибернации @GeneratorType аннотацию и Генератор значений механизм обратного вызова.

Предполагая, что у нас есть следующая таблица датчик в нашей реляционной базе данных:

Мы хотим отобразить это как объект JPA. Если имя может быть отображено как сущность @Id , а значение является просто @базовым свойством, как мы можем автоматизировать столбцы created_by и updated_by с использованием текущего зарегистрированного пользователя?

Для проверки предположим, что у нас есть следующая утилита ThreadLocal , в которой хранится текущий зарегистрированный пользователь:

public class LoggedUser {

    private static final ThreadLocal userHolder = 
        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'
)

Здесь мы можем сделать несколько замечаний:

  1. ВСТАВКА Боба выполняется первой, так как он сначала зафиксировал (и сбросил) свои изменения.
  2. Время Поколение.ВСЕГДА стратегия свойства 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 .