2017-02-16 4 views
0

私は、onSaveonFlushDirtyを上書きして、ユーザが作成した作成日、最終更新日、最後に変更されたユーザに関する情報を設定するために使用するカスタムEmptyInterceptorを持っています。各フィールドをループする以外にJPAエンティティが変更されたことを伝える簡単な方法はありますか?

このシステムはかなりうまく機能しましたが、入力データを使用してエンティティ上のセッターを盲目的に呼び出すコードがあるという問題が見つかりました。この場合、データが変更されていなくても、Hibernateはカスタムインターセプタを起動し、最後に変更されたユーザと日付を設定します。これにより、Hibernateはデータベース内のエンティティを更新します。このテーブルに挿入トリガーと更新トリガーがあるので、これはわかります。インターセプタを無効にすると、Hibernateはデータベース内のエンティティを更新しません。

エンティティが実際に変更されていない場合、そのような状況で更新が行われない場合は、ユーザーと日付の設定を避けたいと考えています。私は、Hibernateがそのdirty entity checkingをやっている方法について読んでいて、とcurrentStateの配列をonFlushDirtyに渡しました。私はこのチェックを自分で行うことができます。それを行う簡単な方法はありますか?

私はHibernateSession.isDirty()を見ましたが、この特定のエンティティが変更されているかどうかは分かりません。セッションに変更されたエンティティがある場合のみです。


UPDATE

それは盲目的にセッターを呼び出し、問題のコードは問題ではなかったことが判明。既に存在していたコレクションを変更するのではなく、子オブジェクトのコレクションを設定するのは問題のコードでした。この場合、Hibernateはエンティティが変更されたか、少なくともインタセプタを呼び出すのに十分だと考えます。

+0

エンティティがあなた自身によって作成され、インターセプタを制御できます: 各オブジェクトがフィールドごとに他のオブジェクトフィールドと比較できるように、エンティティの 'equals'メソッドをオーバーライドしないでください。 'last update date'またはあなたが 'object1.equals(object2); 'と呼ぶインターセプタでそれらを比較するような他の方法でも可能です。そこでは、いくつかのデータが変更されたかどうかをチェックし、特定のデータをチェックするように制御することができます。 –

+0

@SEAang、私は 'onFlushDirty'メソッドに渡されるオブジェクトとして現在のエンティティを持っていますが、私はそれを比較するものは何もありません。 Hibernateは元の状態を表すエンティティの別のコピーを私に提供しません。元のフィールドのすべてを表す配列と、変更されたフィールドを表す配列があります。私はそれを繰り返し、値を比較しています。 –

答えて

1

は、私が実行されるような比較:私は質問を理解しています完全にわからないんだけど、もし

/** 
    * Called upon entity UPDATE. For our BaseEntity, populates updated by with logged in user ID and 
    * updated date-time. 
    * <p> 
    * Note that this method is called even if an object has NOT changed but someone's called a setter on 
    * a Collection related to a child object (as opposed to modifying the Collection itself). Because of 
    * that, the comparisons below are necessary to make sure the entity has really changed before setting 
    * the update date and user. 
    * 
    * @see org.hibernate.EmptyInterceptor#onFlushDirty(java.lang.Object, java.io.Serializable, java.lang.Object[], java.lang.Object[], java.lang.String[], org.hibernate.type.Type[]) 
    */ 
    @Override 
    public boolean onFlushDirty(Object entity, Serializable id, Object[] currentState, Object[] previousState, 
      String[] propertyNames, Type[] types) 
    { 

     boolean changed = false; 

     if (entity instanceof BaseEntity) { 
      logger.debug("onFlushDirty method called on " + entity.getClass().getCanonicalName()); 

      // Check to see if this entity really changed(see Javadoc above). 
      boolean reallyChanged = false; 
      for (int i = 0; i < propertyNames.length; i++) { 
       // Don't care about the collection types because those can change and we still don't consider 
       // this object changed when that happens. 
       if (!(types[i] instanceof CollectionType)) { 
        boolean equals = Objects.equals(previousState[i], currentState[i]); 
        if (!equals) { 
         reallyChanged = true; 
         break; 
        } 
       } 
      } 

      if (reallyChanged) { 
       String userId = somehowGetUserIdForTheUserThatMadeTheRequest(); 
       Date dateTimeStamp = new Date(); 

       // Locate the correct field and update it. 
       for (int i = 0; i < propertyNames.length; i++) { 
        if (UPDATE_BY_FIELD_NAME.equals(propertyNames[i])) { 
         currentState[i] = userId; 
         changed = true; 
        } 

        if (UPDATE_DATE_FIELD_NAME.equals(propertyNames[i])) { 
         currentState[i] = dateTimeStamp; 
         changed = true; 
        } 
       } 
      } 
     } 

     return changed; 
    } 
} 
+1

