Spring Data JPA, JPA, Hibernateで監査する
TL;DR
Spring Data JPA には、JPA の @PrePersist
@PreUpdate
@PreRemove
のリスナーを持ちいた実装が標準で用意されているため、簡単に監査記録を残すことができます。 なお、Hibernate には Envers という、履歴管理機能の付いている非常に強力かつ便利な監査機能がありますが、今回はその説明をしていません。
Spring Data JPAとHibernateで監査機能を有効にする方法
実装は、次の3ステップで終わります。
- @EnableJpaAuditing を有効にする。
- 監査情報を保持する抽象クラスを用意して、監査の設定を入れる。
- エンティティの具象クラスを作成する。
@EnableJpaAuditingを有効にする
まずは、Spring Data JPA の監査機能を、アノテーションで定義するため、@Configuration
クラスに @EnableJpaAuditing
を追加します。 アノテーションではなく、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名) |
@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
クラスを継承した、具象クラス(エンティティクラス)を作成します。 実際のプロダクションコードでは、エンティティクラスをジェネレーション・ギャップ・パターンで定義することが多いですが、その場合は Customer
と JpaAuditRecord
の間に、テーブルカラムのプロパティを保持する中間クラスを入れることになります。
@Entity
@ToString(callSuper = true)
@EqualsAndHashCode(callSuper = true)
@NoArgsConstructor
@Where(clause = "LOGICAL_DELETE=0")
public class Customer extends JpaAuditRecord {
}
また、監査情報を残さないエンティティクラスを定義する場合は、JpaAuditRecord
を継承せずにエンティティクラスを定義します。
@Entity
@NoArgsConstructor
public class SystemMaster implements JpaRecord {
}
この例では JpaRecord
インタフェースを、JPA のエンティティクラスであることを表現するマーカーインタフェースとしているため、これを implements
していますが、監査とは関係ありません。
Discussion