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を使用すると、 InterceptorsEventListeners、、およびデータベーストリガーを使用して監査を実行できます。 しかし、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 { ... }

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

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

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

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

  • hibernate.hbm2ddl.autocreate create-drop、または update に設定して、Enversがそれらを自動的に作成できるようにします
  • o rg.hibernate.tool.EnversSchemaGenerator を使用して、完全なデータベーススキーマをプログラムでエクスポートします
  • 適切なDDLステートメントを生成するためのAntタスクを設定する
  • Mavenプラグインを使用してマッピング(Juploなど)からデータベーススキーマを生成し、Enversスキーマをエクスポートします(Hibernate 4以降で動作します)

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

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

これらに加えて、REVINFOという名前の追加のテーブルがデフォルトで生成されます。 REVREVTSTMP、の2つの重要なフィールドが含まれ、すべてのリビジョンのタイムスタンプが記録されます。 推測できるように、bar_AUD.REVfoo_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 、一般的なCRUD操作のインターフェイス。 リポジトリを作成して別のコンポーネントに挿入するとすぐに、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による変更の作成者の監査

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