2012-10-28 22 views
8

我有一個RESTful API,它使用了一個用@EntityListners註解的實體類。而在EntityListner.java中,我有一個用@PostPersist註解的方法。所以,當這個事件觸發時,我想提取關於剛剛保存到數據庫的實體的所有信息。但是當我嘗試這樣做時,Glassfish正在生成一個異常,並且EntityListner類中的方法未按預期執行。下面是代碼如何使用JPA生命週期事件獲取實體數據

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的或查詢方法,不應該訪問的任何其他實體對象

原始數據庫操作衝突有任何想法嗎?

+0

你是什麼例外,你得到了什麼? –

+0

包含異常和堆棧跟蹤。 – James

回答

8

正如該段所述,該標準不支持從實體監聽器內部調用實體管理器方法。我強烈推薦建議從持續的實體構建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魯普答案是不可行的)。這不是依賴於時序的,因爲它保證在你刷新到數據庫後執行。此外,它具有額外的好處,即如果最終回滾您的交易,則不會通知您。做到這一點的方式取決於你用於事務管理的內容,但基本上你可以創建一個特定實例的實例,然後在註冊表中註冊它。例如,對於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的事務中,代碼將是非常相似,只是一些類名稱的變化。

一些指針:

3

我猜你可能會看到一個NPE,因爲你可能會違反你引用了一段話:

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

find似乎隱含查詢對象(如你描述),這可能不是完全同步到數據庫,因此尚未訪問。

+0

謝謝@Heiko;是的,我正在獲得NPE。我在我的代碼中添加了Thread.sleep(2000),爲數據庫提供了足夠的時間同步,但無濟於事。那麼,有沒有解決辦法? – Abraham

+2

難道你不能只使用傳入的客戶對象嗎? –

+0

我想讀回Jersey用來創建它的參數的Json表示。如果我使用我發送的參數,我只能得到它的ID。 – Abraham

1

在他的回答,gpeche指出,這是相當簡單的,以他的選項#2轉換成春。挽救他人這樣做的麻煩:

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; 
    } 
} 
相關問題