Статьи

Как сохранить историю таблиц в Hibernate

Чтобы сохранить историю базы данных или отследить изменение строки таблицы базы данных, мы создаем таблицу версий, которая содержит поля, идентичные исходной таблице. Каждый раз, когда изменяется исходная таблица, мы создаем еще одну запись в таблице версий. Поэтому для каждого запроса на обновление мы должны написать запрос на вставку в таблицу версий. В hibernate доступен модуль для управления простым аудитом объектов, и нам не нужно самостоятельно писать отдельный запрос на вставку.

Hibernate Envers предоставляет встроенный механизм для ведения истории объектов в базе данных. Envers — это библиотека для Hibernate, которая поможет нам легко достичь функциональности аудита. Это было создано Адамом Варски . Начиная с Hibernate 3.5, Envers включен в качестве основного модуля Hibernate. Давайте рассмотрим пример использования Envers для ведения истории объектов.

Вот зависимость pom для Envers (версия будет такой же, как ваше ядро ​​hibernate и менеджер сущностей):

1
2
3
4
<dependency>  <groupId>org.hibernate</groupId>
  <artifactId>hibernate-envers</artifactId>
  <version>4.0.1.Final</version>
</dependency>

Вы должны настроить слушателей в своем hibernate.cfg.xml.

1
2
3
4
5
6
7
8
<mapping class="com.javaroots.model.User" />
 
  <listener class="org.hibernate.envers.event.AuditEventListener" type="post-insert"/>
  <listener class="org.hibernate.envers.event.AuditEventListener" type="post-update"/>
  <listener class="org.hibernate.envers.event.AuditEventListener" type="post-delete"/>
  <listener class="org.hibernate.envers.event.AuditEventListener" type="pre-collection-update"/>
  <listener class="org.hibernate.envers.event.AuditEventListener" type="pre-collection-remove"/>
  <listener class="org.hibernate.envers.event.AuditEventListener" type="post-collection-recreate"/>

Давайте рассмотрим пример класса User. Мы хотим следить за обновлениями пользовательских полей. Чтобы включить историю для объекта пользователя, нам нужно использовать аннотацию @Audited. Если он используется на уровне класса, все поля в классе будут рассматриваться как пригодные для аудита, и изменение в любом из полей будет иметь новую запись в таблице аудита. Если мы хотим, чтобы некоторые поля не были включены в историю, мы можем использовать аннотацию @NotAudited. Если поле NotAudited изменено, в таблице аудита не будет записи. Вот класс пользователя:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
package com.javaroots.model;
 
import javax.persistence.Column;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Entity;
 
import org.hibernate.envers.Audited;
import org.hibernate.envers.NotAudited;
 
/**
 *
 *
 * @author Abhishek Somani
 *
 */
@Entity
@Audited
public class User {
 
 @Id
 @GeneratedValue(strategy = GenerationType.AUTO)
 private Long id;
 @Column(length = 20)
 private String firstName;
 @Column(length = 20)
 private String lastName;
 @Column(length = 20)
 @NotAudited
 private String password;
 
 public Long getId() {
  return id;
 }
 
 public void setId(Long id) {
  this.id = id;
 }
 
 public String getFirstName() {
  return firstName;
 }
 
 public void setFirstName(String firstName) {
  this.firstName = firstName;
 }
 
 public String getLastName() {
  return lastName;
 }
 
 public void setLastName(String lastName) {
  this.lastName = lastName;
 }
 
 public String getPassword() {
  return password;
 }
 
 public void setPassword(String password) {
  this.password = password;
 }
 
}

Вот тестовый класс, где мы создаем запись в пользовательской таблице и затем обновляем ее поле.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
package com.javaroots.main;
 
import org.hibernate.Session;
 
import com.javaroots.model.User;
import com.javaroots.util.HibernateUtil;
 
public class HibernateTest {
 
public static void main(String[] args) {
 
        Session session = HibernateUtil.getSessionFactory().openSession();
 
        //one entry will be created in user table
        //and audit entry created in user_aud table
        session.beginTransaction();
 
        User u = new User();
        u.setFirstName("Amitabh");
        u.setLastName("bachhan");
        u.setPassword("God");
 
        session.save(u);
 
        session.getTransaction().commit();
 
        session.beginTransaction();
        User amitabh = (User)session.get(User.class,1l);
 
        amitabh.setFirstName("Abhishek");
 
        session.getTransaction().commit();
 
        //no entry in audit table if we change password field
        //because this field is marked as @notAudited
        session.beginTransaction();
        amitabh = (User)session.get(User.class,1l);
 
        amitabh.setPassword("NotGod");
 
        session.getTransaction().commit();
 
   //get specific revision
        AuditReader reader = AuditReaderFactory.get(HibernateUtil.getSessionFactory().openSession());
        User abhishek = (User) reader.find(User.class, new Long(1), 2);
        System.out.println(abhishek.getFirstName() + " " + abhishek.getLastName());
 
        //get all revision
 
        List versions = reader.getRevisions(User.class, new Long(1));
        for (Number number : versions) {
          System.out.print(number + " ");
        }
 
    }
 
}