よく見えます。少なくとも1つの事柄が変更された直後にループから抜け出して、他のすべてのプロパティをチェックする必要はありません。 – raminr

+0

良い点、@raminr。私はその変更を行った。ありがとう! –

1

デザインの観点からは、このチェックは実際にフロントエンド/クライアント側で行う必要があります。フロントエンドがレコードがユーザーによって変更されたと判断した場合は、サーバーに更新を送信する必要があります。あなたは中間層(サーバー側)にこれをしたい場合は

、あなたはHibernateのエンティティのライフサイクルを考える必要があります。一時、永続的な、独立した、削除、およびまたsession.save()session.merge()異なる方法を考えますおよびsession.saveOrUpdate()

さらに、「セッションあたりの操作」アンチパターン、セッションごとの要求、または会話ごとのセッションパターンなど、セッションを管理する際のさまざまなデザインパターンも考慮する必要があります。 ..

セッションが開いていて、エンティティが(別の操作から)セッションに既に入っている場合、Hibernateは実際にあなたのためにダーティチェックを行うことができます。しかし、分離したエンティティがあり、そのエンティティがセッション内に存在せず、mergeを実行していると、HibernateはまずSELECTを発行してデータストア(データベース)からそのエンティティをフェッチし、その管理対象エンティティを永続そして、それは、あなたが提供するものと、1つのHibernateが永続コンテキストに取り込んで入れている2つのエンティティをマージし、それから何かが変更されたかどうかを確認します。

最後に変更されたユーザーの名前と時間をダーティチェックから除外したいので、エンティティをIDで取得することもできます(分離されたエンティティにIDがあると思われるため)。 equals()またはhashCode()を使用して独自のバージョンのダーティチェックを行い、そうでない場合はマージを呼び出します。

これはDBへの余分な移動だと思うかもしれませんが、通常の場合でも、エンティティが永続コンテキスト(セッション)にまだ存在しない場合、そうであれば、get-by-idを実行して、Hibernateはセッション中に既に持っているものを返し、DBにヒットしません。

この引数はsaveOrUpdateには当てはまりません。この場合、Hibernateはダーティチェックなしで(エンティティがセッションにまだ存在しない場合)DBに更新をプッシュし、セッションに既に存在する場合、エンティティがすでにセッションに入っているという例外がスローされます。誰もがこの問題の原因となるコードを変更せずに、同じ問題を解決する必要がある場合には

+0

良い点がありますが、結局私はこれをHibernateのバグと見なします。エンティティが変更されていない場合、EmptyInterceptorの実装を呼び出すべきではありません。このケースでは、子エンティティの「コレクション」がクリアされるのではなく再作成され、インプレースで更新されたために変更されました。そのタイプの変更は 'onFlushDirty'のために考慮されるべきではありません。 –

+0

これはバグではありません。永続的なイベントを傍受しなければならないと宣言したため、Hibernateはあなたのインタセプタを呼び出しています。エンティティへの変更は、実際にはイベントに変換されます。したがって、エンティティを変更するたびに、イベントがキューに入れられ(エンティティがダーティとマークされている場合)、これらのイベントはすべてSQLの作成に使用されます。 – raminr

+0

インターセプタがエンティティをまったく変更しない場合は、データベースに何も書き込まれません。私はオブジェクトが汚れていないと定義します。 APIのdocによれば、 'onFlushDirty'は' 'オブジェクトが汚れていると検出されたときに呼び出されます。このオブジェクトは汚れていないため、他のオブジェクトとの関係が変更されています。このシナリオでは、 'onFlushDirty'を決して呼び出さないでください。 –

関連する問題