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のプロジェクトを作成します。これは前回の記事で使用したものと同じです。

Terminal window
mkdir myApp && cd myApp

myApp配下に以下の内容でmyApp.jdlを作成します。

application {
config {
applicationType monolith
authenticationType jwt
baseName myApp
buildTool gradle
cacheProvider no
clientFramework react
clientTheme none
databaseType sql
devDatabaseType h2Disk
enableHibernateCache false
enableSwaggerCodegen false
enableTranslation true
feignClient false
languages [en]
messageBroker false
nativeLanguage en
packageName com.mycompany.myapp
prodDatabaseType postgresql
reactive false
searchEngine false
serviceDiscoveryType false
testFrameworks []
websocket false
withAdminUi true
}
entities *
}
entity Employee {
FirstName String,
LastName String,
}
service * with serviceClass
paginate * with pagination
filter *

作成したJDLファイルを指定してアプリケーションを生成します。

Terminal window
jhipster jdl myApp.jdl

データモデルのイメージ

今回作成するデータモデルのイメージです。

alt text

カラーの凡例は以下になります。

  • 紫:エンティティで管理したい項目
  • 青: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があるため、それに合わせたファイル名としています。

<?xml version="1.0" encoding="utf-8"?>
<databaseChangeLog
xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:ext="http://www.liquibase.org/xml/ns/dbchangelog-ext"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-latest.xsd
http://www.liquibase.org/xml/ns/dbchangelog-ext http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-ext.xsd">
<changeSet author="jhipster" id="20240708061915-1-add-auditing-column">
<addColumn tableName="employee">
<column name="created_by" type="varchar(50)" />
<column name="created_date" type="${datetimeType}" />
<column name="last_modified_by" type="varchar(50)" />
<column name="last_modified_date" type="${datetimeType}" />
</addColumn>
<dropDefaultValue tableName="employee" columnName="created_date" columnDataType="${datetimeType}"/>
<dropDefaultValue tableName="employee" columnName="last_modified_date" columnDataType="${datetimeType}"/>
</changeSet>
<changeSet id="20240708061915-1-auditing-faker" author="jhipster" context="faker">
<update tableName="employee">
<column name="created_by" value="system" />
<column name="created_date" valueDate="CURRENT_TIMESTAMP"/>
<column name="last_modified_by" value="system" />
<column name="last_modified_date" valueDate="CURRENT_TIMESTAMP"/>
<where>id BETWEEN 1 AND 10</where>
</update>
</changeSet>
</databaseChangeLog>

上記にはemployeeテーブルに対して監査用のカラムを追加する設定と、初期データを更新する設定を記載しています。
更新については手順を簡略化するためにCSVのロードではなくchangeSetで一括でupdateするようにしています。
この方法だとレコードごとのカスタマイズをする場合に煩雑になるため、用途によってCSVをロードする方法との選択をしてください。

src/main/resources/config/liquibase/master.xmlに対して、先ほど作成した設定ファイルを読み込むように<include file>を追加します。

<?xml version="1.0" encoding="utf-8"?>
<databaseChangeLog
xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-latest.xsd">
<property name="now" value="now()" dbms="h2"/>
<property name="floatType" value="float4" dbms="h2"/>
<property name="uuidType" value="uuid" dbms="h2"/>
<property name="datetimeType" value="datetime(6)" dbms="h2"/>
<property name="clobType" value="longvarchar" dbms="h2"/>
<property name="blobType" value="blob" dbms="h2"/>
<property name="now" value="current_timestamp" dbms="postgresql"/>
<property name="floatType" value="float4" dbms="postgresql"/>
<property name="clobType" value="clob" dbms="postgresql"/>
<property name="blobType" value="blob" dbms="postgresql"/>
<property name="uuidType" value="uuid" dbms="postgresql"/>
<property name="datetimeType" value="datetime" dbms="postgresql"/>
<include file="config/liquibase/changelog/00000000000000_initial_schema.xml" relativeToChangelogFile="false"/>
<include file="config/liquibase/changelog/20240708061915_added_entity_Employee.xml" relativeToChangelogFile="false"/>
<!-- jhipster-needle-liquibase-add-changelog - JHipster will add liquibase changelogs here -->
<!-- jhipster-needle-liquibase-add-constraints-changelog - JHipster will add liquibase constraints changelogs here -->
<include file="config/liquibase/changelog/20240708061915_added_entity_Employee_auditing.xml" relativeToChangelogFile="false"/>
<!-- jhipster-needle-liquibase-add-incremental-changelog - JHipster will add incremental liquibase changelogs here -->
</databaseChangeLog>

