Аудит с использованием JPA, Hibernate и Spring Data JPA

Данные]

  • ссылка:/tag/hibernate/[Hibernate]

  • ссылка:/tag/jpa/[JPA]

1. Обзор

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

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

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

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

ссылка:/uploads/Screenshot__4.png[]

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() { ... }

}

Внутренние методы обратного вызова всегда должны возвращаться и не принимать аргументов.

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

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

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

Однако у этого подхода есть существенное ограничение. Как указано в JPA

В общем случае метод жизненного цикла переносимого приложения не должен вызывать операции EntityManager или Query__, обращаться к другим экземплярам сущности или изменять отношения в том же контексте постоянства.

Метод обратного вызова жизненного цикла может изменить состояние отсутствия связи объекта, для которого он вызывается.

В отсутствие инфраструктуры аудита мы должны поддерживать схему базы данных и модель домена вручную. Для нашего простого варианта использования давайте добавим два новых свойства к сущности, поскольку мы можем управлять только «состоянием несвязанности сущности». Свойство 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 мы могли бы использовать Interceptors и EventListeners , а также триггеры базы данных для выполнения аудита. Но платформа ORM предлагает Envers, модуль, реализующий аудит и управление версиями постоянных классов.

==== 3.1. Начните с Envers

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

<dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-envers</artifactId>
    <version>${hibernate.version}</version>
</dependency>

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

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

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

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

==== 3.2. Создание таблиц журнала аудита

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

  • установите hibernate.hbm2ddl.auto в create , create-drop или update ,

поэтому Энверс может создавать их автоматически ** используйте o _rg.hibernate.tool.EnversSchemaGenerator _ для экспорта

полная схема базы данных программно ** используйте задачу Ant для генерации соответствующих операторов DDL

  • используйте плагин Maven для генерации схемы базы данных из ваших отображений

(например, Juplo) для экспорта схемы Envers (работает с Hibernate 4 и выше)

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

В нашем случае таблицы bar AUD и foo AUD (если вы также задали Foo как @ Audited ) должны генерироваться автоматически. Таблицы аудита копируют все проверенные поля из таблицы объекта с двумя полями: REVTYPE (значения: «0» для добавления, «1» для обновления, «2» для удаления объекта) и REV .

Кроме того, по умолчанию будет сгенерирована дополнительная таблица с именем REVINFO , которая включает в себя два важных поля: REV и REVTSTMP и записывает временную метку каждой ревизии. И, как вы можете догадаться, bar AUD.REV и foo 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);

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

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

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

AuditReader reader = AuditReaderFactory.get(session);

Envers предоставляет AuditQueryCreator (возвращаемый AuditReader.createQuery () ) для создания запросов, специфичных для аудита.

Следующая строка вернет все экземпляры Bar , измененные в редакции № 2 (где bar AUDIT LOG.REV = 2 ):

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

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

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

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

Затем вы можете указать ограничения, используя класс фабрики AuditEntity :

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

=== 4. Spring Data JPA

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

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

==== 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 , заполняются именем участника, который создал или последним изменил объект. Информация извлекается из SecurityContext ‘s Authentication экземпляра. Если вы хотите настроить значения, заданные для аннотированных полей, вы можете реализовать интерфейс AuditorAware <T> :

public class AuditorAwareImpl implements AuditorAware<String> {

    @Override
    public String getCurrentAuditor() {
       //your custom logic
    }

}

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

@EnableJpaAuditing(auditorAwareRef="auditorProvider")
public class PersistenceConfig {

   //...

    @Bean
    AuditorAware<String> auditorProvider() {
        return new AuditorAwareImpl();
    }

   //...

}

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

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

  • Чистый JPA подход является самым базовым и состоит из использования

обратные вызовы жизненного цикла. Тем не менее, вам разрешено изменять только состояние отсутствия связи с объектом. Это делает обратный вызов @ PreRemove бесполезным для наших целей, так как любые настройки, сделанные вами в методе, будут затем удалены вместе с сущностью.

  • Envers - это зрелый модуль аудита, предоставляемый Hibernate. Это очень

настраивается и не имеет недостатков в реализации чистого JPA. Таким образом, он позволяет нам проверять операцию удаления, поскольку он регистрируется в таблицах, отличных от таблицы объекта.

  • Подход Spring Data JPA обобщает работу с обратными вызовами JPA и

предоставляет удобные аннотации для аудита свойств. Он также готов к интеграции со Spring Security. Недостатком является то, что он наследует те же недостатки подхода JPA, что операция удаления не может быть проверена.

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