Бывает необходимо реагировать на какие-то события происходящие внутри Hibernate и, например, контролировать как сущности переходят из одного состояния в другое. Применений этой возможности можно придумать множество — вести статистику обращений, проверять уровень доступа, вести историю изменений и так далее. Hibernate поддерживает два разных, но похожих, механизма для вмешательства в свои внутренние дела — интерцепторы и события.
Интерцепторы
Интерцептор это интерфейс для вызовов пользовательского кода из Session во время изменения состояния какой-либо сущности. Для использования интерцепторов надо реализовать интерфейс Interceptor или, что удобнее, расширить класс EmptyInterceptor. Последний уже содержит пустые реализации методов интерфейса Interceptor, что позволяет реализовывать только нужные методы.
Для примера я напишу интерцептор, который будет проверять корректность серии/номера паспорта (на самом деле нет) и кидать исключение, если данные ему не нравятся:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
/**
* Check that passport series/no is valid.
*/
public class PassportValidationInterceptor extends EmptyInterceptor {
@Override
public boolean onFlushDirty(Object entity, Serializable id, Object[] currentState, Object[] previousState, String[] propertyNames, Type[] types) {
if (entity instanceof Passport) {
Passport p = (Passport) entity;
if (p.getSeries().length() !=2 || p.getNo().length() != 6) {
throw new IllegalArgumentException("Incorrect passport data");
}
}
return super.onFlushDirty(entity, id, currentState, previousState, propertyNames, types);
}
}
|
В интерцепторе я перехватываю сохранение сущности, проверяю её тип и если это паспорт, проверяю данные на корректность. В результате попытка сохранить паспорт с неверными данными приводит к провалу:
1
2
3
4
|
//Try to save invalid passport
Passport p = (Passport) session.createCriteria(Passport.class).add(Restrictions.eq("series", "AS")).uniqueResult();
p.setSeries("INVALID");
session.save(p);
|
1
|
ERROR: HHH000346: Error during managed flush [Incorrect passport data]
|
Интерцептор может быть применён на уровне SessionFactory:
1
2
3
4
5
|
sessionFactory = new MetadataSources(registry)
.buildMetadata()
.getSessionFactoryBuilder()
.applyInterceptor(new PassportValidationInterceptor())
.build();
|
Либо назначен при конструировании конкретной Session:
1
2
3
4
|
sessionFactory
.withOptions()
.interceptor(new PassportValidationInterceptor())
.openSession();
|
Понятно, что в первом случае интерцептор будет назначаться каждой новой Session автоматически.
События
События это другой интерфейс реагирования на изменения состояния сущностей, во многом аналогичный интерцепторам. Отличия в основном в более удобном api и возможности конфигурирования через xml.
Событий существует великое множество. Грубо можно положить,что на каждый вызов у Session есть своё собственное событие, плюс пре-событие и пост-событие. Например, посчитаем, сколько раз в ходе выполнения кода делается вызов save():
1
2
3
4
5
6
7
8
9
10
11
|
/**
* Counts save events.
*/
public class SaveCounter implements SaveOrUpdateEventListener {
AtomicInteger counter = new AtomicInteger();
@Override
public void onSaveOrUpdate(SaveOrUpdateEvent e) throws HibernateException {
System.out.println("Saving: " + e.getResultId());
System.out.println("Totally seen " + counter.incrementAndGet() + " save events");
}
}
|
Каждому типу события сооветствует свой *EventListener интерфейс, поэтому для save() вызова я использую SaveOrUpdateListener. Стоит отметить, что хранить данные (состояние) внутри обработчика событий дурной тон и следует этого избегать. С другой стороны, я использую атомарный счётчик, который можно безопасно использовать в конкуретной среде.
Обработчик события надо, помимо прочего, связать с событием. Это можно сделать сконфигурировав Hibernate программно, а можно используя xml конфигурацию:
1
2
3
|
<event type="save">
<listener class="ru.easyjava.data.hibernate.events.SaveCounter"/>
</event>
|
Теперь любой вызов save() приведёт к такой записи на экране:
1
2
|
Saving: 1
Totally seen 1 save events
|
Код примера доступен на github.