JPA、Hibernate、およびSpringDataJPAを使用した監査
1. 概要
ORMのコンテキストでは、データベース監査とは、永続エンティティに関連するイベントの追跡とログ記録、または単にエンティティのバージョン管理を意味します。 SQLトリガーに触発されたイベントは、エンティティに対する挿入、更新、および削除操作です。 データベース監査の利点は、ソースバージョン管理によって提供される利点と類似しています。
このチュートリアルでは、アプリケーションに監査を導入するための3つのアプローチを示します。 まず、標準のJPAを使用して実装します。 次に、独自の監査機能を提供する2つのJPA拡張機能について説明します。1つはHibernateによって提供され、もう1つはSpring Dataによって提供されます。
この例で使用するサンプル関連エンティティBarおよびFoo、は次のとおりです。
2. JPAによる監査
JPAには監査APIが明示的に含まれていませんが、エンティティライフサイクルイベントを使用してこの機能を実現できます。
2.1. @ PrePersist、 @PreUpdateおよび@PreRemove
JPA Entity クラスでは、特定のエンティティライフサイクルイベント中に呼び出すことができるコールバックとしてメソッドを指定できます。 対応するDML操作の前に実行されるコールバックに関心があるため、 @PrePersist 、 @PreUpdate 、および@PreRemoveコールバックアノテーションを次の目的で使用できます。
@Entity
public class Bar {
@PrePersist
public void onPrePersist() { ... }
@PreUpdate
public void onPreUpdate() { ... }
@PreRemove
public void onPreRemove() { ... }
}
内部コールバックメソッドは常にvoid、を返し、引数を取らないようにする必要があります。 それらは任意の名前と任意のアクセスレベルを持つことができますが、staticであってはなりません。
JPAの@Versionアノテーションは、私たちのトピックに厳密に関連しているわけではないことに注意してください。 それは、監査データよりも楽観的ロックと関係があります。
2.2. コールバックメソッドの実装
ただし、このアプローチには大きな制限があります。 JPA 2仕様(JSR 317)に記載されているように:
一般に、ポータブルアプリケーションのライフサイクルメソッドは、EntityManagerまたはQuery操作を呼び出したり、他のエンティティインスタンスにアクセスしたり、同じ永続コンテキスト内の関係を変更したりしないでください。 ライフサイクルコールバックメソッドは、それが呼び出されるエンティティの非関係状態を変更する場合があります。
監査フレームワークがない場合は、データベーススキーマとドメインモデルを手動で維持する必要があります。 単純なユースケースでは、「エンティティの非関係状態」しか管理できないため、エンティティに2つの新しいプロパティを追加しましょう。 操作プロパティは、実行された操作の名前を格納し、 timestamp プロパティは、操作のタイムスタンプ用です。
@Entity
public class Bar {
//...
@Column(name = "operation")
private String operation;
@Column(name = "timestamp")
private long timestamp;
//...
// standard setters and getters for the new properties
//...
@PrePersist
public void onPrePersist() {
audit("INSERT");
}
@PreUpdate
public void onPreUpdate() {
audit("UPDATE");
}
@PreRemove
public void onPreRemove() {
audit("DELETE");
}
private void audit(String operation) {
setOperation(operation);
setTimestamp((new Date()).getTime());
}
}
このような監査を複数のクラスに追加する必要がある場合は、@EntityListenersを使用してコードを一元化できます。
@EntityListeners(AuditListener.class)
@Entity
public class Bar { ... }
public class AuditListener {
@PrePersist
@PreUpdate
@PreRemove
private void beforeAnyOperation(Object object) { ... }
}
3. Hibernate Envers
Hibernateを使用すると、 InterceptorsとEventListeners、、およびデータベーストリガーを使用して監査を実行できます。 しかし、ORMフレームワークは、永続クラスの監査とバージョン管理を実装するモジュールであるEnversを提供します。
3.1. Enversを使い始める
Enversを設定するには、 hibernate-enversJARをクラスパスに追加する必要があります。
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-envers</artifactId>
<version>${hibernate.version}</version>
</dependency>
次に、 @Entity (エンティティ全体を監査する場合)または特定の @Column (監査する必要がある場合)のいずれかに@Auditedアノテーションを追加します。特定のプロパティのみ):
@Entity
@Audited
public class Bar { ... }
BarはFooと1対多の関係にあることに注意してください。 この場合、 Foo、に @Auditedを追加してFoo も監査するか、関係の@NotAuditedを設定する必要があります。 バーのプロパティ:
@OneToMany(mappedBy = "bar")
@NotAudited
private Set<Foo> fooSet;
3.2. 監査ログテーブルの作成
監査テーブルを作成するには、いくつかの方法があります。
- hibernate.hbm2ddl.autoをcreate、 create-drop、または update に設定して、Enversがそれらを自動的に作成できるようにします
- o rg.hibernate.tool.EnversSchemaGenerator を使用して、完全なデータベーススキーマをプログラムでエクスポートします
- 適切なDDLステートメントを生成するためのAntタスクを設定する
- Mavenプラグインを使用してマッピング(Juploなど)からデータベーススキーマを生成し、Enversスキーマをエクスポートします(Hibernate 4以降で動作します)
最も簡単な最初のルートを使用しますが、hibernate.hbm2ddl.autoの使用は本番環境では安全ではないことに注意してください。
この場合、bar_AUDおよびfoo_AUD(Fooを@Auditedとして設定した場合)テーブルは自動的に生成されます。 。 監査テーブルは、 REVTYPE (値は、追加の場合は「0」、更新の場合は「1」、エンティティの削除の場合は「2」)との2つのフィールドを持つエンティティのテーブルからすべての監査済みフィールドをコピーします。 ]REV。
これらに加えて、REVINFOという名前の追加のテーブルがデフォルトで生成されます。 REVとREVTSTMP、の2つの重要なフィールドが含まれ、すべてのリビジョンのタイムスタンプが記録されます。 推測できるように、bar_AUD.REVとfoo_AUD.REVは、実際にはREVINFO.REV。への外部キーです。
3.3. Enversの構成
Enversプロパティは、他のHibernateプロパティと同じように構成できます。
たとえば、監査テーブルのサフィックス(デフォルトは「 _AUD 」)を「_AUDIT_LOG。」に変更しましょう。対応するプロパティorg.hibernateの値を設定する方法は次のとおりです。 .envers.audit_table_suffix :
Properties hibernateProperties = new Properties();
hibernateProperties.setProperty(
"org.hibernate.envers.audit_table_suffix", "_AUDIT_LOG");
sessionFactory.setHibernateProperties(hibernateProperties);
利用可能なプロパティの完全なリストは、Enversのドキュメントにあります。
3.4. エンティティ履歴へのアクセス
Hibernate Criteria APIを介したデータのクエリと同様の方法で、履歴データをクエリできます。 AuditReader インターフェースを使用してエンティティの監査履歴にアクセスできます。これは、AuditReaderFactoryを介してEntityManagerまたはSessionを開いて取得できます。 ]:
AuditReader reader = AuditReaderFactory.get(session);
Enversは、監査固有のクエリを作成するために、 AuditQueryCreator ( AuditReader.createQuery()によって返される)を提供します。 次の行は、リビジョン#2( bar_AUDIT_LOG.REV = 2 )で変更されたすべてのBarインスタンスを返します。
AuditQuery query = reader.createQuery()
.forEntitiesAtRevision(Bar.class, 2)
Barのリビジョンをクエリする方法は次のとおりです。 その結果、すべての状態のすべての監査済みBarインスタンスのリストが取得されます。
AuditQuery query = reader.createQuery()
.forRevisionsOfEntity(Bar.class, true, true);
2番目のパラメーターがfalseの場合、結果はREVINFOテーブルと結合されます。 それ以外の場合は、エンティティインスタンスのみが返されます。 最後のパラメーターは、削除されたBarインスタンスを返すかどうかを指定します。
次に、AuditEntityファクトリクラスを使用して制約を指定できます。
query.addOrder(AuditEntity.revisionNumber().desc());
4. Spring Data JPA
Spring Data JPAは、JPAプロバイダーの最上位に抽象化レイヤーを追加することでJPAを拡張するフレームワークです。 このレイヤーは、SpringJPAリポジトリー・インターフェースを拡張することにより、JPAリポジトリーの作成をサポートします。
私たちの目的のために、私たちは拡張することができます CrudRepository
4.1. JPA監査の有効化
まず、アノテーション構成による監査を有効にします。 そのために、@Configurationクラスに@EnableJpaAuditingを追加します。
@Configuration
@EnableTransactionManagement
@EnableJpaRepositories
@EnableJpaAuditing
public class PersistenceConfig { ... }
4.2. Springのエンティティコールバックリスナーの追加
すでに知っているように、JPAはコールバックリスナークラスを指定するために@EntityListenersアノテーションを提供します。 Spring Dataは、独自のJPAエンティティリスナークラスAuditingEntityListenerを提供します。 それでは、Barエンティティのリスナーを指定しましょう。
@Entity
@EntityListeners(AuditingEntityListener.class)
public class Bar { ... }
これで、 Bar エンティティを永続化および更新するときに、リスナーによる監査情報をキャプチャできます。
4.3. 作成日と最終変更日の追跡
次に、作成日と最終変更日を保存するための2つの新しいプロパティをBarエンティティに追加します。 プロパティには、それに応じて@CreatedDateおよび@LastModifiedDateアノテーションが付けられ、それらの値は自動的に設定されます。
@Entity
@EntityListeners(AuditingEntityListener.class)
public class Bar {
//...
@Column(name = "created_date", nullable = false, updatable = false)
@CreatedDate
private long createdDate;
@Column(name = "modified_date")
@LastModifiedDate
private long modifiedDate;
//...
}
通常、プロパティを基本クラス( @MappedSuperClass で注釈が付けられている)に移動します。これにより、すべての監査対象エンティティが拡張されます。 この例では、簡単にするために、それらをBarに直接追加します。
4.4. SpringSecurityによる変更の作成者の監査
アプリでSpringSecurityを使用している場合、変更がいつ行われたか、誰が変更したかを追跡できます。
@Entity
@EntityListeners(AuditingEntityListener.class)
public class Bar {
//...
@Column(name = "created_by")
@CreatedBy
private String createdBy;
@Column(name = "modified_by")
@LastModifiedBy
private String modifiedBy;
//...
}
@CreatedByおよび@LastModifiedByで注釈が付けられた列には、エンティティを作成または最後に変更したプリンシパルの名前が入力されます。 この情報は、SecurityContextのAuthenticationインスタンスから取得されます。 注釈付きフィールドに設定されている値をカスタマイズする場合は、 AuditorAware
public class AuditorAwareImpl implements AuditorAware<String> {
@Override
public String getCurrentAuditor() {
// your custom logic
}
}
AuditorAwareImpl を使用して現在のプリンシパルを検索するようにアプリを構成するために、 AuditorAwareImpl、のインスタンスで初期化されたAuditorAwareタイプのbeanを宣言します。 ]そして、beanの名前を@EnableJpaAuditing:のauditorAwareRefパラメーターの値として指定します。
@EnableJpaAuditing(auditorAwareRef="auditorProvider")
public class PersistenceConfig {
//...
@Bean
AuditorAware<String> auditorProvider() {
return new AuditorAwareImpl();
}
//...
}
5. 結論
この記事では、監査機能を実装するための3つのアプローチを検討しました。
- 純粋なJPAアプローチは最も基本的であり、ライフサイクルコールバックの使用で構成されます。 ただし、エンティティの非関係状態を変更することのみが許可されています。 これにより、 @PreRemove コールバックが目的に使用できなくなります。これは、メソッドで行った設定がエンティティとともに削除されるためです。
- Enversは、Hibernateによって提供される成熟した監査モジュールです。 高度に構成可能であり、純粋なJPA実装の欠陥がありません。 したがって、エンティティのテーブル以外のテーブルにログインするときに、削除操作を監査できます。
- Spring Data JPAアプローチは、JPAコールバックの操作を抽象化し、プロパティを監査するための便利なアノテーションを提供します。 Springセキュリティとの統合も可能です。 欠点は、JPAアプローチと同じ欠陥を継承するため、削除操作を監査できないことです。
この記事の例は、GitHubリポジトリで入手できます。