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

Аудит JPA: Автоматическое сохранение журналов аудита с использованием EntityListeners

В одной из моих статей Spring Data JPA Аудит: Сохранение CreatedBy, CreatedDate, LastModifiedBy, LASTMOD… Помечен как java, spring boot, jpa.

В одной из моих статей Аудит Spring Data JPA: автоматическое сохранение CreatedBy, CreatedDate, lastmodifiedby, LastModifiedDate Я обсудил, почему аудит важен для любого бизнес-приложения и как мы можем использовать Spring Data JPA для его автоматизации.

Я также обсуждал, как Spring Data использует JPA Прослушиватели сущностей и методы обратного вызова для автоматического обновления свойств CreatedBy, CreatedDate, lastmodifiedby, LastModifiedDate.

Что ж, здесь, в этой статье, я собираюсь немного углубиться и обсудить, как мы можем использовать JPA EntityListeners для создания журналов аудита и хранения информации о каждой операции вставки, обновления и удаления наших данных.

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

Мы будем использовать Spring Boot, Spring Data JPA (потому что это дает нам полную функциональность JPA плюс некоторую приятную настройку Spring), MySQL, чтобы продемонстрировать это.

Нам нужно будет добавить нижеприведенный родитель и зависимости в наш файл pom


    org.springframework.boot
    spring-boot-starter-parent
    1.5.1.RELEASE
    



    
        org.springframework.boot
        spring-boot-starter-data-jpa
    

    
        mysql
        mysql-connector-java
        runtime
    

Реализация методов обратного вызова JPA с использованием аннотаций @prePersist, @preUpdate, @PreRemove

JPA предоставляет нам функциональность для определения методов обратного вызова для любого объекта с использованием аннотаций @prePersist , @Предварительное обновление , @PreRemove и эти методы будут вызваны до соответствующего события жизненного цикла.

Подобно предварительным аннотациям, JPA также предоставляет аннотации для публикации, такие как @postPersist , @Обновление после обновления , @PostRemove, * и * @postLoad . Мы можем использовать их для определения методов обратного вызова, которые будут запущены после события.

Имя аннотации может сообщить вам об их соответствующем событии, например @prePersist – До сохранения сущности и @PostUpdate – после обновления сущности, и это то же самое для других аннотаций.

Определение методов обратного вызова внутри сущности

Мы можем определить методы обратного вызова внутри нашего класса сущностей, но нам нужно следовать некоторым правилам, таким как внутренние методы обратного вызова, которые всегда должны возвращать void и не принимать аргументов. Они могут иметь любое имя и любой уровень доступа, а также могут быть статическими.

@Entity
public class File {

    @PrePersist
    public void prePersist() { // Persistence logic }

    @PreUpdate
    public void preUpdate() { //Updation logic }

    @PreRemove
    public void preRemove() { //Removal logic }

}

Определение методов обратного вызова во внешнем классе и использование @EntityListeners

Мы также можем определить наши методы обратного вызова во внешнем классе прослушивателя таким образом, чтобы они всегда возвращали void и принимали целевой объект в качестве аргумента. Однако они могут иметь любое имя и любой уровень доступа, а также могут быть статическими.

public class FileEntityListener {
    @PrePersist
    public void prePersist(File target) { // Persistence logic }

    @PreUpdate
    public void preUpdate(File target) { //Updation logic }

    @PreRemove
    public void preRemove(File target) { //Removal logic }
}

И нам нужно будет зарегистрировать этот класс EntityListener файла в сущности файла или его суперклассе с помощью аннотации @EntityListeners

@Entity
@EntityListeners(FileEntityListener.class)
class File extends Auditable {

    @Id
    @GeneratedValue
    private Integer id;
    private String name;
    private String content;

    // Fields, Getters and Setters
}

Преимущества использования @EntityListeners

  • Прежде всего, мы не должны писать какую-либо бизнес-логику в наших классах сущностей и следовать принципу единой ответственности. Каждый класс сущности должен быть POJO (обычный старый объект Java) .
  • У нас может быть только один метод обратного вызова для определенного события в одном классе, например, в классе разрешен только один метод обратного вызова с @PrePresist. Хотя мы можем определить более одного класса слушателей в @EntityListeners, и у каждого класса слушателей может быть @prePersist.

Например, я использовал @EntityListeners в файле и предоставил ему класс EntityListener файла, а также расширил класс для проверки в классе файлов.

В самом проверяемом классе есть @EntityListeners с классом AuditingEntityListener, поскольку я использую этот класс для сохранения CreatedBy и других вышеупомянутых свойств, вы можете проверить мою предыдущую статью Аудит Spring Data JPA: автоматическое сохранение CreatedBy, CreatedDate, lastmodifiedby, LastModifiedDate для получения более подробной информации.

@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
public abstract class Auditable {

    @CreatedBy
    protected U createdBy;

    @CreatedDate
    @Temporal(TIMESTAMP)
    protected Date createdDate;

    @LastModifiedBy
    protected U lastModifiedBy;

    @LastModifiedDate
    @Temporal(TIMESTAMP)
    protected Date lastModifiedDate;

    // Getters and Setters
}

Нам также нужно будет предоставить геттеры, сеттеры, конструкторы, методы toString и equals для всех сущностей. Тем не менее, вам может понравиться посмотреть Проект Ломбок: Экстрактор шаблонного кода если вы хотите автоматически генерировать эти вещи.

Теперь у нас все готово, и нам нужно реализовать нашу стратегию ведения блога, мы можем хранить журналы истории файла в отдельной таблице истории FileHistory.

@Entity
@EntityListeners(AuditingEntityListener.class)
public class FileHistory {

    @Id
    @GeneratedValue
    private Integer id;

    @ManyToOne
    @JoinColumn(name = "file_id", foreignKey = @ForeignKey(name = "FK_file_history_file"))
    private File file;

    private String fileContent;

    @CreatedBy
    private String modifiedBy;

    @CreatedDate
    @Temporal(TIMESTAMP)
    private Date modifiedDate;

    @Enumerated(STRING)
    private Action action;

    public FileHistory() {
    }

    public FileHistory(File file, Action action) {
        this.file = file;
        this.fileContent = file.toString();
        this.action = action;
    }

    // Getters, Setters
}

Здесь Действие – это перечисление

public enum Action {

    INSERTED("INSERTED"),
    UPDATED("UPDATED"),
    DELETED("DELETED");

    private final String name;

    private Action(String value) {
        this.name = value;
    }

    public String value() {
        return this.name;
    }

    @Override
    public String toString() {
        return name;
    }
}

И нам нужно будет вставлять запись в Историю файлов для каждой операции вставки, обновления, удаления, и нам нужно записать эту логику внутри нашего класса FileEntityListener. Для этой цели нам нужно будет ввести либо класс репозитория, либо EntityManager в класс прослушивателя сущностей файла.

Внедрение компонентов, управляемых Spring, таких как EntityManager, в EntityListeners

Но здесь у нас есть проблема: прослушиватели сущностей создаются JPA, а не Spring, поэтому Spring не может вводить какой-либо компонент, управляемый Spring, например, EntityManager, в любые EntityListeners.

Так если вы попытаетесь автоматически подключить EntityManager внутри класса EntityListener файла, это не сработает

@Autowired EntityManager entityManager; //Will not work and entityManager will be null always

Я также написал отдельную статью о том, как Автоматическое Подключение Компонентов Spring К Классам, Не Управляемым Spring, Таким Как Слушатели Сущностей JPA , вы можете прочитать его , если хотите узнать больше.

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

@Service
public class BeanUtil implements ApplicationContextAware {

    private static ApplicationContext context;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        context = applicationContext;
    }

    public static  T getBean(Class beanClass) {
        return context.getBean(beanClass);
    }

}

А теперь мы напишем логику создания записей истории внутри Fileentrylistener

public class FileEntityListener {

    @PrePersist
    public void prePersist(File target) {
        perform(target, INSERTED);
    }

    @PreUpdate
    public void preUpdate(File target) {
        perform(target, UPDATED);
    }

    @PreRemove
    public void preRemove(File target) {
        perform(target, DELETED);
    }

    @Transactional(MANDATORY)
    private void perform(File target, Action action) {
        EntityManager entityManager = BeanUtil.getBean(EntityManager.class);
        entityManager.persist(new FileHistory(target, action));
    }

}

И теперь, если мы попытаемся сохранить или обновить объект и файл, эти свойства аудита будут автоматически сохранены.

Вы можете найти полный код в этом репозитории Github и, пожалуйста, не стесняйтесь оставлять свои ценные отзывы.

Оригинал: “https://dev.to/njnareshjoshi/jpa-auditing-persisting-audit-logs-automatically-using-entitylisteners-238p”