JHipster によるエンタープライズアプリケーションの構築(9) Spring Data JPAでデータ変更履歴を管理する
この記事では、JHipsterのバージョン8.7.0を使用してSpring Data JPAのAuditing機能とSpring Data Enversの履歴管理機能を利用する方法を紹介します。
エンタープライズアプリケーションにおいて、データの作成日時、更新日時、作成者、更新者などのメタデータを保存することは、データのトレーサビリティと透明性を確保するために重要です。
メタデータにより、誰がいつデータを作成・更新したのかを追跡でき、過去の状態を再現したり、変更の原因を特定したりすることが可能になります。
特に、法的要件や内部監査のニーズに対応するためには、詳細な変更履歴の管理が不可欠です。
Spring Data JPAのAuditing機能は、アノテーションを使用してエンティティのメタデータを自動的に更新します。
これにより、SQLやソースコードを手書きする必要がなくなり、開発効率が向上します。
Spring Data Enversは、Hibernate Enversを利用してエンティティの変更履歴を詳細に記録し、特定の時点でのデータ状態を確認することができるようにします。
これにより、デバッグや監査、データ復旧などを効率的に行うことができます。
これらの機能を活用することで、データの整合性と透明性を確保し、法的要件や内部監査のニーズに対応することができます。
JHipsterを使用することのメリット
JHipsterで自動生成されるプロジェクトはSpring Security、Spring Data JPAを使用しています。デフォルトでSpring Data JPAのAuditing機能が有効になるように設定されており、
- ユーザー情報を取得するためのAuditorAwareコンポーネントの実装と有効化設定
- Auditing機能を組み込むためのエンティティの抽象クラス
が自動生成されます。
JHipster Domain Language (JDL)の定義でAuditing機能を有効化することはできませんが、利用頻度が高い場合にBlueprintを開発することは比較的容易です。
Spring Data Enversは組み込まれていませんが、ベースとなるプロジェクト構造はできており、定型的な作業で組み込むことができます。
こちらも利用頻度が高ければBlueprintを開発することは難しくないでしょう。
ベースプロジェクトの作成
ベースとなるJHipsterのプロジェクトを作成します。これは前回の記事で使用したものと同じです。
myApp配下に以下の内容でmyApp.jdl
を作成します。
作成したJDLファイルを指定してアプリケーションを生成します。
データモデルのイメージ
今回作成するデータモデルのイメージです。
カラーの凡例は以下になります。
- 紫:エンティティで管理したい項目
- 青:Spring Data JPAのAuditing機能で設定する監査情報
- 緑:Spring Data Enversで設定する履歴情報
- オレンジ:PrimaryKey
各テーブルの概要です。
名称 | 概要 |
---|---|
エンティティテーブル employee | 業務で管理したい情報です。 ユーザー操作によって作成・更新・削除されます。 |
エンティティ履歴テーブル employee_aud | テーブルの更新履歴です。 employeeテーブルの変更が行われるごとに 1レコードが作成されます。 |
履歴管理テーブル revinfo | 履歴情報の管理テーブルです。 エンティティ履歴テーブル1レコードに対して 1レコードが作成されます。 |
カラムの詳細についてはこの後の手順で説明していきます。
Spring Data JPAのAuditing機能を組み込む
自動生成したソースコードに対して監査用カラムを追加して、Auditing機能を有効化します。監査用のカラムは以下となります。
名称 | カラム名 | 型 |
---|---|---|
作成者 | created_by | 文字列 |
作成日時 | created_date | タイムスタンプ |
更新者 | last_modified_by | 文字列 |
更新日時 | last_modified_date | タイムスタンプ |
LiquibaseによるDBへのカラム追加
JHipsterはデータベースの管理にLiquibaseを使用しています。
Liquibaseについては過去の記事で紹介していますので、そちらも参考にしてください。
自動生成の際にLiquibase用の設定ファイルが作成され、アプリケーション起動時にエンティティに対応したテーブルなどが作成されます。
今回はそこに監査用のカラムを作成するための設定を追加します。
src/main/resources/config/liquibase/changelog/
配下に20240708061915_added_entity_Employee_auditing.xml
を以下の内容で作成します。
XMLファイル名はユニークになるように設定してください。今回は同じフォルダに自動生成されたXMLファイル20240708061915_added_entity_Employee.xml
があるため、それに合わせたファイル名としています。
上記にはemployee
テーブルに対して監査用のカラムを追加する設定と、初期データを更新する設定を記載しています。
更新については手順を簡略化するためにCSVのロードではなくchangeSetで一括でupdateするようにしています。
この方法だとレコードごとのカスタマイズをする場合に煩雑になるため、用途によってCSVをロードする方法との選択をしてください。
src/main/resources/config/liquibase/master.xml
に対して、先ほど作成した設定ファイルを読み込むように<include file>
を追加します。
サーバ側のエンティティへのフィールド追加
次にsrc/main/java/com/mycompany/myapp/domain/Employee.java
に対して、追加した監査用のカラムを扱うようにフィールドを追加します。
JHipsterが生成したソースコードの中に監査用のエンティティを作るための抽象クラス(AbstractAuditingEntity)が用意されていますので、それを継承します。
AbstractAuditingEntityには監査用のカラムに対応するフィールドやgetter・setter、JPAの設定などが含まれています。
また、AbstractAuditingEntityはエンティティが自動生成されるパッケージにありますので、importは不要です。
以上の設定で、レコードの作成・更新時に自動で監査用の4カラムに値が設定されるようになります。
補足:監査用カラムのカラム名について
監査用の4カラムの名称はcreated_by、created_date、last_modified_by、last_modified_dateに固定になります。
カラム名に重要な意味はないと思われますので、できるだけ変更しないことをお勧めしますが、要件に合わない場合はエンティティクラスにアノテーションを追加することで対応可能です。
この場合はLiquibaseのChangelogファイルともカラム名を一致させてください。設定例:下記で
任意のカラム名
となっている部分を設定したいカラム名に変更してください
Spring Data Enversの履歴管理機能を組み込む
続けてSpring Data Enversを組み込み、更新履歴の出力を有効化します。
手順は以下の通りです。
- build.gradleにSpring Data Enversの依存関係を追加
- Spring BootのConfigurationを変更してEnversを有効化
- Envers用のシーケンスと履歴管理テーブルを作成
- エンティティ履歴テーブルを作成
- 履歴管理テーブルに更新者を出力するための設定を追加
- 履歴出力を有効にするためにエンティティを修正
build.gradleにSpring Data Enversの依存関係を追加する
Enversを有効にするためのspring-data-envers
はデフォルトでは組み込まれていませんので、build.gradleに依存関係を追加する必要があります。
Spring BootのConfigurationを変更してEnversを有効化する
JHipsterが自動生成したsrc/main/java/com/mycompany/myapp/config/DatabaseConfiguration.java
のアノテーションを変更します。
修正前はJPAを有効化するための
というアノテーションが付いていますが、これを
に変更します。
併せてimportを
から
に変更します。
以下が例となります。
Envers用のシーケンスと履歴管理テーブルを作成する
履歴管理テーブルは、履歴情報のPrimaryKey(rev)とタイムスタンプ(revtstmp)を持つテーブルです。Enversでは履歴管理テーブルのrevにシーケンスから払い出した番号を、revtstmpに履歴を作成した時のエポックミリ秒を設定します。
エンティティ履歴テーブルは更新時(更新後)のエンティティの情報と、履歴管理テーブルのrevを持っています。
履歴管理テーブルはデフォルトで更新者の情報を持ちませんが、今回は更新者の情報を追加することにします。Spring Data JPAのAuditing機能で各テーブルに監査情報を出力していれば更新者の情報は不要に思えるかもしれませんが、以下の理由により追加しています。
- 監査情報が不要なテーブルを履歴管理する時にも更新者がわかるようにする
- 監査情報だけでは削除時の更新者を特定することができない
- デフォルトでは削除時のエンティティ履歴テーブルのカラム値は、PrimaryKeyと履歴情報を除いて全て
null
になる - デフォルト動作をカスタマイズすることは可能だが、エンティティ履歴テーブルに残せるのは削除時(削除前)のエンティティ情報となる(削除を実行した更新者や更新日時は残らない)
- デフォルトでは削除時のエンティティ履歴テーブルのカラム値は、PrimaryKeyと履歴情報を除いて全て
シーケンスと履歴管理テーブルはLiquibaseで作成します。
src/main/resources/config/liquibase/changelog/
配下に10000000000000_envers_schema.xml
を以下の内容で作成します。XMLファイル名はユニークになるように設定します。
ChangeSetのidについては、XMLファイル内でユニークになるように設定します。今回は10000000000000
からカウントアップするように指定しています。
上記XMLのそれぞれの名称はEnversのデフォルト設定を使用しています。
- シーケンス名(
revinfo_seq
):revinfoテーブルのrevカラムに設定する値を払い出すシーケンス - テーブル名(
revinfo
):履歴管理テーブルのテーブル名 - カラム名(
rev
):履歴管理テーブルのPrimaryKeyで、エンティティ履歴テーブルから外部参照される - カラム名(
revtstmp
):履歴情報が作成された時のタイムスタンプ。設定される値はエポックミリ秒
ここに、更新者を入れるカラムを追加しています。
- カラム名(
revmodby
):更新者を設定するカラム。デフォルトのカラム名に合わせてrevision modified by
を短縮して命名
履歴管理テーブルにはダミーデータは不要なため、設定しません。
エンティティ履歴テーブルを作成する
Enversではエンティティ履歴テーブルのテーブル名は、デフォルトで{対象のテーブル名}_aud
です。
今回はemployee
テーブルの履歴テーブルですので、employee_aud
というテーブルを作成します。
エンティティ履歴テーブルには履歴を取得するテーブルと同じカラムに加えて、以下のカラムを追加します。
- rev:履歴管理テーブルのPrimaryKeyを外部参照する
- revtype:変更種別が保存される。
0:insert
、1:update
、2:delete
が入る
また、履歴テーブルの制約は以下のようにします。
- 履歴を取得するテーブルに存在するカラムの制約は引き継がない
- 履歴情報なので、データの整合性よりも値を正しく保存できることが重要
- revカラムは履歴管理テーブルのPrimaryKeyを外部参照する
- 履歴テーブルのidカラムとrevカラムで複合PrimaryKeyを構成する
src/main/resources/config/liquibase/changelog/
配下に20240708061915_added_entity_EmployeeAud.xml
を以下の内容で作成します。
XMLファイル名はユニークになるように設定してください。今回は同じフォルダに自動生成されたXMLファイル20240708061915_added_entity_Employee.xml
があるため、それに合わせたファイル名としています。
エンティティ履歴テーブルにはダミーデータは不要なため、設定しません。
作成したChangelogをmaster.xmlからincludeする
src/main/resources/config/liquibase/master.xml
に対して、先ほど作成した設定ファイルを読み込むように<include file>
を追加します。
履歴管理テーブルに更新者を出力するための設定を追加する
デフォルト設定であれば履歴管理テーブルに出力するための設定は不要ですが、履歴管理テーブルをカスタマイズしているため、対応するエンティティクラスを作成する必要があります。
新規にsrc/main/java/com/mycompany/myapp/config/envers/
ディレクトリを作成し、CustomRevisionEntity.java
を以下の内容で作成します。
デフォルトのクラスを継承し、revmodby
フィールドを追加します。
revmodby
フィールドに値を設定するためのRevisionListenerを作成します。
同じディレクトリにCustomRevisionListener.java
を以下の内容で作成します。
ここでインジェクションしているAuditorAwareはJHipsterがデフォルトで生成してくれています。Spring Data JPAのAuditing機能で作成者や更新者を取得するためのコンポーネントと同じものです。
履歴出力を有効にするためにエンティティを修正する
今回はAuditingを有効にしたsrc/main/java/com/mycompany/myapp/domain/Employee.java
の履歴を出力するようにします。
履歴を出力するエンティティにはクラスアノテーションとして@Audited
を付与します。
クラス継承をしていないエンティティであれば@Audited
を付与するだけで良いのですが、クラス継承をしている場合にはデフォルトでは親クラスの情報は履歴に出力されません。
Employee
クラスはAbstractAuditingEntity
クラスを継承しているため、AbstractAuditingEntity
クラスが保持している監査情報を履歴に出力するためには追加の設定が必要になります。
親クラスの情報を履歴に出力する方法はいくつかがありますが、今回は@AuditOverride
というアノテーションを付与することで対応しました。
以上で設定は完了です。
動作確認
動作確認のため、gradlew
コマンドでアプリケーションを起動します。
adminユーザーでログインし、Administration
からDatabase
を選択し、H2 Consoleを起動します。
employee、employee_aud、revinfoの3つのテーブルが作成されていることを確認できます。
Entities
からEmployee
を選択し、一覧画面を表示します。
一覧画面で「Create a new Employee」ボタンを押下します。
氏名を入力し、Saveボタンを押下します。
一覧画面に遷移し、新しいレコードが作成されていることを確認できます。
H2 Consoleからデータベースに保存された内容を確認します。
employeeテーブルにレコードが追加されています。
created_by、created_date、last_modified_by、last_modified_dateのカラムに自動で値が設定されていることを確認できます。
revinfoテーブルにはレコードが1件追加され、revカラムにシーケンスの値が設定されています。
revtstmpはエポックミリ秒です。revmodbyにレコードを操作したユーザー名が入っていることが確認できます。
employee_audテーブルにもレコードが追加されています。
revの値はrevinfoテーブルのrevカラムへの外部参照です。revtype=0
はレコードの作成を意味しています。
次に、adminユーザーからSign OutしてuserユーザーでSign Inします。
一覧画面に遷移し、先ほど作成したレコードのEditボタンを押下します。
Last Nameを変更し、Saveボタンを押下します。
レコードが更新されます。
userユーザーではH2 Consoleを起動できないため、一度Sign OutしてadminユーザーでSign Inし直してからデータベースを確認します。
employeeテーブルのレコードが更新されています。
last_modified_byとlast_modified_dateも自動で更新されています。
last_modified_byには正しくuser
が設定されていることが確認できます。
履歴管理テーブルにはレコードが追加されます。
エンティティ履歴テーブルにもレコードが追加されます。revtype=1
はレコードの更新を意味しています。
最後に削除の確認をします。
adminユーザーのままで一覧画面に遷移し、Deleteボタンを押下します。
削除確認のダイアログが表示されますので、Deleteボタンを押下します。
H2 Consoleで各テーブルの値を確認します。
employeeテーブルからはレコードが削除されています。
履歴管理テーブルにはレコードが追加されています。
エンティティ履歴テーブルにもレコードが追加されています。
削除時のレコードでは、元のレコードの情報はPrimaryKey(id)以外はnull
になります。revtype=2
はレコードの削除を意味しています。
以上でSpring Data JPAのAuditing機能とSpring Data Enversの履歴管理機能が有効に動作していることが確認できました。
補足:削除時のエンティティ履歴レコード内容について
エンティティ履歴テーブルの削除時のレコードでは、エンティティの内容はPrimaryKey(id)以外
null
になります。 つまり、削除時点のエンティティの内容は履歴から追跡できますが複数のレコードを見る必要があります。ここに削除時点のエンティティの情報を入れるようにすることで、1レコードで特定することが可能になります。
具体的には、src/main/resources/config/application-dev.yml
などに以下の設定を追加することで、エンティティ履歴テーブルに値を設定することができます。
最後に
JHipsterを利用することで、Spring Data JPAのAuditing機能やSpring Data Enversの履歴管理機能を組み込んだアプリケーションを簡単に構築することができます。
ぜひ、JHipsterを活用して効率的な開発を体験してみてください。
JHipster 関連記事
- JHipster によるエンタープライズアプリケーションの構築(1) 紹介編
- JHipster によるエンタープライズアプリケーションの構築(2) Spring Native で高速起動化し AWS Lambda で動かす
- JHipster によるエンタープライズアプリケーションの構築(3) Blueprint でコード生成処理をカスタマイズする
- JHipster によるエンタープライズアプリケーションの構築(4) Liquibase によるデータベース管理
- JHipster によるエンタープライズアプリケーションの構築(5) Spring Modulith でモジュラーモノリス化する
- JHipster によるエンタープライズアプリケーションの構築(6) Blueprint でコード生成処理をカスタマイズする 2
- JHipster によるエンタープライズアプリケーションの構築(7) Amazon Cognitoを利用したユーザー認証
- JHipster によるエンタープライズアプリケーションの構築(8) 楽観ロックと論理削除