Pull to refresh

Hibernate Envers: аудирование операций

Reading time4 min
Views34K

Зачем это надо?


В enterprise-разработке часто бывает необходимо отслеживать процесс редактирования каких-нибудь таблиц.
Скажем, завели у вас документ в системе. Потом кто-то поменял ему серию. Потом номер. Потом сменил тип с паспорта РФ на загран. паспорт Германии. Хотелось бы иметь возможность отследить историю изменений и, скажем, настучать по шапке пользователю, который совершил ошибку.



Чем пользуемся?

Будем исходить из того, что наша система работает с базой через Hibernate. В этом случае нам доступен замечательный механизм аудирования записей: Envers.

Что получим в итоге?

Для каждой таблицы, чьи изменения мы хотим отслеживать Envers создаст нам по дополнительной табличке, в которую будет класть запись каждый раз, когда мы что-то сделали с аудированной таблицей. Например:

Таблица документ (DOCUMENT):
ID SERIES NUMBER
1 1000 100000


Таблица аудита для документов: (DOCUMENT_AUD)

ID REV REVTYPE SERIES NUMBER
1 1 0 1000 100000
1 2 1 1345 100000


Как видно из таблицы, у нас 2 ревизии для документа с id = 1. Одна( с REVTYPE = 0) — это вновь добавленная запись, вторая — изменение записи. Серия 1000 стала 1345.

Окей, скажите вы, но когда это произошло? Envers генерирует еще одну таблицу, которая по-умолчанию называется REVINFO. В нее кладется номер ревизии и timestamp. Но количество полей можно расширить.

Что же нужно, чтобы все это взлетело?

Из библиотек, ничего, кроме самого hibernate нам не понадобится.

Надо только добавить следующие строки в persistence.xml, но я думаю всем понятно, зачем они нужны:

   <property name="hibernate.ejb.event.post-insert"           value="org.hibernate.ejb.event.EJB3PostInsertEventListener,org.hibernate.envers.event.AuditEventListener" />
   <property name="hibernate.ejb.event.post-update"
             value="org.hibernate.ejb.event.EJB3PostUpdateEventListener,org.hibernate.envers.event.AuditEventListener" />
   <property name="hibernate.ejb.event.post-delete"
             value="org.hibernate.ejb.event.EJB3PostDeleteEventListener,org.hibernate.envers.event.AuditEventListener" />
   <property name="hibernate.ejb.event.pre-collection-update"
             value="org.hibernate.envers.event.AuditEventListener" />
   <property name="hibernate.ejb.event.pre-collection-remove"
             value="org.hibernate.envers.event.AuditEventListener" />
   <property name="hibernate.ejb.event.post-collection-recreate"
             value="org.hibernate.envers.event.AuditEventListener" />


Чтобы указать, что сущность нужно аудировать, необходимо указать аннотацию Auditable, вот так:

@Entity
@Audited
public Document


Если вы генерите схему по классам, то для того, чтобы добавились таблицы аудита нужно поменять classname у антовой таски, которая запускает hbm2ddl:

classname="org.hibernate.tool.ant.EnversHibernateToolTask"


Итак, у нас есть табличка с версиями и таблица с временем для этих ревизий. А что если мы хотим немного расширить эту таблицу и дать ей другое имя? Нам на помощь спешит аннотация @RevisionEntity.

Определям свою entity:

@Entity
@org.hibernate.envers.RevisionEntity(RevisionListener.class)
public class RevisionEntity extends DefaultRevisionEntity {
}


DefaultRevisionEntity здесь — это сущность по-умолчанию, которая уже содержит в себе таймстамп и номер ревизии. Если хочется переопределить что-то, то можно сделать так:

@Entity
@RevisionEntity(ExampleListener.class)
public class ExampleRevEntity {
    @Id
    @GeneratedValue
    @RevisionNumber
    private int id;

    @RevisionTimestamp
    private long timestamp;

...
}


@RevisionNumber и @RevisionTimestamp могут использоваться только один раз, поэтому если вы наследуетесь от DefaultRevisionEntity, то второй раз их использовать уже нельзя.

Как же дополнить нашу сущность? Так же, как и любую другую!

@Entity
@org.hibernate.envers.RevisionEntity(RevisionListener.class)
public class RevisionEntity extends DefaultRevisionEntity {

   @ManyToOne
   private User user;
 
   // геттеры, сеттеры

}


Но вот вопрос: а когда же эти дополнительные данные класть? Для этого нужен RevisionListener. Это некий класс, который имплементирует org.hibernate.envers.RevisionListener и имеет один метод:

public void newRevision(Object revision);

Он будет вызыван когда наша аудированная сущность(Document) изменяется. Объект revision здесь — это наша RevisionEntity. Так что мы можем написать что-нибудь вроде

RevisionEntity revisionEntity = (RevisionEntity) revision;
revisionEntity.setUser(...); // надо как-то достать сюда юзера, да


А как же читать данные?


Так как у вас нет сущности, которая маппируется на таблицу аудита(Document_AUD), то вы и не можете написать нормальную query. Но Envers приходит вам на помощь. Чтобы читать эту таблицу нам понадится AuditQuery. Получить его можно так:

AuditQuery query = AuditReaderFactory.get(em).createQuery();


Ну а дальше нам просто остается сказать, что именно нам нужно, конкретная ревизия или может список ревизий:

query.forRevisionsOfEntity(Document.class, false, false);


Флаги здесь очень важны. Если последний из них будет true, то query вернет так же и удаленные записи.
А вот второй флаг регулирует возвращаемое значение. Если стоит true — то в списке будут только сами ревизии. А если нет, то возвращается список, состоящих из массивов по 3 элемента.

Первый — это ревизия, второй — это RevisionEntity, а третий — тип ревизии.

На эту query можно навешивать различные фильтрации, например, по id главной сущности:

query.add(AuditEntity.id().eq(docId))


Или по параметрам RevisionEntity:

query.add(AuditEntity.revisionProperty("user_id").eq(userId))


но сейчас(hibernate 3.6.1 — 3.6.4) эта фича не работает, ибо баг.

Ну и, наконец, получаем список:

List<Object []> resultList = query.getResultList();


Стратегии аудита


Для эффективности запросов рекомендуется использовать ValidityAuditStrategy. Эта стратегия добавляет в таблицы _AUD еще одно поле — REVEND. Таким образом не нужно делать вложенные селекты, для того, чтобы получать максимальную ревизию.

Для ее включения нужно всего лишь добавить строку в persistence.xml:

            <property name="org.hibernate.envers.audit_strategy"
                      value="org.hibernate.envers.strategy.ValidityAuditStrategy"/>


Литература:

Hibernate Envers

Это статья хабраюзера dzigoro, попросил опубликовать.
Tags:
Hubs:
+27
Comments11

Articles

Change theme settings