9

@EntityListners で注釈が付けられたエンティティ クラスを利用する RESTful API があります。EntityListner.java には、@PostPersist で注釈が付けられたメソッドがあります。そのため、そのイベントが発生したときに、データベースに永続化されたばかりのエンティティに関するすべての情報を抽出したいと考えています。しかし、それをしようとすると、Glassfish が例外を生成し、EntityListner クラスのメソッドが期待どおりに実行されません。ここにコードがあります

public class EntityListner {
private final static String QUEUE_NAME = "customer";
@PostUpdate
@PostPersist
public void notifyOther(Customer entity){
    CustomerFacadeREST custFacade = new CustomerFacadeREST(); 
    Integer customerId = entity.getCustomerId();
    String custData = custFacade.find(customerId).toString();
    String successMessage = "Entity added to server";
    try{
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("localhost");
        Connection connection = factory.newConnection();
        Channel channel = connection.createChannel();

        channel.queueDeclare(QUEUE_NAME, false, false, false, null);
      //  channel.basicPublish("", QUEUE_NAME, null, successMessage .getBytes()); 
        channel.basicPublish("", QUEUE_NAME, null, custData.getBytes());  
        channel.close();
        connection.close();


    }
    catch(IOException ex){

    }
    finally{

    }
  }    
} 

custData の代わりにコメントアウトされたsuccessMessageメッセージを送信すると、すべて正常に動作します。

http://www.objectdb.com/java/jpa/persistence/eventは、エンティティのライフサイクル メソッドに関して次のように述べています。

エンティティ ライフサイクル イベント (まだ進行中) を発生させる元のデータベース操作との競合を避けるため、コールバック メソッドは EntityManager または Query メソッドを呼び出したり、他のエンティティ オブジェクトにアクセスしたりしないでください。

何か案は?

4

3 に答える 3

9

その段落が言うように、標準はエンティティ リスナー内からのエンティティ マネージャ メソッドの呼び出しをサポートしていません。Heiko Rupp が回答で述べているように、永続化されたエンティティから構築することを強くお勧めします。custDataそれが不可能な場合は、次のことを検討してください。

  • 非同期に通知します。おそらく適切に動作するタイミングに依存するため、これはあまりお勧めしません。
パブリッククラスEntityListener {
    private final static String QUEUE_NAME = "顧客";

    プライベート ScheduledExecutorService getExecutorService() {
        // どこかから非同期エグゼキュータ サービスを取得します
        // ほとんどの場合、ScheduledExecutorService が必要になります
        // インスタンス、通知をスケジュールするために
        // 若干の遅延。または、 Thread.sleep(...) を試すこともできます
        // 通知する前ですが、それは醜いです。
    }

    private void doNotifyOtherInNewTransaction(顧客エンティティ) {
        // これらすべてが正しく機能するには、
        // 通知を実行する必要があります
        // 新しいトランザクション内。あなたかもしれません
        // これは宣言的に行う方が簡単です
        // 区切られたメソッドを呼び出すことによって
        // REQUIRES_NEW 付き
        試す {
            // (トランザクションを開始)
            doNotifyOther(エンティティ);
            // (トランザクションをコミット)
        } catch (例外例) {
            // (ロールバック トランザクション)
        }
    }

    @PostUpdate
    @PostPersist
    public void notifyOther(最終的な顧客エンティティ) {
        ScheduledExecutorService エグゼキュータ = getExecutorService();
        // これは「未加工」バージョンです
        // ほとんどの場合、呼び出す必要があります
        // executor.schedule で遅延を指定し、
        // 古いトランザクションに時間を与えるために
        // フラッシュしてコミットする
        executor.execute(新しいRunnable() {
            @オーバーライド
            public void run() {
                doNotifyOtherInNewTransaction(エンティティ);
            }
        });
    }

    // これは元のコードとまったく同じです
    public void doNotifyOther(顧客エンティティ) {
        CustomerFacadeREST custFacade = new CustomerFacadeREST();
        Integer customerId = entity.getCustomerId();
        文字列 custData = custFacade.find(customerId).toString();
        String successMessage = "エンティティがサーバーに追加されました";
        試す {
            ConnectionFactory factory = 新しい ConnectionFactory();
            factory.setHost("localhost");
            接続 connection = factory.newConnection();
            チャンネル channel = connection.createChannel();
            channel.queueDeclare(QUEUE_NAME, false, false, false, null);
            channel.basicPublish("", QUEUE_NAME, null, custData.getBytes());  
            channel.close();
            connection.close();
        }
        キャッチ(IOExceptionの例){
        }
        最後に {
        }
    }    
}
  • コミット後のトリガーを登録します(Heilo Ruppの回答が実行できない場合の私の推奨事項)。これは、データベースにフラッシュした後に実行されることが保証されているため、タイミングに依存しません。さらに、トランザクションをロールバックしても通知されないという追加の利点があります。これを行う方法は、トランザクション管理に何を使用しているかによって異なりますが、基本的には特定のインスタンスのインスタンスを作成し、それをレジストリに登録します。たとえば、JTA では次のようになります。
