Hibernate Enversで履歴管理
エンティティが登録、更新、削除された履歴を管理します。
Hibernate Enversは、履歴管理テーブル全体を管理するエンティティ(デフォルトでRevinfo)をいて、自動採番されるid(プロパティ名はrev)と履歴を永続化した日時(プロパティ名はrevtstmp)の2プロパティを持っています。
エンティティ(後述のClient)の登録、更新、削除された履歴を管理するエンティティ(後述のClientHistory。デフォルトのクラス名はClientAud)が履歴管理するプロパティに加えて、Revinfoのid(デフォルトのプロパティ名はrev)とどのようなイベントが行われたかのフィールド(デフォルトのプロパティ名はrevtype)を持っています。ちなみにrevは、Revisionの略です。
環境
・Spring Boot 1.5.13.RELEASE
・Hibernate Envers 5.0.12
・Mysql
やること
1. hibernate-enverを依存関係に追加する
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-envers</artifactId>
</dependency>
2. @Auditedをエンティティクラス(またはエンティティの特定のフィールド)に付与する
今回は、Clientエンティティを使って説明します。
@Entity
@Audited
public class Client implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Getter
private Integer id;
@Getter
@Setter
private String name;
@Embedded
@Getter
@Setter
private Address address;
}
@Embeddable
@Getter
@Setter
@Audited
public class Address implements Serializable {
private int postalCode;
private String address1;
private String address2;
}
ここでClientエンティティの履歴を管理したいのでクラスに対して@Auditedを付与します。
Clientエンティティの履歴を管理するエンティティは、デフォルトでClientAudとなります。Audは、Auditの略ですがわかりずらいのでClientHistoryにします。また、ClientHistoryのプロパティ名も、それぞれrevをauditId、revtypeをauditTypeに変更します。application.propertiesに次のように書けば変更できます。
spring.jpa.properties.org.hibernate.envers.audit_table_suffix=_history
spring.jpa.properties.org.hibernate.envers.revision_field_name=audit_id
spring.jpa.properties.org.hibernate.envers.revision_type_field_name=audit_type
3. テーブルを準備する
Flywayを使うので、sqlファイルに次のように書きます。
これで、Clientと対になるclientテーブル、ClientHistoryと対になるclient_historyテーブル、履歴管理テーブル全体の管理をするrevinfoが作られます。
create table client (
id integer not null auto_increment,
name varchar(255),
postal_code integer not null,
address1 varchar(255),
address2 varchar(255),
primary key (id)
)
engine=InnoDB;
create table client_history (
id integer not null,
name varchar(255),
postal_code integer,
address1 varchar(255),
address2 varchar(255),
audit_id integer not null,
audit_type tinyint,
primary key (id, audit_id)
) engine=InnoDB;
create table revinfo (
rev integer not null auto_increment,
revtstmp bigint,
primary key (rev)
)
engine=InnoDB;
alter table client_history add constraint FK_client_history_revinfo foreign key (audit_id) references revinfo (rev);
できたテーブルがこれです。
登録、更新、削除やってみる
それぞれ、Hibernateが生成したSQLです。
● 登録
Hibernate: insert into client (address1, address2, postal_code, name) values (?, ?, ?, ?)
Hibernate: insert into revinfo (revtstmp) values (?)
Hibernate: insert into client_history (audit_type, address1, address2, postal_code, name, id, audit_id) values (?, ?, ?, ?, ?, ?, ?)
作られたデータ
"client" : {
"id": 1,
"name": "hey,inc",
"postalCode": 1500011,
"address1": "東京都",
"address2": "渋谷区東3丁目16番3号 エフ・ニッセイ恵比寿ビル4階"
}
"clientHistory": {
"id": 1,
"name": "hey,inc",
"postalCode": 1500011,
"address1": "東京都",
"address2": "渋谷区東3丁目16番3号 エフ・ニッセイ恵比寿ビル4階",
"audit_id": 1,
"audit_type": 0
}
"revinfo": {
"rev": 1,
"revtstmp": 1528214457258
}
● 更新
Hibernate: update client set address1=?, address2=?, postal_code=?, name=? where id=?
Hibernate: insert into revinfo (revtstmp) values (?)
Hibernate: insert into client_history (audit_type, address1, address2, postal_code, name, id, audit_id) values (?, ?, ?, ?, ?, ?, ?)
作られたデータ
"client" : {
"id": 1,
"name": "Coiney,inc",
"postalCode": 1500011,
"address1": "東京都",
"address2": "渋谷区東3丁目16番3号 エフ・ニッセイ恵比寿ビル4階"
}
"clientHistory": {
"id": 1,
"name": "Coiney,inc",
"postalCode": 1500011,
"address1": "東京都",
"address2": "渋谷区東3丁目16番3号 エフ・ニッセイ恵比寿ビル4階",
"audit_id": 2,
"audit_type": 1
}
"revinfo": {
"rev": 2,
"revtstmp": 1528214465266
}
● 削除
Hibernate: delete from client where id=?
Hibernate: insert into revinfo (revtstmp) values (?)
Hibernate: insert into client_history (audit_type, address1, address2, postal_code, name, id, audit_id) values (?, ?, ?, ?, ?, ?, ?)
作られたデータ
"clientHistory": {
"id": 1,
"name": null,
"postalCode": null,
"address1": null,
"address2": null,
"audit_id": 3,
"audit_type": 2
}
"revinfo": {
"rev": 3,
"revtstmp": 1528214465681
}
ここでauditTypeの0、1、2は何ですか?という疑問が残ってるかと思いますが、それぞれ次の通りです。
0 → 登録
1 → 更新
2 → 削除
イベントにあったタイプが与えられます。
Hibernate Enversで履歴管理が楽になりました。現場からは以上でした。