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

Аудит с помощью JPA, Hibernate и Spring Data JPA

В этой статье демонстрируются три подхода к внедрению аудита в приложение: JPA, Hibernate Envers и Spring Data JPA.

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

1. Обзор

В контексте ORM аудит базы данных означает отслеживание и протоколирование событий, связанных с постоянными сущностями, или просто управление версиями сущностей. Вдохновленные триггерами SQL, события представляют собой операции вставки, обновления и удаления объектов. Преимущества аудита баз данных аналогичны тем, которые предоставляет система управления версиями исходных текстов.

Мы продемонстрируем три подхода к внедрению аудита в приложение. Во-первых, мы реализуем его с помощью стандартного JPA. Далее мы рассмотрим два расширения JPA, которые предоставляют свои собственные функции аудита: одно предоставляется Hibernate, другое-Spring Data.

Вот примеры связанных сущностей Bar и Foo, , которые будут использоваться в этом примере:

2. Аудит С Помощью JPA

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

2.1. @prePersist, @preUpdate и @PreRemove

В классе JPA Entity метод может быть указан как обратный вызов, который будет вызван во время определенного события жизненного цикла сущности. Поскольку нас интересуют обратные вызовы, которые выполняются перед соответствующими операциями DML, для наших целей доступны аннотации @prePersist , @preUpdate и @PreRemove обратного вызова:

@Entity
public class Bar {
      
    @PrePersist
    public void onPrePersist() { ... }
      
    @PreUpdate
    public void onPreUpdate() { ... }
      
    @PreRemove
    public void onPreRemove() { ... }
      
}

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

Имейте в виду, что аннотация @Version в JPA не имеет строгого отношения к нашей теме – она больше связана с оптимистической блокировкой, чем с данными аудита.

2.2. Реализация методов обратного вызова

Однако при таком подходе существует значительное ограничение. Как указано в спецификации JPA 2 (JSR 317):

Как правило, метод жизненного цикла переносимого приложения не должен вызывать EntityManager или Запрос операции, обращаться к другим экземплярам сущностей или изменять отношения в том же контексте сохранения. Метод обратного вызова жизненного цикла может изменять состояние, не связанное с отношением, сущности, на которой он вызывается.

В отсутствие структуры аудита мы должны поддерживать схему базы данных и модель домена вручную. Для нашего простого случая использования давайте добавим два новых свойства к сущности, так как мы можем управлять только “состоянием, не связанным с сущностью”. Свойство operation будет хранить имя выполненной операции, а свойство timestamp – для метки времени операции:

@Entity
public class Bar {
     
    //...
     
    @Column(name = "operation")
    private String operation;
     
    @Column(name = "timestamp")
    private long timestamp;
     
    //...
     
    // standard setters and getters for the new properties
     
    //...
     
    @PrePersist
    public void onPrePersist() {
        audit("INSERT");
    }
     
    @PreUpdate
    public void onPreUpdate() {
        audit("UPDATE");
    }
     
    @PreRemove
    public void onPreRemove() {
        audit("DELETE");
    }
     
    private void audit(String operation) {
        setOperation(operation);
        setTimestamp((new Date()).getTime());
    }
     
}

Если вам нужно добавить такой аудит в несколько классов, вы можете использовать @EntityListeners для централизации кода. Например:

@EntityListeners(AuditListener.class)
@Entity
public class Bar { ... }
public class AuditListener {
    
    @PrePersist
    @PreUpdate
    @PreRemove
    private void beforeAnyOperation(Object object) { ... }
    
}

3. Спящие Энверы

С помощью Hibernate мы могли бы использовать Перехватчики и Прослушиватели событий , а также триггеры базы данных для выполнения аудита. Но платформа ORM предлагает Envers, модуль, реализующий аудит и управление версиями постоянных классов.

3.1. Начало Работы С Envers

Чтобы настроить Envers, вам нужно добавить hibernate-envers JAR в путь к классу:


    org.hibernate
    hibernate-envers
    ${hibernate.version}

Затем просто добавьте аннотацию @Audited либо в @Entity (для аудита всей сущности), либо в конкретный @Столбец s (если вам нужно провести аудит только определенных свойств):

@Entity
@Audited
public class Bar { ... }

