👻

Spring Data JPA, JPA, Hibernateで監査する

2022/05/22に公開

TL;DR

Spring Data JPA には、JPA の @PrePersist @PreUpdate @PreRemove のリスナーを持ちいた実装が標準で用意されているため、簡単に監査記録を残すことができます。 なお、Hibernate には Envers という、履歴管理機能の付いている非常に強力かつ便利な監査機能がありますが、今回はその説明をしていません。

Spring Data JPAとHibernateで監査機能を有効にする方法

実装は、次の3ステップで終わります。

  1. @EnableJpaAuditing を有効にする。
  2. 監査情報を保持する抽象クラスを用意して、監査の設定を入れる。
  3. エンティティの具象クラスを作成する。

@EnableJpaAuditingを有効にする

まずは、Spring Data JPA の監査機能を、アノテーションで定義するため、@Configuration クラスに @EnableJpaAuditing を追加します。 アノテーションではなく、Java でコンフィグを定義する場合は、このアノテーションは不要ですが、アノテーションでの定義がシンプルでお勧めです。

AppConfig.java
@Configuration
@EnableJpaRepositories
@EnableJpaAuditing
public class AppConfig {

}

また、@EnableJpaAuditing の属性によって、監査情報の残し方を変更できますが、基本的に何も設定する必要はありません。

属性 意味
setDates true/false: 作成日時や最終更新日時を残すか。
modifyOnCreate true/false: 作成時に最終更新日時を残すか。
auditorAwareRef 特別な操作者を使う場合は、AuditorAware インタフェースを実装したクラスのBean名
dateTimeProviderRef 特別な日時を残す場合は、DateTimeProvider インタフェースを実装したクラスのBean名

監査情報を保持する抽象クラスを用意して、監査の設定を入れる

監査情報をまとめて保持・管理する、抽象クラスを作成します。 この抽象クラスに、監査に必要なプロパティやアノテーションを集めることで、エンティティの具象クラスから監査機能を分離します。

このクラスに必要なのは、Spring Data JPA が提供する、JPA のリスナーを有効にするための @EntityListeners(AuditingEntityListener.class) のアノテーションと、監査情報を残すためのフィールドアノテーションです。

アノテーション 説明
@CreateDate 作成日時
@CreatedBy 作成者(Spring Securityのprincipal名)
@LastModifiedDate 最終更新日時
@LastModifiedBy 最終更新日時(Spring Securityのprincipal名)
JpaAuditRecord.java
@Data
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
public abstract class JpaAuditRecord implements JpaRecord {
    
    @CreatedDate
    private LocalDateTime createDatetime;
    
    @CreatedBy
    private String createUser;

    @LastModifiedDate
    private LocalDateTime updateDatetime;
    
    @LastModifiedBy
    private String updateUser;

    private boolean logicalDelete = false;

    @Version
    private long version;

}

また上の例では @Data のアノテーションを付与し、セッター/ゲッターを定義していますが、監査情報を「ユースケース実装での誤用する問題」や「作成者や作成日時の更新を防止する」ためにも、監査情報のプロパティへのアクセッサーを用意しないことをお勧めします。 この仕組みは、セッターがなくても、監査情報は正しく記録されます。

エンティティの具象クラスを作成する

最後に、JpaAuditRecord クラスを継承した、具象クラス(エンティティクラス)を作成します。 実際のプロダクションコードでは、エンティティクラスをジェネレーション・ギャップ・パターンで定義することが多いですが、その場合は CustomerJpaAuditRecord の間に、テーブルカラムのプロパティを保持する中間クラスを入れることになります。

Customer.java
@Entity
@ToString(callSuper = true)
@EqualsAndHashCode(callSuper = true)
@NoArgsConstructor
@Where(clause = "LOGICAL_DELETE=0")
public class Customer extends JpaAuditRecord {

}

また、監査情報を残さないエンティティクラスを定義する場合は、JpaAuditRecord を継承せずにエンティティクラスを定義します。

SystemMaster.java
@Entity
@NoArgsConstructor
public class SystemMaster implements JpaRecord {

}

この例では JpaRecord インタフェースを、JPA のエンティティクラスであることを表現するマーカーインタフェースとしているため、これを implements していますが、監査とは関係ありません。

Discussion