サーバ側のエンティティへのフィールド追加

次にsrc/main/java/com/mycompany/myapp/domain/Employee.javaに対して、追加した監査用のカラムを扱うようにフィールドを追加します。
JHipsterが生成したソースコードの中に監査用のエンティティを作るための抽象クラス(AbstractAuditingEntity)が用意されていますので、それを継承します。
AbstractAuditingEntityには監査用のカラムに対応するフィールドやgetter・setter、JPAの設定などが含まれています。
また、AbstractAuditingEntityはエンティティが自動生成されるパッケージにありますので、importは不要です。

・・・
public class Employee extends AbstractAuditingEntity<Long> implements Serializable {
・・・

以上の設定で、レコードの作成・更新時に自動で監査用の4カラムに値が設定されるようになります。

補足:監査用カラムのカラム名について

監査用の4カラムの名称はcreated_by、created_date、last_modified_by、last_modified_dateに固定になります。
カラム名に重要な意味はないと思われますので、できるだけ変更しないことをお勧めしますが、要件に合わない場合はエンティティクラスにアノテーションを追加することで対応可能です。
この場合はLiquibaseのChangelogファイルともカラム名を一致させてください。

設定例:下記で任意のカラム名となっている部分を設定したいカラム名に変更してください

・・・
@AttributeOverrides({
@AttributeOverride(name = "createdBy", column = @Column(name = "任意のカラム名", nullable = false, length = 50, updatable = false)),
@AttributeOverride(name = "createdDate", column = @Column(name = "任意のカラム名", updatable = false)),
@AttributeOverride(name = "lastModifiedBy", column = @Column(name = "任意のカラム名", length = 50)),
@AttributeOverride(name = "lastModifiedDate", column = @Column(name = "任意のカラム名"))
})
public class Employee extends AbstractAuditingEntity<Long> implements Serializable {
・・・

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に依存関係を追加する必要があります。

・・・
dependencies {
・・・
implementation "org.springframework.data:spring-data-envers"
・・・
}
・・・

Spring BootのConfigurationを変更してEnversを有効化する

JHipsterが自動生成したsrc/main/java/com/mycompany/myapp/config/DatabaseConfiguration.javaのアノテーションを変更します。
修正前はJPAを有効化するための

@EnableJpaRepositories({ "com.mycompany.myapp.repository" })

というアノテーションが付いていますが、これを

@EnableEnversRepositories({ "com.mycompany.myapp.repository" })

に変更します。
併せてimportを

import org.springframework.data.jpa.repository.config.EnableJpaRepositories;

から

import org.springframework.data.envers.repository.config.EnableEnversRepositories;

に変更します。
以下が例となります。

・・・
import org.springframework.data.envers.repository.config.EnableEnversRepositories;
・・・
@Configuration
@EnableEnversRepositories({ "com.mycompany.myapp.repository" })
@EnableJpaAuditing(auditorAwareRef = "springSecurityAuditorAware")
@EnableTransactionManagement
public class DatabaseConfiguration {
・・・

Envers用のシーケンスと履歴管理テーブルを作成する

履歴管理テーブルは、履歴情報のPrimaryKey(rev)とタイムスタンプ(revtstmp)を持つテーブルです。Enversでは履歴管理テーブルのrevにシーケンスから払い出した番号を、revtstmpに履歴を作成した時のエポックミリ秒を設定します。
エンティティ履歴テーブルは更新時(更新後)のエンティティの情報と、履歴管理テーブルのrevを持っています。
履歴管理テーブルはデフォルトで更新者の情報を持ちませんが、今回は更新者の情報を追加することにします。Spring Data JPAのAuditing機能で各テーブルに監査情報を出力していれば更新者の情報は不要に思えるかもしれませんが、以下の理由により追加しています。