Сначала в пользовательской таблице создается пользовательская строка. В user_aud создается одна строка с идентификатором ревизии и полями пользовательской таблицы. В таблице revinfo создается одна строка с идентификатором ревизии и отметкой времени. Эти две записи выполняются envers автоматически. Вот SQL-запросы и табличные структуры:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
Hibernate:
    insert
    into
        User
        (firstName, lastName, password)
    values
        (?, ?, ?)
Hibernate:
    insert
    into
        REVINFO
        (REVTSTMP)
    values
        (?)
Hibernate:
    insert
    into
        User_AUD
        (REVTYPE, firstName, lastName, id, REV)
    values
        (?, ?, ?, ?, ?)
Hibernate:
    update
        User
    set
        firstName=?,
        lastName=?,
        password=?
    where
        id=?
Hibernate:
    insert
    into
        REVINFO
        (REVTSTMP)
    values
        (?)
Hibernate:
    insert
    into
        User_AUD
        (REVTYPE, firstName, lastName, id, REV)
    values
        (?, ?, ?, ?, ?)
Hibernate:
    update
        User
    set
        firstName=?,
        lastName=?,
        password=?
    where
        id=?
Hibernate:
    select
        user_aud0_.id as id4_,
        user_aud0_.REV as REV4_,
        user_aud0_.REVTYPE as REVTYPE4_,
        user_aud0_.firstName as firstName4_,
        user_aud0_.lastName as lastName4_
    from
        User_AUD user_aud0_
    where
        user_aud0_.REV=(
            select
                max(user_aud1_.REV)
            from
                User_AUD user_aud1_
            where
                user_aud1_.REV<=?
                and user_aud0_.id=user_aud1_.id
        )
        and user_aud0_.REVTYPE<>?
        and user_aud0_.id=?
Abhishek bachhan
Hibernate:
    select
        user_aud0_.REV as col_0_0_
    from
        User_AUD user_aud0_ cross
    join
        REVINFO defaultrev1_
    where
        user_aud0_.id=?
        and user_aud0_.REV=defaultrev1_.REV
    order by
        user_aud0_.REV asc
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
mysql> select * from user;
+----+-----------+----------+---------------+
| id | firstName | lastName | password      |
+----+-----------+----------+---------------+
1 | Amitabh   | bachchan    | God|
+----+-----------+----------+---------------+
1 row in set (0.03 sec)
 
mysql> select * from user_aud;
+----+-----+---------+-----------+----------+
| id | REV | REVTYPE | firstName | lastName |
+----+-----+---------+-----------+----------+
1 |   1 |       0 | Amitabh   | bachchan    |
+----+-----+---------+-----------+----------+
1 row in set (0.00 sec)
 
mysql> select * from revinfo;
+-----+---------------+
| REV | REVTSTMP      |
+-----+---------------+
|   1 | 1375956506278|
+-----+---------------+
1 row in set (0.00 sec)
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
mysql> select * from user;
+----+-----------+----------+----------------+
| id | firstName | lastName | password       |
+----+-----------+----------+----------------+
1 | Amitabh   | bachchan| NotGod |
+----+-----------+----------+----------------+
1 row in set (0.00 sec)
 
mysql> select * from user_aud;
+----+-----+---------+-----------+----------+
| id | REV | REVTYPE | firstName | lastName |
+----+-----+---------+-----------+----------+
1 |   1 |       0 | Amitabh   | bachchan |
1 |   2 |       1 | Abhishek  | bachchan|
+----+-----+---------+-----------+----------+
2 rows in set (0.00 sec)
 
mysql> select * from revinfo;
+-----+---------------+
| REV | REVTSTMP      |
+-----+---------------+
|   1 | 1375956506278|
|   2 | 1375956506328|
+-----+---------------+
2 rows in set (0.00 sec)

Справка: Как вести историю таблиц в Hibernate от нашего партнера по JCG Абхишека Сомани из блога Java, J2EE, Server .