JPA、Hibernate、Spring Data JPAによる監査

JPA、Hibernate、およびSpring Data JPAを使用した監査

1. 概要

ORMのコンテキストでは、データベース監査は、永続エンティティに関連するイベントの追跡とログ記録、または単にエンティティのバージョン管理を意味します。 SQLトリガーに触発されたイベントは、エンティティに対する挿入、更新、および削除操作です。 データベース監査の利点は、ソースバージョン管理によって提供されるものと類似しています。

アプリケーションに監査を導入するための3つのアプローチを示します。 まず、標準のJPAを使用して実装します。 次に、独自の監査機能を提供する2つのJPA拡張機能について説明します。1つはHibernateによって提供され、もう1つはSpring Dataによって提供されます。

この例で使用されるサンプル関連エンティティ、BarおよびFoo,は次のとおりです。

image

2. JPAによる監査

JPAには明示的に監査APIが含まれていませんが、エンティティライフサイクルイベントを使用して機能を実現できます。

2.1. @PrePersist,@PreUpdateおよび @PreRemove

JPAEntityクラスでは、特定のエンティティライフサイクルイベント中に呼び出されるコールバックとしてメソッドを指定できます。 対応する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. コールバックメソッドの実装

ただし、このアプローチには大きな制限があります。 JPA2 specification (JSR 317):に記載されているとおり

一般に、ポータブルアプリケーションのライフサイクルメソッドは、EntityManagerまたはQuery操作を呼び出したり、他のエンティティインスタンスにアクセスしたり、同じ永続コンテキスト内の関係を変更したりしないでください。 ライフサイクルコールバックメソッドは、呼び出されるエンティティの非関係状態を変更する場合があります。

監査フレームワークがない場合、データベーススキーマとドメインモデルを手動で保守する必要があります。 単純なユースケースでは、「エンティティの非関係状態」のみを管理できるため、エンティティに2つの新しいプロパティを追加しましょう。 operationプロパティは実行された操作の名前を格納し、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を使用すると、InterceptorsEventListeners、およびデータベーストリガーを使用して監査を実行できます。 しかし、ORMフレームワークは、永続クラスの監査とバージョン管理を実装するモジュールであるEnversを提供します。

3.1. Enversを使い始める

Enversを設定するには、hibernate-enversJARをクラスパスに追加する必要があります。


    org.hibernate
    hibernate-envers
    ${hibernate.version}

次に、@Entity(エンティティ全体を監査する場合)または特定の@Columns(特定のプロパティのみを監査する必要がある場合)のいずれかに@Auditedアノテーションを追加します。

@Entity
@Audited
public class Bar { ... }

BarFooと1対多の関係にあることに注意してください。 この場合、Foo@Auditedを追加してFooも監査するか、Barのリレーションシップのプロパティに@NotAuditedを設定する必要があります。

@OneToMany(mappedBy = "bar")
@NotAudited
private Set fooSet;

3.2. 監査ログテーブルの作成

監査テーブルを作成する方法はいくつかあります。

  • hibernate.hbm2ddl.autocreatecreate-drop、またはupdateに設定して、Enversがそれらを自動的に作成できるようにします

  • org.hibernate.tool.EnversSchemaGeneratorを使用して、完全なデータベーススキーマをプログラムでエクスポートします

  • Antタスクを使用して適切なDDLステートメントを生成する

  • Mavenプラグインを使用してマッピング(Juploなど)からデータベーススキーマを生成し、Enversスキーマをエクスポートします(Hibernate 4以降で動作します)

最も簡単な最初のルートを使用しますが、hibernate.hbm2ddl.autoの使用は本番環境では安全ではないことに注意してください。

この場合、bar_AUDおよびfoo_AUDFoo@Auditedとして設定した場合)テーブルは自動的に生成されます。 監査テーブルは、エンティティのテーブルからすべての監査済みフィールドを、REVTYPE(値は、追加の場合は「0」、更新の場合は「1」、エンティティの削除の場合は「2」)とREVの2つのフィールドでコピーします。 。

