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 SetfooSet;
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 AuditorAwareauditorProvider() { return new AuditorAwareImpl(); } //... }
5. Заключение
Мы рассмотрели три подхода к реализации функций аудита:
- Чистый подход JPA является самым базовым и состоит из использования обратных вызовов жизненного цикла. Однако вам разрешено изменять только состояние объекта, не связанное с отношениями. Это делает обратный вызов @PreRemove бесполезным для наших целей, так как любые настройки, которые вы сделали в методе, будут удалены вместе с сущностью.
- Envers-это зрелый модуль аудита, предоставляемый Hibernate. Он легко настраивается и лишен недостатков чистой реализации JPA. Таким образом, это позволяет нам проверять операцию удаления, поскольку она входит в таблицы, отличные от таблицы сущности.
- Подход Spring Data JPA абстрагирует работу с обратными вызовами JPA и предоставляет удобные аннотации для свойств аудита. Он также готов к интеграции с Spring Security. Недостатком является то, что он наследует те же недостатки подхода JPA, поэтому операция удаления не может быть проверена.
Примеры для этой статьи доступны в репозитории GitHub .