パブリッククラスEntityListener {
    private final static String QUEUE_NAME = "顧客";

    プライベート トランザクション getTransaction() {
        // どこかから現在の JTA トランザクション参照を取得します
    }

    private void doNotifyOtherInNewTransaction(顧客エンティティ) {
        // これらすべてが正しく機能するには、
        // 通知を実行する必要があります
        // 新しいトランザクション内。あなたかもしれません
        // これは宣言的に行う方が簡単です
        // 区切られたメソッドを呼び出すことによって
        // REQUIRES_NEW 付き
        試す {
            // (トランザクションを開始)
            doNotifyOther(エンティティ);
            // (トランザクションをコミット)
         } catch (例外例) {
            // (ロールバック トランザクション)
         }
    }

    @PostUpdate
    @PostPersist
    public void notifyOther(最終的な顧客エンティティ) {
        トランザクション transaction = getTransaction();
        transaction.registerSynchronization(新しい同期() {
            @オーバーライド
            public void beforeCompletion() { }

            @オーバーライド
            public void afterCompletion(int ステータス) {
                if (ステータス == Status.STATUS_COMMITTED) {
                    doNotifyOtherInNewTransaction(エンティティ);
                }
            }
        });             
    }

    // これは元のコードとまったく同じです
    public void doNotifyOther(顧客エンティティ) {
        CustomerFacadeREST custFacade = new CustomerFacadeREST();
        Integer customerId = entity.getCustomerId();
        文字列 custData = custFacade.find(customerId).toString();
        String successMessage = "エンティティがサーバーに追加されました";
        試す {
            ConnectionFactory factory = 新しい ConnectionFactory();
            factory.setHost("localhost");
            接続 connection = factory.newConnection();
            チャンネル channel = connection.createChannel();
            channel.queueDeclare(QUEUE_NAME, false, false, false, null);
            channel.basicPublish("", QUEUE_NAME, null, custData.getBytes());  
            channel.close();
            connection.close();
        }
        キャッチ(IOExceptionの例){
        }
        最後に {
        }
    }    
}

Spring トランザクションを使用している場合、コードは非常に似ていますが、クラス名がいくつか変更されています。

いくつかのポインタ:

于 2012-12-18T20:45:37.093 に答える
3

引用していた段落に違反している可能性があるため、NPEが表示されている可能性があると思います。

String custData = custFacade.find(customerId).toString();

findデータベースに完全に同期されていないため、まだアクセスできない可能性がある(説明したように)オブジェクトを暗黙的にクエリしているようです。

于 2012-10-28T08:25:13.590 に答える
1

gpeche は回答の中で、オプション #2 を Spring に変換するのはかなり簡単だと述べています。他の人がそれを行う手間を省くために:

package myapp.entity.listener;

import javax.persistence.PostPersist;
import javax.persistence.PostUpdate;
import org.springframework.transaction.support.TransactionSynchronizationAdapter;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import myapp.util.ApplicationContextProvider;
import myapp.entity.NetScalerServer;
import myapp.service.LoadBalancerService;

public class NetScalerServerListener {

    @PostPersist
    @PostUpdate
    public void postSave(final NetScalerServer server) {
        TransactionSynchronizationManager.registerSynchronization(
            new TransactionSynchronizationAdapter() {

                @Override
                public void afterCommit() { postSaveInNewTransaction(server); }
            });
    }

    private void postSaveInNewTransaction(NetScalerServer server) {
        ApplicationContext appContext =
            ApplicationContextProvider.getApplicationContext();
        LoadBalancer lbService = appContext.getBean(LoadBalancerService.class);
        lbService.updateEndpoints(server);
    }
}

サービス メソッド (ここではupdateEndpoints()) は JPA EntityManager(私の場合は、クエリの発行とエンティティの更新) を問題なく使用できます。永続化操作を実行するための新しいトランザクションが存在することを確認するために、必ずupdateEndpoints()メソッドに で注釈を付けてください。@Transaction(propagation = Propagation.REQUIRES_NEW)

質問とは直接関係ありませんがApplicationContextProvider、JPA 2.0 エンティティ リスナーはマネージド コンポーネントではないため、アプリ コンテキストを返すカスタム クラスに過ぎず、@Configurableここで使用するのが面倒です。完全を期すために以下に示します。

package myapp.util;

import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;

public class ApplicationContextProvider implements ApplicationContextAware {
    private static ApplicationContext applicationContext;

    public static ApplicationContext getApplicationContext() {
        return applicationContext;
    }

    @Override
    public void setApplicationContext(ApplicationContext appContext)
            throws BeansException {

        applicationContext = appContext;
    }
}
于 2014-05-13T01:41:15.760 に答える