Интерцепторы и события в Hibernate

Бывает необходимо реагировать на какие-то события происходящие внутри и, например, контролировать как сущности переходят из одного состояния в другое. Применений этой возможности можно придумать множество — вести статистику обращений, проверять уровень доступа, вести историю изменений и так далее. Hibernate поддерживает два разных, но похожих, механизма для вмешательства в свои внутренние дела — интерцепторы и события.

Интерцепторы

Интерцептор это интерфейс для вызовов пользовательского кода из Session во время изменения состояния какой-либо сущности. Для использования интерцепторов надо реализовать интерфейс Interceptor или, что удобнее, расширить класс EmptyInterceptor. Последний уже содержит пустые реализации методов интерфейса Interceptor, что позволяет реализовывать только нужные методы.

Для примера я напишу интерцептор, который будет проверять корректность серии/номера паспорта (на самом деле нет) и кидать исключение, если данные ему не нравятся:

В интерцепторе я перехватываю сохранение сущности, проверяю её тип и если это паспорт, проверяю данные на корректность. В результате попытка сохранить паспорт с неверными данными приводит к провалу:

Интерцептор может быть применён на уровне SessionFactory:

Либо назначен при конструировании конкретной Session:

Понятно, что в первом случае интерцептор будет назначаться каждой новой Session автоматически.

События

События это другой интерфейс реагирования на изменения состояния сущностей, во многом аналогичный интерцепторам. Отличия в основном в более удобном api и возможности конфигурирования через xml.

Событий существует великое множество. Грубо можно положить,что на каждый вызов у Session есть своё собственное событие, плюс пре-событие и пост-событие. Например, посчитаем, сколько раз в ходе выполнения кода делается вызов save():

Каждому типу события сооветствует свой *EventListener интерфейс, поэтому для save() вызова я использую SaveOrUpdateListener. Стоит отметить, что хранить данные (состояние) внутри обработчика событий дурной тон и следует этого избегать. С другой стороны, я использую атомарный счётчик, который можно безопасно использовать в конкуретной среде.

Обработчик события надо, помимо прочего, связать с событием. Это можно сделать сконфигурировав Hibernate программно, а можно используя xml конфигурацию:

Теперь любой вызов save() приведёт к такой записи на экране:

Код примера доступен на github.