Обратите внимание, что Бар имеет отношение “один ко многим” с Едой . В этом случае нам нужно либо провести аудит Food , добавив @Audited on Foot , либо установить @NotAudited на свойство отношения в Bar :

@OneToMany(mappedBy = "bar")
@NotAudited
private Set fooSet;

3.2. Создание Таблиц Журнала Аудита

Существует несколько способов создания таблиц аудита:

  • установите hibernate.hbm2ddl.auto в create , create-drop или update , чтобы Энверы могли создавать их автоматически
  • используйте o rg.hibernate.tool.Генератор схем Envers для программного экспорта полной схемы базы данных
  • используйте задачу Ant для создания соответствующих операторов DDL
  • используйте плагин Maven для создания схемы базы данных из ваших сопоставлений (например, Juplo) для экспорта схемы Envers (работает с Hibernate 4 и выше)

Мы пойдем по первому маршруту, так как он самый простой, но имейте в виду, что использование hibernate.hbm2ddl.auto небезопасно в производстве.

В нашем случае таблицы bar_AUD и foot_AUD (если вы также установили Foot как @Audited ) должны создаваться автоматически. Таблицы аудита копируют все проверенные поля из таблицы сущности с двумя полями: RECTYPE (значения: “0” для добавления, “1” для обновления, “2” для удаления сущности) и REV .

Кроме того, по умолчанию будет сгенерирована дополнительная таблица с именем REVINFO , она включает в себя два важных поля/| REV и REVTSTMP и записывает метку времени каждой ревизии. И, как вы можете догадаться, bar_AUD.REV и food_AUD.REV на самом деле являются внешними ключами к REVINFO.REV.

3.3. Настройка Envers

Вы можете настроить свойства Envers так же, как и любое другое свойство Hibernate.

Например, давайте изменим суффикс таблицы аудита (по умолчанию ” _AUD “) на ” _AUDIT_LOG “. Вот как установить значение соответствующего свойства org.hibernate.envers.audit_table_suffix :

Properties hibernateProperties = new Properties(); 
hibernateProperties.setProperty(
  "org.hibernate.envers.audit_table_suffix", "_AUDIT_LOG"); 
sessionFactory.setHibernateProperties(hibernateProperties);

Полный список доступных свойств можно найти в документации Envers .

3.4. Доступ к Истории сущностей

Вы можете запросить исторические данные способом, аналогичным запросу данных с помощью API критериев гибернации. Доступ к истории аудита объекта можно получить с помощью интерфейса AuditReader , который можно получить с помощью открытого EntityManager или Сеанса через AuditReaderFactory :

AuditReader reader = AuditReaderFactory.get(session);

Envers предоставляет AuditQueryCreator (возвращается AuditReader.CreateQuery() ) для создания запросов, специфичных для аудита. В следующей строке будут возвращены все экземпляры Bar , измененные в редакции № 2 (где bar_AUDIT_LOG.REV ):

AuditQuery query = reader.createQuery()
  .forEntitiesAtRevision(Bar.class, 2)

Вот как запросить изменения Bar , т. Е. Это приведет к получению списка всех экземпляров Bar во всех их состояниях, которые были проверены:

AuditQuery query = reader.createQuery()
  .forRevisionsOfEntity(Bar.class, true, true);

Если второй параметр имеет значение false, результат соединяется с таблицей REVINFO , в противном случае возвращаются только экземпляры сущностей. Последний параметр указывает, следует ли возвращать удаленные Bar экземпляры.

Затем вы можете указать ограничения с помощью класса Audit Entity factory:

query.addOrder(AuditEntity.revisionNumber().desc());

4. Весенние данные JPA

Spring Data JPA – это фреймворк, который расширяет JPA, добавляя дополнительный уровень абстракции в верхней части поставщика JPA. Этот уровень позволяет поддерживать создание репозиториев JPA путем расширения интерфейсов репозиториев Spring JPA.

Для наших целей вы можете расширить CrudRepository ID расширяет сериализуемый> , интерфейс для общих операций CRUD. Как только вы создали и внедрили свой репозиторий в другой компонент, Spring Data автоматически предоставит реализацию, и вы будете готовы добавить функции аудита. ID расширяет сериализуемый>

