6.5. 監査情報のクエリー

6.5.1. クエリーを介した監査情報の取得

Hibernate Enver は、クエリーを介して監査情報を取得する機能を提供します。

注記

監査されたデータでのクエリーは、関連する subselect を伴うため、多くの場合、live データに対するクエリーよりもはるかに遅くなります。

所定リビジョンでのクラスのエンティティーのクエリー

このタイプのクエリーのエントリーポイントは以下の通りです。

AuditQuery query = getAuditReader()
    .createQuery()
    .forEntitiesAtRevision(MyEntity.class, revisionNumber);

制約は AuditEntity ファクトリークラスを使用して指定できます。以下のクエリーは、name プロパティーが John と同等のエンティティーのみを選択します。

query.add(AuditEntity.property("name").eq("John"));

以下のクエリーは、特定のエンティティーに関連するエンティティーのみを選択します。

query.add(AuditEntity.property("address").eq(relatedEntityInstance));
// or
query.add(AuditEntity.relatedId("address").eq(relatedEntityId));

その結果は、順序付け、制限でき、集計と予測 (グループ化を除く) セットを設定することができます。以下の例は、フルクエリーです。

List personsAtAddress = getAuditReader().createQuery()
    .forEntitiesAtRevision(Person.class, 12)
    .addOrder(AuditEntity.property("surname").desc())
    .add(AuditEntity.relatedId("address").eq(addressId))
    .setFirstResult(4)
    .setMaxResults(2)
    .getResultList();

所定クラスエンティティーが変更されるクエリーリビジョン

このタイプのクエリーのエントリーポイントは以下の通りです。

AuditQuery query = getAuditReader().createQuery()
    .forRevisionsOfEntity(MyEntity.class, false, true);

このクエリーには、前述の例と同じように制約を追加できます。このクエリーには、追加の方法が考えられます。

AuditEntity.revisionNumber()
監査済みエンティティーが変更されたリビジョン番号の制約、展開、および順序を指定します。
AuditEntity.revisionProperty(propertyName)
監査済みエンティティーが変更されたリビジョンに対応する、リビジョンエンティティーのプロパティーに対する制約、調整および順序付けを指定します。
AuditEntity.revisionType()
リビジョンのタイプ (ADD、MOD、DEL) へのアクセスを提供します。

クエリー結果は必要に応じて調整できます。以下のクエリーは、リビジョン番号 42 の後に entityId が変更された MyEntity クラスのエンティティーが変更する最小リビジョン番号を選択します。

Number revision = (Number) getAuditReader().createQuery()
    .forRevisionsOfEntity(MyEntity.class, false, true)
    .setProjection(AuditEntity.revisionNumber().min())
    .add(AuditEntity.id().eq(entityId))
    .add(AuditEntity.revisionNumber().gt(42))
    .getSingleResult();

リビジョンのクエリーは、プロパティーを最小限に抑えたり、最大値にしたりすることもできます。以下のクエリーは、特定のエンティティーの actualDate の値が指定の値よりも大きくなるリビジョンを選択しますが、可能な限り小さくなります。

Number revision = (Number) getAuditReader().createQuery()
    .forRevisionsOfEntity(MyEntity.class, false, true)
    // We are only interested in the first revision
    .setProjection(AuditEntity.revisionNumber().min())
    .add(AuditEntity.property("actualDate").minimize()
        .add(AuditEntity.property("actualDate").ge(givenDate))
        .add(AuditEntity.id().eq(givenEntityId)))
    .getSingleResult();

minimize() および maximize() のメソッドは基準を返します。これは、制約を追加でき、 maximized/minimized プロパティーを持つエンティティーによって満たされる必要があります。

クエリーの作成時に、2 つのブール値パラメーターが渡されます。

selectEntitiesOnly

このパラメーターは、明示的な子が設定されていない場合のみ有効です。
true の場合、クエリーの結果は、指定された制約を満たすリビジョンで変更されたエンティティーのリストになります。
false の場合、結果は 3 つの要素の配列のリストになります。最初の要素は変更されたエンティティーインスタンスになります。次は、リビジョンデータを含むエンティティーです。カスタムエンティティーが使用されていない場合、DefaultRevisionEntity のインスタンスになります。3 番目の要素アレイはリビジョンのタイプ (ADD、MOD、DEL) になります。
selectDeletedEntities
このパラメーターは、エンティティーが削除されたリビジョンを結果に含める必要があるかどうかを指定します。true の場合、エンティティーにはリビジョンタイプ DEL が指定され、id を除くすべてのフィールドの値は null になります。

所定プロパティーを変更するエンティティーのクエリーの修正

以下のクエリーは、指定の id を持つ MyEntity のすべてのリビジョンを返します。ここでは、realDate プロパティーが変更になりました。

AuditQuery query = getAuditReader().createQuery()
  .forRevisionsOfEntity(MyEntity.class, false, true)
  .add(AuditEntity.id().eq(id));
  .add(AuditEntity.property("actualDate").hasChanged())

hasChanged 条件は、追加の基準と組み合わせることができます。以下のクエリーは、revisionNumber の生成時に MyEntity の水平スライスを返します。これは prop1 が変更されたリビジョンに限定されますが、prop2 には適用されません。

AuditQuery query = getAuditReader().createQuery()
  .forEntitiesAtRevision(MyEntity.class, revisionNumber)
  .add(AuditEntity.property("prop1").hasChanged())
  .add(AuditEntity.property("prop2").hasNotChanged());

