Автор оригинала: Vlad Mihalcea.
Вступление
В этой статье мы рассмотрим, как работают прослушиватели событий Hibernate и как вы добавляете пользовательские прослушиватели для перехвата изменений сущностей и их репликации в другие таблицы базы данных.
Недавно один из читателей моего блога задал очень хороший вопрос о StackOverflow .
@vlad_mihalcea @vlad_mihalcea hi! I’m a regular reader of your tweets & blogs, and have a problem I’d really appreciate if you could cast your eyes over! Very much appreciated 🙂 Rename a table in a backwards compatible way using JPA and Postgres (i.e. duplicate/alias) https://t.co/4uLvBUWeoO https://t.co/52WMVBYMLp Привет! Я регулярно читаю ваши твиты и блоги, и у меня есть проблема, которую я был бы очень признателен, если бы вы могли взглянуть на нее! Очень признателен 🙂
Поскольку моя главная цель как защитника разработчиков Hibernate-помочь разработчикам Java получить максимальную отдачу от JPA и Hibernate, я решил, что это хорошая возможность поговорить о механизме прослушивания событий Hibernate.
Как перехватить изменения сущности с помощью #Hibernate прослушивателей событий @vlad_mihalcea https://t.co/crSOQ1UZZa
Модель предметной области
Предположим, мы хотим перенести наше приложение, чтобы использовать новую таблицу базы данных (например, post
) вместо старой (например, old_post
). Сообщение
и old_post
таблицы выглядят следующим образом:
Обе таблицы базы данных имеют общий первичный ключ, а столбец id
таблицы old_post
является как Первичным ключом, так и внешним ключом к таблице post
Both database tables share the Primary Key, and the id
column of the old_post
table is both the Primary Key and a Foreign Key to the
Нам нужно только сопоставить сущность Post
, и изменения в более новой сущности также будут реплицированы в таблицу old_post
:
@Entity(name = "Post") @Table(name = "post") public class Post { @Id private Long id; private String title; @Column(name = "created_on") private LocalDate createdOn = LocalDate.now(); @Version private int version; //Getters and setters omitted for brevity }
Более новая таблица post
также содержит новый столбец, который будет пропущен при репликации изменений, внесенных в таблицу post
.
Репликация изменений с помощью CDC
Существует множество способов репликации изменений, происходящих в системе баз данных. Эта функция называется CDC (Сбор данных об изменениях) .
Наиболее популярным методом CDC является использование триггеров базы данных. Менее известным методом является анализ журнала транзакций базы данных (например, Журнал повтора в Oracle, журнал предварительной записи в PostgreSQL) с использованием такого инструмента, как Debezium .
Если ваше приложение выполняет все операции с базой данных через режим гибернации, вы также можете использовать механизм прослушивания событий гибернации для перехвата изменений сущностей.
Система событий гибернации
За кулисами Hibernate использует систему, основанную на событиях, для обработки переходов состояний сущностей . org.hibernate.событие.spi. Тип события
Перечисление Java определяет все типы событий, поддерживаемые Hibernate.
При вызове EntityManager
//сохраняйтесь
метод, режим гибернации запускает
Постоянный это обрабатывается
DefaultPersistEventListener . Вы можете либо заменить прослушиватели событий по умолчанию, используя собственные реализации соответствующих интерфейсов прослушивателей событий, либо добавить прослушиватели до и после событий, такие как
PreInsertEventListener или
PostInsertEventListener
Перехват события вставки сущности
Чтобы перехватить событие вставки сущности, мы можем использовать следующий Прослушиватель событий вставки репликации
который реализует интерфейс Hibernate PostInsertEventListener
:
public class ReplicationInsertEventListener implements PostInsertEventListener { public static final ReplicationInsertEventListener INSTANCE = new ReplicationInsertEventListener(); @Override public void onPostInsert( PostInsertEvent event) throws HibernateException { final Object entity = event.getEntity(); if(entity instanceof Post) { Post post = (Post) entity; event.getSession().createNativeQuery( "INSERT INTO old_post (id, title, version) " + "VALUES (:id, :title, :version)") .setParameter("id", post.getId()) .setParameter("title", post.getTitle()) .setParameter("version", post.getVersion()) .setFlushMode(FlushMode.MANUAL) .executeUpdate(); } } @Override public boolean requiresPostCommitHanding( EntityPersister persister) { return false; } }
Итак, после вставки сущности Post
мы запускаем дополнительную инструкцию SQL INSERT для создания зеркальной записи в таблице old_post
.
Перехват события обновления сущности
Чтобы перехватить событие обновления сущности, мы можем использовать следующий Прослушиватель событий обновления репликации
который реализует интерфейс Hibernate PostUpdateEventListener
:
public class ReplicationUpdateEventListener implements PostUpdateEventListener { public static final ReplicationUpdateEventListener INSTANCE = new ReplicationUpdateEventListener(); @Override public void onPostUpdate( PostUpdateEvent event) { final Object entity = event.getEntity(); if(entity instanceof Post) { Post post = (Post) entity; event.getSession().createNativeQuery( "UPDATE old_post " + "SET title = :title, version = :version " + "WHERE id = :id") .setParameter("id", post.getId()) .setParameter("title", post.getTitle()) .setParameter("version", post.getVersion()) .setFlushMode(FlushMode.MANUAL) .executeUpdate(); } } @Override public boolean requiresPostCommitHanding( EntityPersister persister) { return false; } }
После обновления сущности Post
мы выполняем инструкцию SQL UPDATE, чтобы изменить запись зеркального отображения в таблице old_post
.
Перехват события удаления сущности
Чтобы перехватить событие удаления сущности, мы можем использовать следующее Репликация DeleteEventListener
, реализующая режим гибернации PreDeleteEventListener
интерфейс:
public class ReplicationDeleteEventListener implements PreDeleteEventListener { public static final ReplicationDeleteEventListener INSTANCE = new ReplicationDeleteEventListener(); @Override public boolean onPreDelete( PreDeleteEvent event) { final Object entity = event.getEntity(); if(entity instanceof Post) { Post post = (Post) entity; event.getSession().createNativeQuery( "DELETE FROM old_post " + "WHERE id = :id") .setParameter("id", post.getId()) .setFlushMode(FlushMode.MANUAL) .executeUpdate(); } return false; } }
В то время как для вставки и обновления мы использовали прослушиватели событий после вставки и после обновления, для операции удаления нам необходимо использовать прослушиватель событий перед удалением, поскольку запись old_post
должна быть удалена до удаления родительской записи post
.
Регистрация прослушивателей пользовательских сущностей
Чтобы зарегистрировать пользовательские прослушиватели событий, которые мы только что создали, мы можем реализовать org.hibernate.integrator.spi. Интегратор
интерфейс для добавления прослушивателей в режим гибернации Список событий
.
public class ReplicationEventListenerIntegrator implements Integrator { public static final ReplicationEventListenerIntegrator INSTANCE = new ReplicationEventListenerIntegrator(); @Override public void integrate( Metadata metadata, SessionFactoryImplementor sessionFactory, SessionFactoryServiceRegistry serviceRegistry) { final EventListenerRegistry eventListenerRegistry = serviceRegistry.getService(EventListenerRegistry.class); eventListenerRegistry.appendListeners( EventType.POST_INSERT, ReplicationInsertEventListener.INSTANCE ); eventListenerRegistry.appendListeners( EventType.POST_UPDATE, ReplicationUpdateEventListener.INSTANCE ); eventListenerRegistry.appendListeners( EventType.PRE_DELETE, ReplicationDeleteEventListener.INSTANCE ); } @Override public void disintegrate( SessionFactoryImplementor sessionFactory, SessionFactoryServiceRegistry serviceRegistry) { } }
Чтобы указать Hibernate использовать интегратор прослушивателя событий репликации , нам нужно настроить свойство конфигурации
hibernate.integrator_provider
, которое должно быть задано программно для использования объекта IntegratorProvider
.
Чтобы узнать, как можно задать свойство конфигурации hibernate.integrator_provider
при использовании Spring с JPA или Spring с Hibernate, ознакомьтесь с этой статьей .
Время тестирования
Теперь, когда сохраняется Сообщение
сущность:
Post post1 = new Post(); post1.setId(1L); post1.setTitle( "The High-Performance Java Persistence book is to be released!" ); entityManager.persist(post1);
Hibernate выполняет следующие инструкции SQL insert:
INSERT INTO post ( created_on, title, version, id ) VALUES ( '2018-12-12', 'The High-Performance Java Persistence book is to be released!', 0, 1 ) INSERT INTO old_post ( id, title, version ) VALUES ( 1, 'The High-Performance Java Persistence book is to be released!', 0 )
Теперь при обновлении ранее вставленной Записи
сущности и создании новой Записи
:
Post post1 = entityManager.find(Post.class, 1L); post1.setTitle( post1.getTitle().replace("to be ", "") ); Post post2 = new Post(); post2.setId(2L); post2.setTitle( "The High-Performance Java Persistence book is awesome!" ); entityManager.persist(post2);
Hibernate выполняет следующие инструкции SQL:
SELECT p.id as id1_1_0_, p.created_on as created_2_1_0_, p.title as title3_1_0_, p.version as version4_1_0_ FROM post p WHERE p.id = 1 INSERT INTO post ( created_on, title, version, id ) VALUES ( '2018-12-12', 'The High-Performance Java Persistence book is awesome!', 0, 2 ) INSERT INTO old_post ( id, title, version ) VALUES ( 2, 'The High-Performance Java Persistence book is awesome!', 0 ) UPDATE post SET created_on = '2018-12-12', title = 'The High-Performance Java Persistence book is released!', version = 1 WHERE id = 1 and version = 0 UPDATE old_post SET title = 'The High-Performance Java Persistence book is released!', version = 1 WHERE id = 1
Обратите внимание, что как вставка сущности, так и обновление были правильно реплицированы в таблицу old_post
.
При удалении Записи
сущности:
entityManager.remove( entityManager.getReference(Post.class, 1L) );
Hibernate собирается удалить запись old_post
до строки post
таблицы:
DELETE FROM old_post WHERE id = 1 DELETE FROM post WHERE id = 1 AND version = 1
Потрясающе, правда?
Вывод
Механизм системы событий гибернации очень удобен, когда дело доходит до настройки логики доступа к данным. Как уже объяснялось , вы также можете использовать прослушиватели событий Hibernate для увеличения версии корневой сущности всякий раз, когда дочерняя или внучатая запись вставляется, обновляется или удаляется.
В то время как прослушиватели событий Hibernate могут отслеживать переходы состояний сущностей, изменения на уровне SQL, которые происходят с помощью собственных SQL-запросов или инструкций массового обновления или удаления, не могут быть перехвачены. Если вам нужен более общий способ отслеживания изменений записей таблицы, вместо этого следует использовать триггеры базы данных.