4.1. Включение аудита JPA

Для начала мы хотим включить аудит с помощью конфигурации аннотаций. Для этого просто добавьте @EnableJpaAuditing в свой @Configuration класс:

@Configuration
@EnableTransactionManagement
@EnableJpaRepositories
@EnableJpaAuditing
public class PersistenceConfig { ... }

4.2. Добавление прослушивателя обратного вызова сущности Spring

Как мы уже знаем, JPA предоставляет аннотацию @EntityListeners для указания классов прослушивателей обратного вызова. Spring Data предоставляет свой собственный класс прослушивателя сущностей JPA: AuditingEntityListener . Итак, давайте укажем прослушиватель для объекта Bar :

@Entity
@EntityListeners(AuditingEntityListener.class)
public class Bar { ... }

Теперь информация об аудите будет собираться слушателем при сохранении и обновлении объекта Bar .

4.3. Отслеживание Созданных и Последних Измененных дат

Затем мы добавим два новых свойства для хранения созданных и последних измененных дат в нашу сущность Bar . Свойства аннотируются аннотациями @CreatedDate и @LastModifiedDate соответственно, и их значения устанавливаются автоматически:

@Entity
@EntityListeners(AuditingEntityListener.class)
public class Bar {
    
    //...
    
    @Column(name = "created_date", nullable = false, updatable = false)
    @CreatedDate
    private long createdDate;

    @Column(name = "modified_date")
    @LastModifiedDate
    private long modifiedDate;
    
    //...
    
}

Как правило, вы перемещаете свойства в базовый класс (с аннотацией @MappedSuperclass ), который будет расширен всеми вашими проверяемыми объектами. В нашем примере мы добавляем их непосредственно в Bar для простоты.

4.4. Аудит автора изменений с помощью Spring Security

Если ваше приложение использует Spring Security, вы не можете отслеживать не только время внесения изменений, но и то, кто их внес:

@Entity
@EntityListeners(AuditingEntityListener.class)
public class Bar {
    
    //...
    
    @Column(name = "created_by")
    @CreatedBy
    private String createdBy;

    @Column(name = "modified_by")
    @LastModifiedBy
    private String modifiedBy;
    
    //...
    
}

Столбцы с аннотациями @CreatedBy и @LastModifiedBy заполняются именем участника, который создал или в последний раз изменил сущность. Информация извлекается из Контекста безопасности ‘s Аутентификации экземпляра. Если вы хотите настроить значения, заданные для аннотированных полей, вы можете реализовать интерфейс AuditorAware :

public class AuditorAwareImpl implements AuditorAware {
 
    @Override
    public String getCurrentAuditor() {
        // your custom logic
    }

}

Чтобы настроить приложение на использование AuditorAwareImpl для поиска текущего участника, объявите компонент типа AuditorAware , инициализированный экземпляром AuditorAwareImpl , и укажите имя компонента в качестве значения параметра auditorawaref в @EnableJpaAuditing :

@EnableJpaAuditing(auditorAwareRef="auditorProvider")
public class PersistenceConfig {
    
    //...
    
    @Bean
    AuditorAware auditorProvider() {
        return new AuditorAwareImpl();
    }
    
    //...
    
}

5. Заключение

Мы рассмотрели три подхода к реализации функций аудита:

  • Чистый подход JPA является самым базовым и состоит из использования обратных вызовов жизненного цикла. Однако вам разрешено изменять только состояние объекта, не связанное с отношениями. Это делает обратный вызов @PreRemove бесполезным для наших целей, так как любые настройки, которые вы сделали в методе, будут удалены вместе с сущностью.
  • Envers-это зрелый модуль аудита, предоставляемый Hibernate. Он легко настраивается и лишен недостатков чистой реализации JPA. Таким образом, это позволяет нам проверять операцию удаления, поскольку она входит в таблицы, отличные от таблицы сущности.
  • Подход Spring Data JPA абстрагирует работу с обратными вызовами JPA и предоставляет удобные аннотации для свойств аудита. Он также готов к интеграции с Spring Security. Недостатком является то, что он наследует те же недостатки подхода JPA, поэтому операция удаления не может быть проверена.

Примеры для этой статьи доступны в репозитории GitHub .