簡體   English   中英

如何使用JPA生命周期事件來獲取實體數據

[英]how to use JPA life-cycle events to get entity data

我有一個RESTful API,它使用了一個用@EntityListners注釋的實體類。 在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{

    }
  }    
} 

如果我發送注釋掉的successMessage消息而不是custData ,一切正常。

http://www.objectdb.com/java/jpa/persistence/event關於實體生命周期方法說了以下內容,我想知道這是否是這種情況。

為避免與觸發實體生命周期事件(仍在進行中)的原始數據庫操作發生沖突,回調方法不應調用EntityManager或Query方法,也不應訪問任何其他實體對象

有任何想法嗎?

正如該段所述,該標准不支持從實體偵聽器內部調用實體管理器方法。 強烈建議從持久化實體構建custData ,正如Heiko Rupp在他的回答中所說的那樣。 如果這不可行,請考慮:

  • 異步通知。 我不建議這樣做,因為它可能取決於正常工作的時間:
public class EntityListener {
    private final static String QUEUE_NAME = "customer";

    private ScheduledExecutorService getExecutorService() {
        // get asynchronous executor service from somewhere
        // you will most likely need a ScheduledExecutorService
        // instance, in order to schedule notification with
        // some delay. Alternatively, you could try Thread.sleep(...)
        // before notifying, but that is ugly.
    }

    private void doNotifyOtherInNewTransaction(Customer entity) {
        // For all this to work correctly,
        // you should execute your notification
        // inside a new transaction. You might
        // find it easier to do this declaratively
        // by invoking some method demarcated
        // with REQUIRES_NEW
        try {
            // (begin transaction)
            doNotifyOther(entity);
            // (commit transaction)
        } catch (Exception ex) {
            // (rollback transaction)
        }
    }

    @PostUpdate
    @PostPersist
    public void notifyOther(final Customer entity) {
        ScheduledExecutorService executor = getExecutorService();
        // This is the "raw" version
        // Most probably you will need to call
        // executor.schedule and specify a delay,
        // in order to give the old transaction some time
        // to flush and commit
        executor.execute(new Runnable() {
            @Override
            public void run() {
                doNotifyOtherInNewTransaction(entity);
            }
        });
    }

    // This is exactly as your original code
    public void doNotifyOther(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, custData.getBytes());  
            channel.close();
            connection.close();
        }
        catch(IOException ex){
        }
        finally {
        }
    }    
}
  • 注冊一些提交后觸發器 (如果Heilo Rupp回答不可行,我的建議)。 這不依賴於時序,因為它保證在刷新到數據庫后執行。 此外,它還有一個額外的好處,即如果最終回滾您的交易,您不會通知。 執行此操作的方式取決於您用於事務管理的內容,但基本上您創建某個特定實例的實例,然后在某個注冊表中注冊它。 例如,使用JTA,它將是:
public class EntityListener {
    private final static String QUEUE_NAME = "customer";

    private Transaction getTransaction() {
        // get current JTA transaction reference from somewhere
    }

    private void doNotifyOtherInNewTransaction(Customer entity) {
        // For all this to work correctly,
        // you should execute your notification
        // inside a new transaction. You might
        // find it easier to do this declaratively
        // by invoking some method demarcated
        // with REQUIRES_NEW
        try {
            // (begin transaction)
            doNotifyOther(entity);
            // (commit transaction)
         } catch (Exception ex) {
            // (rollback transaction)
         }
    }

    @PostUpdate
    @PostPersist
    public void notifyOther(final Customer entity) {
        Transaction transaction = getTransaction();
        transaction.registerSynchronization(new Synchronization() {
            @Override
            public void beforeCompletion() { }

            @Override
            public void afterCompletion(int status) {
                if (status == Status.STATUS_COMMITTED) {
                    doNotifyOtherInNewTransaction(entity);
                }
            }
        });             
    }

    // This is exactly as your original code
    public void doNotifyOther(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, custData.getBytes());  
            channel.close();
            connection.close();
        }
        catch(IOException ex){
        }
        finally {
        }
    }    
}

如果您使用的是Spring事務,那么代碼將非常相似,只需更改一些類名。

一些指示:

我想你可能會看到一個NPE,因為你可能違反了你引用的段落:

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

find似乎隱式查詢對象(如您所述),該對象可能未完全同步到數據庫,因此無法訪問。

在他的回答中,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 (在我的情況下,發出查詢和更新實體)而沒有任何問題。 請務必使用@Transaction(propagation = Propagation.REQUIRES_NEW)注釋updateEndpoints()方法,以確保有一個新事務來執行持久性操作。

與問題沒有直接關系,但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;
    }
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM