В одной из моих статей Аудит 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 staticT 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”