作成されたセットには、revisionNumber よりも少ない数字のリビジョンも含まれます。これは、このクエリーを Return all MyEntities changed in revisionNumber with prop1 modified and prop2 untouched として読み取ることができないことを意味します。

以下のクエリーは、forEntitiesModifiedAtRevision クエリーを使用して、この結果が返される方法を示しています。

AuditQuery query = getAuditReader().createQuery()
  .forEntitiesModifiedAtRevision(MyEntity.class, revisionNumber)
  .add(AuditEntity.property("prop1").hasChanged())
  .add(AuditEntity.property("prop2").hasNotChanged());

指定リビジョンでのクエリーエンティティーの変更

以下の例は、特定のリビジョンで変更されたエンティティーの基本クエリーを示しています。指定されたリビジョンで変更されたエンティティー名と対応する Java クラスを取得することができます。

Set<Pair<String, Class>> modifiedEntityTypes = getAuditReader()
    .getCrossTypeRevisionChangesReader().findEntityTypes(revisionNumber);

org.hibernate.envers.CrossTypeRevisionChangesReader からもアクセス可能なクエリーは多数あります。

List<Object> findEntities(Number)
指定のリビジョンで変更された (追加、更新、削除された) すべての監査済みエンティティーのスナップショットを返します。n+1 SQL クエリーを実行します。n は、指定されたリビジョン内で変更された複数の異なるエンティティークラスです。
List<Object> findEntities(Number, RevisionType)
変更タイプでフィルターされた特定のリビジョンで変更 (追加、更新、または削除) されたすべての監査済みエンティティーのスナップショットを返します。n+1 SQL クエリーを実行します。n は、指定されたリビジョン内で変更された複数の異なるエンティティークラスです。Map<RevisionType, List<Object>>
findEntitiesGroupByRevisionType(Number)
変更操作 (追加、更新、または削除など) でグループ化されたエンティティースナップショットの一覧が含まれるマップを返します。3n+1 SQL クエリーを実行します。n は、指定されたリビジョン内で変更された複数の異なるエンティティークラスです。

6.5.2. 参照されるエンティティーのプロパティーを使用したエンティティーの関連付けのトラバース

参照されるエンティティーのプロパティーを使用して、クエリー内のエンティティーをトラバースできます。これにより、1 対 1 および多対 1 の関連付けをクエリーできます。

以下の例は、クエリーでエンティティーをトラバースする方法の一部を示しています。

  • リビジョン番号 1 では、所有者が 20 歳であるか、住所番号 30 に居住している車を見つけます。そして、車のメーカーで設定した結果の順序づけを行います。

    List<Car> resultList = auditReader.createQuery()
                    .forEntitiesAtRevision( Car.class, 1 )
                    .traverseRelation( "owner", JoinType.INNER, "p" )
                    .traverseRelation( "address", JoinType.INNER, "a" )
                    .up().up().add( AuditEntity.disjunction().add(AuditEntity.property( "p", "age" )
                           .eq( 20 ) ).add( AuditEntity.property( "a", "number" ).eq( 30 ) ) )
                    .addOrder( AuditEntity.property( "make" ).asc() ).getResultList();
  • リビジョン番号 1 では、所有者の年齢と住所番号が同じ車を見つけます。

    Car result = (Car) auditReader.createQuery()
                    .forEntitiesAtRevision( Car.class, 1 )
                    .traverseRelation( "owner", JoinType.INNER, "p" )
                    .traverseRelation( "address", JoinType.INNER, "a" )
                    .up().up().add(AuditEntity.property( "p", "age" )
                            .eqProperty( "a", "number" ) ).getSingleResult();
  • リビジョン番号 1 では、所有者の年齢が 20 歳であるか、所有者がない車すべてを見つけます。

    List<Car> resultList = auditReader.createQuery()
                    .forEntitiesAtRevision( Car.class, 1 )
                    .traverseRelation( "owner", JoinType.LEFT, "p" )
                    .up().add( AuditEntity.or( AuditEntity.property( "p", "age").eq( 20 ),
                            AuditEntity.relatedId( "owner" ).eq( null ) ) )
                    .addOrder( AuditEntity.property( "make" ).asc() ).getResultList();
  • リビジョン番号 1 では、メーカーが car3 であり、所有者の年齢が 30 歳であるか、所有者のいない車すべてを見つけます。

    List<Car> resultList = auditReader.createQuery()
                    .forEntitiesAtRevision( Car.class, 1 )
                    .traverseRelation( "owner", JoinType.LEFT, "p" )
                    .up().add( AuditEntity.and( AuditEntity.property( "make" ).eq( "car3" ), AuditEntity.property( "p", "age" ).eq( 30 ) ) )
                    .getResultList();
  • リビジョン番号 1 では、メーカーが car3 であるか、所有者の年齢が 10 歳であるか、あるいは所有者がいない車すべてを見つけます。

    List<Car> resultList = auditReader.createQuery()
                    .forEntitiesAtRevision( Car.class, 1 )
                    .traverseRelation( "owner", JoinType.LEFT, "p" )
                    .up().add( AuditEntity.or( AuditEntity.property( "make" ).eq( "car3" ), AuditEntity.property( "p", "age" ).eq( 10 ) ) )
                    .getResultList();