Автор оригинала: Vlad Mihalcea.
Вступление
В этой статье мы узнаем, как лучше всего реализовать журнал аудита для отслеживания инструкций ВСТАВКИ, ОБНОВЛЕНИЯ и УДАЛЕНИЯ с помощью Hibernate Envers .
Как объяснялось ранее , CDC (Сбор данных об изменениях) является важным шагом для извлечения событий изменений из приложения OLTP, чтобы сделать их доступными для других модулей в корпоративной системе (например, кэш, хранилище данных).
Хотя Debezium является наиболее эффективным способом выполнения CDC , возможно, вам понадобится более простое решение в вашем проекте. Hibernate Envers-это расширение Hibernate ORM, которое позволяет мгновенно фиксировать события изменений.
В этой статье мы рассмотрим наиболее эффективный способ использования энверов Hibernate.
Спящий режим Энверс-зависимость от Maven
Поскольку Hibernate Envers упакован как отдельная зависимость, если вы хотите ее использовать, вам необходимо объявить следующую зависимость Maven:
org.hibernate hibernate-envers ${hibernate.version}
Спящий режим Envers @Проверенная аннотация
Теперь, после добавления зависимости hibernate-envers
, вам необходимо указать Hibernate, какие объекты должны быть проверены, и это можно сделать с помощью аннотации @Audited
на уровне сущности.
@Entity(name = "Post") @Table(name = "post") @Audited public class Post { @Id private Long id; private String title; //Getters and setters omitted for brevity @Override public String toString() { return "Post{" + "id=" + id + ", title='" + title + '\'' + '}'; } }
Стратегия ведения журнала аудита по умолчанию в режиме гибернации Envers
Как только вы добавите аннотацию @Audited
к своей сущности и сгенерируете схему базы данных с помощью инструмента hbm2ddl
, будут созданы следующие таблицы аудита:
CREATE TABLE post_AUD ( id BIGINT NOT NULL, REV INTEGER NOT NULL, REVTYPE TINYINT, title VARCHAR(255), PRIMARY KEY ( id, REV ) ) CREATE TABLE revinfo ( rev INTEGER GENERATED BY DEFAULT AS IDENTITY ( START WITH 1 ), revtstmp BIGINT, PRIMARY KEY ( rev ) ) ALTER TABLE post_aud ADD CONSTRAINT fkllaf9w93qaiooguo8mfvwtwbg FOREIGN KEY ( REV ) REFERENCES revinfo
Использование hbm2ddl не рекомендуется для производственных сред, поэтому используйте его только для создания прототипа схемы базы данных. Поэтому вам следует предпочесть использовать инструмент автоматической миграции схем, такой как Flyway .
Тем не менее, Hibernate ожидает найти вышеупомянутые таблицы аудита в вашей базе данных, в противном случае Envers потерпит неудачу. Поэтому убедитесь, что вы добавили их в свои сценарии миграции вместе с реальными таблицами сущностей.
В таблице revinfo
хранится номер редакции и ее временная метка эпохи, в то время как в таблице post_AUD
хранится снимок сущности в определенной редакции.
В терминологии Envers, ревизия означает транзакцию базы данных, которая либо вставила, обновила, либо удалила аудируемый объект. У каждого проверяемого объекта есть зеркальная таблица, которая по умолчанию заканчивается суффиксом AUD
и в которой хранится состояние объекта в конце определенной ревизии.
В столбце EVTYPE
хранится порядковый номер Типа редакции
Перечисления, который инкапсулирует одно из следующих изменений состояния сущности:
ДОБАВИТЬ
– anВСТАВИТЬ
Инструкция SQL создала рассматриваемую сущностьMOD
– инструкцияUPDATE
SQL изменила нашу сущностьИнструкция DEL
– aDELETE
SQL удалила проверяемый объект из таблицы базы данных
Сохранение сущности
При создании Записи
сущности и сохранении в текущем контексте сохранения:
Post post = new Post(); post.setId( 1L ); post.setTitle( "High-Performance Java Persistence 1st edition" ); entityManager.persist( post );
Hibernate генерирует следующие три инструкции SQL INSERT
:
INSERT INTO post (title, id) VALUES ('High-Performance Java Persistence 1st edition', 1) INSERT INTO REVINFO (REV, REVTSTMP) VALUES (default, 1503062974131) INSERT INTO post_AUD (REVTYPE, title, id, REV) VALUES (0, 'High-Performance Java Persistence 1st edition', 1, 1)
- Первый оператор создает сущность
Post
и выполняется с помощью Hibernate ORM. - Второй оператор выполняется Envers для создания новой редакции.
- Третий оператор также выполняется Envers для фиксации состояния объекта
Post
в этой редакции.
Обратите внимание, что значение столбца REV TYPE
равно 0, что соответствует типу редакции. ДОБАВЬТЕ
значение перечисления.
Обновление сущности
При обновлении ранее созданной Записи
сущности:
Post post = entityManager.find( Post.class, 1L ); post.setTitle( "High-Performance Java Persistence 2nd edition" );
Hibernate создает следующие инструкции SQL:
SELECT p.id as id1_0_0_, p.title as title2_0_0_ FROM post p WHERE p.id = 1 UPDATE post SET title = 'High-Performance Java Persistence 2nd edition' WHERE id = 1 INSERT INTO REVINFO (REV, REVTSTMP) VALUES (default, 1503064508185) INSERT INTO post_AUD (REVTYPE, title, id, REV) VALUES (1, 'High-Performance Java Persistence 2nd edition', 1, 2)
- Первые два оператора генерируются Hibernate ORM для загрузки и изменения сущности.
- Вторые утверждения генерируются Envers для фиксации новой редакции и нового состояния
Post
сущности.
Обратите внимание, что значение столбца REV TYPE
равно 1, что соответствует типу редакции. MOD
значение перечисления.
Удаление сущности
При удалении Записи
сущности:
entityManager.remove( entityManager.getReference( Post.class, 1L ) );
Hibernate создает следующие инструкции SQL:
DELETE FROM post WHERE id = 1 INSERT INTO REVINFO (REV, REVTSTMP) VALUES (default, 1503065294147) INSERT INTO post_AUD (REVTYPE, title, id, REV) VALUES (2, NULL(VARCHAR), 1, 3)
После удаления сущности из таблицы базы данных с помощью Hibernate ORM Envers вставляет новую редакцию, и все свойства, за исключением идентификатора сущности, будут установлены в null
.
Обратите внимание, что значение столбца ТИП REV
равно 2, что соответствует типу Редакции. DEL
значение перечисления.
Запрос моментальных снимков сущности
Hibernate Envers предлагает AuditReaderFactory
, который использует JPA EntityManager
или Hibernate Сессия
экземпляр и генерирует объект AuditReader
.
Средство AuditReader
можно использовать для запроса журнала аудита, как показано в следующем примере, в котором извлекаются все Запись
моментальные снимки состояния сущности:
Listposts = AuditReaderFactory.get( entityManager ) .createQuery() .forRevisionsOfEntity( Post.class, true, true ) .add( AuditEntity.id().eq( 1L ) ) .getResultList(); assertEquals( 3, posts.size() ); for ( int i = 0; i < posts.size(); i++ ) { LOGGER.info( "Revision {} of Post entity: {}", i + 1, posts.get( i ) ); }
При запуске приведенного выше тестового случая Hibernate генерирует следующие выходные данные:
SELECT p.id as id1_1_, p.REV as REV2_1_, p.REVTYPE as REVTYPE3_1_, p.title as title4_1_ FROM post_AUD p WHERE p.id = 1 ORDER BY p.REV ASC -- Revision 1 of Post entity: Post{id=1, title='High-Performance Java Persistence 1st edition'} -- Revision 2 of Post entity: Post{id=1, title='High-Performance Java Persistence 2nd edition'} -- Revision 3 of Post entity: Post{id=1, title='null'}
Пока все идет так хорошо!
Однако, предполагая, что вы ранее извлекли Публикации
редакции сущностей:
Listrevisions = doInJPA( entityManager -> { return AuditReaderFactory.get( entityManager ).getRevisions( Post.class, 1L ); } );
Если вы хотите загрузить Запись
моментальный снимок сущности на момент данной ревизии:
Post post = (Post) AuditReaderFactory.get( entityManager ) .createQuery() .forEntitiesAtRevision( Post.class, revisions.get( 0 ) ) .getSingleResult(); assertEquals( "High-Performance Java Persistence 1st edition", post.getTitle() );
Hibernate Envers собирается сгенерировать SQL-запрос, подобный этому:
SELECT p1.id AS id1_1_, p1.REV AS REV2_1_, p1.REVTYPE AS REVTYPE3_1_, p1.title AS title4_1_ FROM post_AUD p1 WHERE ( p1.REV IN ( SELECT MAX(p2.REV) FROM post_AUD p2 WHERE p2.REV <= 1 AND p1.id = p2.id ) ) AND p1.REVTYPE <> 2
Ну, это определенно не очень эффективно!
Запросы, подобные приведенному выше, генерируются DefaultAuditStrategy
и для очень большого журнала аудита они работают не так хорошо.
К счастью, Envers предлагает стратегию ValidityAuditStrategy
для решения этой проблемы.
Гибернация включает в себя стратегию проверки достоверности, которая приходит на помощь
Чтобы переключиться с DefaultAuditStrategy
на ValidityAuditStrategy
, у вас есть свойство предоставить следующую конфигурацию гибернации:
Теперь при создании схемы базы данных с помощью инструмента hbm2ddl
вместо этого будут созданы следующие таблицы аудита:
CREATE TABLE post_AUD ( id BIGINT NOT NULL , REV INTEGER NOT NULL , REVTYPE TINYINT , REVEND INTEGER , title VARCHAR(255) , PRIMARY KEY ( id, REV ) ) CREATE TABLE revinfo ( rev INTEGER GENERATED BY DEFAULT AS IDENTITY ( START WITH 1 ), revtstmp BIGINT, PRIMARY KEY ( rev ) ) ALTER TABLE post_AUD ADD CONSTRAINT FKllaf9w93qaiooguo8mfvwtwbg FOREIGN KEY ( REV ) REFERENCES revinfo ALTER TABLE post_AUD ADD CONSTRAINT FKmo46u9kx2pmomhkxbmctlbwmg FOREIGN KEY ( REVEND ) REFERENCES revinfo
Таблица revinfo
идентична таблице DefaultAuditStrategy
, но в post_AWD
есть новый столбец ПЕРЕМОТКА НАЗАД
, который ссылается на таблицу revinfo
и отмечает последнюю редакцию, для которой этот снимок сущности все еще действителен.
Сохранение сущности
При сохранении одной и той же сущности Post
Hibernate генерирует следующие инструкции SQL:
INSERT INTO post (title, id) VALUES ('High-Performance Java Persistence 1st edition', 1) INSERT INTO REVINFO (REV, REVTSTMP) VALUES (default, 1503116813359) INSERT INTO post_AUD (REVTYPE, REVEND, title, id, REV) VALUES (0, NULL(INTEGER), 'High-Performance Java Persistence 1st edition', 1, 1)
Значение столбца REVEND
равно NULL
, так что таким образом, эта запись журнала аудита является действительной.
Обновление сущности
При обновлении сущности Post
Hibernate выполняет следующие инструкции SQL:
SELECT p.id as id1_0_0_, p.title as title2_0_0_ FROM post p WHERE p.id = 1 UPDATE post SET title = 'High-Performance Java Persistence 2nd edition' WHERE id = 1 INSERT INTO REVINFO (REV, REVTSTMP) VALUES (default, 1503117067335) INSERT INTO post_AUD (REVTYPE, REVEND, title, id, REV) VALUES (1, NULL(INTEGER), 'High-Performance Java Persistence 2nd edition', 1, 2) UPDATE post_AUD SET REVEND = 2 WHERE id = 1 AND REV <> 2 AND REVEND IS NULL
Мало того, что добавляется новая запись журнала аудита Post
сущности, но и предыдущая запись обновляется, чтобы отметить, что она больше недействительна.
По сравнению с DefaultAuditStrategy
, ValidityAuditStrategy
выполняет больше работы при хранении журналов аудита, поскольку ему необходимо обновить конец ревизии.
Однако, как и в случае с индексом базы данных, эти дополнительные затраты времени на запись незначительны по сравнению с улучшением времени отклика во время чтения.
Удаление сущности
При удалении объекта Post
Hibernate создает следующие инструкции SQL:
DELETE FROM post WHERE id = 1 INSERT INTO REVINFO (REV, REVTSTMP) VALUES (default, 1503117987724) INSERT INTO post_AUD (REVTYPE, REVEND, title, id, REV) VALUES (2, NULL(INTEGER), NULL(VARCHAR), 1, 3) UPDATE post_AUD SET REVEND = 3 WHERE id = 1 AND REV <> 3 AND REVEND IS NULL
Как и в случае с обновлением Post
сущности, удаление сущности также будет означать конец ревизии для ранее сохраненной записи журнала аудита.
Запрос моментальных снимков сущности
При извлечении всех Post
снимков состояния сущности генерируется один и тот же SQL-запрос, например DefaultAuditStrategy While fetching all
Post entity state snapshots generates the same SQL query like
DefaultAuditStrategy
SELECT p.id as id1_1_, p.REV as REV2_1_, p.REVTYPE as REVTYPE3_1_, p.REVEND as REVEND4_1_, p.title as title5_1_ FROM post_AUD p WHERE p.REV <= 1 AND p.REVTYPE <> 2 AND ( p.REVEND > 1 OR p.REVEND IS NULL)
Намного лучше, чем использовать коррелированный подзапрос! Чтобы ускорить этот тип запроса, мы могли бы добавить индекс для столбцов REV
и REV И
, таким образом, избегая последовательного сканирования.
Вывод
Hibernate Envers чрезвычайно прост в настройке, что делает его очень привлекательным выбором для приложений на основе Hibernate. Хотя DefaultAuditStrategy
выполняет меньше работы при выполнении транзакции изменения состояния сущности, ValidityAuditStrategy
является гораздо лучшим выбором при попытке ускорить получение моментальных снимков сущности.
В зависимости от требований вашего приложения вам следует выбрать стратегию пересмотра, которая лучше соответствует вашим шаблонам доступа к данным журнала аудита.