  • 監査情報が不要なテーブルを履歴管理する時にも更新者がわかるようにする
  • 監査情報だけでは削除時の更新者を特定することができない
    • デフォルトでは削除時のエンティティ履歴テーブルのカラム値は、PrimaryKeyと履歴情報を除いて全てnullになる
    • デフォルト動作をカスタマイズすることは可能だが、エンティティ履歴テーブルに残せるのは削除時(削除前)のエンティティ情報となる(削除を実行した更新者や更新日時は残らない)

シーケンスと履歴管理テーブルはLiquibaseで作成します。
src/main/resources/config/liquibase/changelog/配下に10000000000000_envers_schema.xmlを以下の内容で作成します。XMLファイル名はユニークになるように設定します。 ChangeSetのidについては、XMLファイル内でユニークになるように設定します。今回は10000000000000からカウントアップするように指定しています。

<?xml version="1.0" encoding="utf-8"?>
<databaseChangeLog
xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:ext="http://www.liquibase.org/xml/ns/dbchangelog-ext"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-latest.xsd
http://www.liquibase.org/xml/ns/dbchangelog-ext http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-ext.xsd">
<changeSet id="10000000000000" author="jhipster">
<createSequence sequenceName="revinfo_seq" startValue="1" incrementBy="50"/>
</changeSet>
<changeSet id="10000000000001" author="jhipster">
<createTable tableName="revinfo">
<column name="rev" type="bigint" autoIncrement="true">
<constraints primaryKey="true" nullable="false"/>
</column>
<column name="revtstmp" type="bigint" />
<column name="revmodby" type="varchar(50)" />
</createTable>
</changeSet>
</databaseChangeLog>

上記XMLのそれぞれの名称はEnversのデフォルト設定を使用しています。

  • シーケンス名(revinfo_seq):revinfoテーブルのrevカラムに設定する値を払い出すシーケンス
  • テーブル名(revinfo):履歴管理テーブルのテーブル名
  • カラム名(rev):履歴管理テーブルのPrimaryKeyで、エンティティ履歴テーブルから外部参照される
  • カラム名(revtstmp):履歴情報が作成された時のタイムスタンプ。設定される値はエポックミリ秒

ここに、更新者を入れるカラムを追加しています。

  • カラム名(revmodby):更新者を設定するカラム。デフォルトのカラム名に合わせてrevision modified byを短縮して命名

履歴管理テーブルにはダミーデータは不要なため、設定しません。

エンティティ履歴テーブルを作成する

Enversではエンティティ履歴テーブルのテーブル名は、デフォルトで{対象のテーブル名}_audです。
今回はemployeeテーブルの履歴テーブルですので、employee_audというテーブルを作成します。
エンティティ履歴テーブルには履歴を取得するテーブルと同じカラムに加えて、以下のカラムを追加します。

  • rev:履歴管理テーブルのPrimaryKeyを外部参照する
  • revtype:変更種別が保存される。0:insert1:update2:deleteが入る

また、履歴テーブルの制約は以下のようにします。