これらに加えて、REVINFOという名前の追加のテーブルがデフォルトで生成されます。このテーブルには、REVREVTSTMPの2つの重要なフィールドが含まれ、すべてのリビジョンのタイムスタンプが記録されます。 そして、ご想像のとおり、bar_AUD.REVfoo_AUD.REVは実際にはREVINFO.REV.への外部キーです。

3.3. Enversの構成

Enversプロパティは、他のHibernateプロパティと同様に構成できます。

たとえば、監査テーブルのサフィックス(デフォルトは「AUD_“) to “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);

利用可能なプロパティの完全なリストはin the Envers documentationにあります。

3.4. エンティティ履歴へのアクセス

Hibernate基準APIを介したデータのクエリと同様の方法で、履歴データをクエリできます。 エンティティの監査履歴には、AuditReaderインターフェイスを使用してアクセスできます。これは、AuditReaderFactoryを介してEntityManagerまたはSessionを開いた状態で取得できます。

AuditReader reader = AuditReaderFactory.get(session);

Enversは、監査固有のクエリを作成するために、AuditQueryCreatorAuditReader.createQuery()によって返される)を提供します。 次の行は、リビジョン#2で変更されたすべてのBarインスタンスを返します(bar_AUDIT_LOG.REV = 2):

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を拡張するフレームワークです。 このレイヤーでは、Spring JPAリポジトリインターフェースを拡張することにより、JPAリポジトリの作成をサポートできます。

私たちの目的のために、一般的なCRUD操作のインターフェースであるCrudRepository<T, ID extends Serializable>を拡張できます。 リポジトリを作成して別のコンポーネントに挿入するとすぐに、Spring Dataが自動的に実装を提供し、監査機能を追加する準備が整います。

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による変更の作成者の監査

アプリでSpring Securityを使用している場合、変更が行われたときだけでなく、変更者も追跡できます。

@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で注釈が付けられた列には、エンティティを作成または最後に変更したプリンシパルの名前が入力されます。 情報はSecurityContextAuthenticationインスタンスから取得されます。 注釈付きフィールドに設定されている値をカスタマイズする場合は、AuditorAware<T>インターフェースを実装できます。

public class AuditorAwareImpl implements AuditorAware {

    @Override
    public String getCurrentAuditor() {
        // your custom logic
    }

}

AuditorAwareImplを使用して現在のプリンシパルを検索するようにアプリを構成するには、AuditorAwareImplのインスタンスで初期化されたAuditorAwareタイプのBeanを宣言し、Beanの名前を%(t3 )s

@EnableJpaAuditing(auditorAwareRef="auditorProvider")
public class PersistenceConfig {

    //...

    @Bean
    AuditorAware auditorProvider() {
        return new AuditorAwareImpl();
    }

    //...

}

5. 結論

監査機能を実装するための3つのアプローチを検討しました。

  • 純粋なJPAアプローチは最も基本的であり、ライフサイクルコールバックを使用して構成されます。 ただし、エンティティの非リレーションシップ状態のみを変更できます。 これにより、メソッドで行った設定はエンティティとともに削除されるため、@PreRemoveコールバックは私たちの目的には使用できなくなります。

  • Enversは、Hibernateが提供する成熟した監査モジュールです。 高度な設定が可能で、純粋なJPA実装の欠陥がありません。 したがって、エンティティのテーブル以外のテーブルにログインするときに、削除操作を監査できます。

  • Spring Data JPAアプローチは、JPAコールバックの操作を抽象化し、プロパティを監査するための便利な注釈を提供します。 また、SpringSecurityと統合する準備ができています。 欠点は、JPAアプローチの同じ欠陥を継承するため、削除操作を監査できないことです。

この記事の例は、a GitHub repositoryで入手できます。