  • 履歴を取得するテーブルに存在するカラムの制約は引き継がない
    • 履歴情報なので、データの整合性よりも値を正しく保存できることが重要
  • revカラムは履歴管理テーブルのPrimaryKeyを外部参照する
  • 履歴テーブルのidカラムとrevカラムで複合PrimaryKeyを構成する

src/main/resources/config/liquibase/changelog/配下に20240708061915_added_entity_EmployeeAud.xmlを以下の内容で作成します。
XMLファイル名はユニークになるように設定してください。今回は同じフォルダに自動生成されたXMLファイル20240708061915_added_entity_Employee.xmlがあるため、それに合わせたファイル名としています。

<?xml version="1.0" encoding="utf-8"?>
<databaseChangeLog
xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:ext="http://www.liquibase.org/xml/ns/dbchangelog-ext"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-latest.xsd
http://www.liquibase.org/xml/ns/dbchangelog-ext http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-ext.xsd">
<changeSet id="20240708061915-aud-1" author="jhipster">
<createTable tableName="employee_aud">
<!-- 元のEntityのカラム -->
<column name="id" type="bigint">
<constraints nullable="false" />
</column>
<column name="first_name" type="varchar(255)" />
<column name="last_name" type="varchar(255)" />
<!-- Auditing用のカラム -->
<column name="created_by" type="varchar(50)" />
<column name="created_date" type="${datetimeType}" />
<column name="last_modified_by" type="varchar(50)" />
<column name="last_modified_date" type="${datetimeType}" />
<!-- Envers用のカラム -->
<column name="rev" type="bigint">
<constraints nullable="false" />
</column>
<column name="revtype" type="tinyint" />
</createTable>
<!-- idとrevでPrimaryKeyを構成する -->
<addPrimaryKey
columnNames="id, rev"
constraintName="pk_employee_aud"
tableName="employee_aud"/>
<!-- revカラムは履歴管理テーブルのrevカラムを外部参照する -->
<addForeignKeyConstraint
baseColumnNames="rev"
baseTableName="employee_aud"
constraintName="fk_employee_aud_envers"
referencedColumnNames="rev"
referencedTableName="revinfo"/>
</changeSet>
</databaseChangeLog>

エンティティ履歴テーブルにはダミーデータは不要なため、設定しません。

作成したChangelogをmaster.xmlからincludeする

src/main/resources/config/liquibase/master.xmlに対して、先ほど作成した設定ファイルを読み込むように<include file>を追加します。

<?xml version="1.0" encoding="utf-8"?>
<databaseChangeLog
xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-latest.xsd">
<property name="now" value="now()" dbms="h2"/>
<property name="floatType" value="float4" dbms="h2"/>
<property name="uuidType" value="uuid" dbms="h2"/>
<property name="datetimeType" value="datetime(6)" dbms="h2"/>
<property name="clobType" value="longvarchar" dbms="h2"/>
<property name="blobType" value="blob" dbms="h2"/>
<property name="now" value="current_timestamp" dbms="postgresql"/>
<property name="floatType" value="float4" dbms="postgresql"/>
<property name="clobType" value="clob" dbms="postgresql"/>
<property name="blobType" value="blob" dbms="postgresql"/>
<property name="uuidType" value="uuid" dbms="postgresql"/>
<property name="datetimeType" value="datetime" dbms="postgresql"/>
<include file="config/liquibase/changelog/00000000000000_initial_schema.xml" relativeToChangelogFile="false"/>
<include file="config/liquibase/changelog/20240708061915_added_entity_Employee.xml" relativeToChangelogFile="false"/>
<!-- jhipster-needle-liquibase-add-changelog - JHipster will add liquibase changelogs here -->
<!-- jhipster-needle-liquibase-add-constraints-changelog - JHipster will add liquibase constraints changelogs here -->
<include file="config/liquibase/changelog/20240708061915_added_entity_Employee_auditing.xml" relativeToChangelogFile="false"/>
<include file="config/liquibase/changelog/10000000000000_envers_schema.xml" relativeToChangelogFile="false"/>
<include file="config/liquibase/changelog/20240708061915_added_entity_EmployeeAud.xml" relativeToChangelogFile="false"/>
<!-- jhipster-needle-liquibase-add-incremental-changelog - JHipster will add incremental liquibase changelogs here -->
</databaseChangeLog>

履歴管理テーブルに更新者を出力するための設定を追加する

デフォルト設定であれば履歴管理テーブルに出力するための設定は不要ですが、履歴管理テーブルをカスタマイズしているため、対応するエンティティクラスを作成する必要があります。
新規にsrc/main/java/com/mycompany/myapp/config/envers/ディレクトリを作成し、CustomRevisionEntity.javaを以下の内容で作成します。

デフォルトのクラスを継承し、revmodbyフィールドを追加します。

package com.mycompany.myapp.config.envers;
import org.hibernate.envers.DefaultRevisionEntity;
import org.hibernate.envers.RevisionEntity;
import jakarta.persistence.AttributeOverride;
import jakarta.persistence.AttributeOverrides;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Table;
@Entity
@RevisionEntity(CustomRevisionListener.class)
@Table(name = "revinfo")
@AttributeOverrides({
@AttributeOverride(name = "timestamp", column = @Column(name = "revtstmp")),
@AttributeOverride(name = "id", column = @Column(name = "rev"))
})
public class CustomRevisionEntity extends DefaultRevisionEntity {
@Column(name = "revmodby")
private String revmodby;
public String getRevmodby() {
return revmodby;
}
public void setRevmodby(String revmodby) {
this.revmodby = revmodby;
}
}

revmodbyフィールドに値を設定するためのRevisionListenerを作成します。
同じディレクトリにCustomRevisionListener.javaを以下の内容で作成します。

package com.mycompany.myapp.config.envers;
import org.hibernate.envers.RevisionListener;
import org.springframework.data.domain.AuditorAware;
import org.springframework.stereotype.Component;
@Component
public class CustomRevisionListener implements RevisionListener {
private final AuditorAware<String> auditorAware;
public CustomRevisionListener(AuditorAware<String> auditorAware) {
this.auditorAware = auditorAware;
}
@Override
public void newRevision(Object revisionEntity) {
((CustomRevisionEntity) revisionEntity).setRevmodby(this.auditorAware.getCurrentAuditor().orElse("unknown"));
}
}

ここでインジェクションしているAuditorAwareはJHipsterがデフォルトで生成してくれています。Spring Data JPAのAuditing機能で作成者や更新者を取得するためのコンポーネントと同じものです。

履歴出力を有効にするためにエンティティを修正する

今回はAuditingを有効にしたsrc/main/java/com/mycompany/myapp/domain/Employee.javaの履歴を出力するようにします。
履歴を出力するエンティティにはクラスアノテーションとして@Auditedを付与します。

クラス継承をしていないエンティティであれば@Auditedを付与するだけで良いのですが、クラス継承をしている場合にはデフォルトでは親クラスの情報は履歴に出力されません。
EmployeeクラスはAbstractAuditingEntityクラスを継承しているため、AbstractAuditingEntityクラスが保持している監査情報を履歴に出力するためには追加の設定が必要になります。
親クラスの情報を履歴に出力する方法はいくつかがありますが、今回は@AuditOverrideというアノテーションを付与することで対応しました。

・・・
import org.hibernate.envers.AuditOverride;
import org.hibernate.envers.Audited;
・・・
@Entity
@Table(name = "employee")
@SuppressWarnings("common-java:DuplicatedBlocks")
@Audited
@AuditOverride( forClass = AbstractAuditingEntity.class )
public class Employee extends AbstractAuditingEntity<Long> implements Serializable {
・・・

以上で設定は完了です。

動作確認

動作確認のため、gradlewコマンドでアプリケーションを起動します。

adminユーザーでログインし、AdministrationからDatabaseを選択し、H2 Consoleを起動します。
employee、employee_aud、revinfoの3つのテーブルが作成されていることを確認できます。

alt text alt text

EntitiesからEmployeeを選択し、一覧画面を表示します。

alt text

一覧画面で「Create a new Employee」ボタンを押下します。

alt text

氏名を入力し、Saveボタンを押下します。

alt text

一覧画面に遷移し、新しいレコードが作成されていることを確認できます。

alt text

H2 Consoleからデータベースに保存された内容を確認します。

employeeテーブルにレコードが追加されています。
created_by、created_date、last_modified_by、last_modified_dateのカラムに自動で値が設定されていることを確認できます。

alt text

revinfoテーブルにはレコードが1件追加され、revカラムにシーケンスの値が設定されています。
revtstmpはエポックミリ秒です。revmodbyにレコードを操作したユーザー名が入っていることが確認できます。

alt text

employee_audテーブルにもレコードが追加されています。
revの値はrevinfoテーブルのrevカラムへの外部参照です。revtype=0はレコードの作成を意味しています。

alt text

次に、adminユーザーからSign OutしてuserユーザーでSign Inします。
一覧画面に遷移し、先ほど作成したレコードのEditボタンを押下します。

alt text

Last Nameを変更し、Saveボタンを押下します。

alt text

レコードが更新されます。

alt text

userユーザーではH2 Consoleを起動できないため、一度Sign OutしてadminユーザーでSign Inし直してからデータベースを確認します。

employeeテーブルのレコードが更新されています。
last_modified_byとlast_modified_dateも自動で更新されています。
last_modified_byには正しくuserが設定されていることが確認できます。

alt text

履歴管理テーブルにはレコードが追加されます。

alt text

エンティティ履歴テーブルにもレコードが追加されます。revtype=1はレコードの更新を意味しています。

alt text

最後に削除の確認をします。
adminユーザーのままで一覧画面に遷移し、Deleteボタンを押下します。

alt text

削除確認のダイアログが表示されますので、Deleteボタンを押下します。

alt text

H2 Consoleで各テーブルの値を確認します。
employeeテーブルからはレコードが削除されています。

alt text

履歴管理テーブルにはレコードが追加されています。

alt text

エンティティ履歴テーブルにもレコードが追加されています。
削除時のレコードでは、元のレコードの情報はPrimaryKey(id)以外はnullになります。revtype=2はレコードの削除を意味しています。

alt text

以上でSpring Data JPAのAuditing機能とSpring Data Enversの履歴管理機能が有効に動作していることが確認できました。

補足:削除時のエンティティ履歴レコード内容について

エンティティ履歴テーブルの削除時のレコードでは、エンティティの内容はPrimaryKey(id)以外nullになります。 つまり、削除時点のエンティティの内容は履歴から追跡できますが複数のレコードを見る必要があります。

ここに削除時点のエンティティの情報を入れるようにすることで、1レコードで特定することが可能になります。
具体的には、src/main/resources/config/application-dev.ymlなどに以下の設定を追加することで、エンティティ履歴テーブルに値を設定することができます。

spring:
jpa:
properties:
org:
hibernate:
envers:
store_data_at_delete: true

alt text alt text

最後に

JHipsterを利用することで、Spring Data JPAのAuditing機能やSpring Data Enversの履歴管理機能を組み込んだアプリケーションを簡単に構築することができます。
ぜひ、JHipsterを活用して効率的な開発を体験してみてください。

JHipster 関連記事

参考文献


TOP
アルファロゴ 株式会社アルファシステムズは、ITサービス事業を展開しています。このブログでは、技術的な取り組みを紹介しています。X(旧Twitter)で更新通知をしています。