如何使用JPA生命周期事件来获取实体数据

我有一个RESTful API,它使用了一个用@EntityListners注释的实体类。 在EntityListner.java中,我有一个用@PostPersist注释的方法。 因此,当该事件触发时,我想要提取有关刚刚保存到数据库的实体的所有信息。 但是,当我尝试这样做时,Glassfish正在生成exception,并且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在他的回答中所说的那样。 如果这不可行,请考虑:

  • 异步通知。 我不建议这样做,因为它可能取决于正常工作的时间:
公共类EntityListener {
     private final static String QUEUE_NAME =“customer”;

     private ScheduledExecutorService getExecutorService(){
         //从某个地方获取异步执行程序服务
         //您很可能需要ScheduledExecutorService
         //实例,以便安排通知
         //有些延迟 或者,您可以尝试Thread.sleep(...)
         //在通知之前,但这很难看。
     }

     private void doNotifyOtherInNewTransaction(Customer entity){
         //为了让所有这些正常工作,
         //你应该执行你的通知
         //在新交易中。 你可能会
         //以声明方式更容易地执行此操作
         //通过调用一些划分的方法
         //使用REQUIRES_NEW
        尝试{
             //(开始交易)
             doNotifyOther(实体);
             //(提交事务)
         } catch(Exception ex){
             //(回滚事务)
         }
     }

     @PostUpdate
     @PostPersist
     public void notifyOther(最终客户实体){
         ScheduledExecutorService executor = getExecutorService();
         //这是“原始”版本
         //很可能你需要打电话
         // executor.schedule并指定延迟,
         //为了给旧事务一些时间
         //刷新并提交
         executor.execute(new Runnable(){
             @覆盖
             public void run(){
                 doNotifyOtherInNewTransaction(实体);
             }
         });
     }

     //这与原始代码完全一样
     public void doNotifyOther(Customer entity){
         CustomerFacadeREST custFacade = new CustomerFacadeREST(); 
        整数customerId = entity.getCustomerId();
         String custData = custFacade.find(customerId).toString();
         String successMessage =“实体已添加到服务器”;
        尝试{
             ConnectionFactory factory = new ConnectionFactory();
             factory.setHost( “本地主机”);
            连接连接= 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){
         }
        终于{
         }
     }    
 } 
  • 注册一些提交后触发器 (如果Heilo Rupp回答不可行,我的建议)。 这不依赖于时序,因为它保证在刷新到数据库后执行。 此外,它还有一个额外的好处,即如果最终回滚您的交易,您不会通知。 执行此操作的方式取决于您用于事务管理的内容,但基本上您创建某个特定实例的实例,然后在某个注册表中注册它。 例如,使用JTA,它将是:
公共类EntityListener {
     private final static String QUEUE_NAME =“customer”;

     private Transaction getTransaction(){
         //从某处获取当前的JTA事务引用
     }

     private void doNotifyOtherInNewTransaction(Customer entity){
         //为了让所有这些正常工作,
         //你应该执行你的通知
         //在新交易中。 你可能会
         //以声明方式更容易地执行此操作
         //通过调用一些划分的方法
         //使用REQUIRES_NEW
        尝试{
             //(开始交易)
             doNotifyOther(实体);
             //(提交事务)
          } catch(Exception ex){
             //(回滚事务)
          }
     }

     @PostUpdate
     @PostPersist
     public void notifyOther(最终客户实体){
         Transaction transaction = getTransaction();
         transaction.registerSynchronization(new Synchronization(){
             @覆盖
             public void beforeCompletion(){}

             @覆盖
             public void afterCompletion(int status){
                 if(status == Status.STATUS_COMMITTED){
                     doNotifyOtherInNewTransaction(实体);
                 }
             }
         });             
     }

     //这与原始代码完全一样
     public void doNotifyOther(Customer entity){
         CustomerFacadeREST custFacade = new CustomerFacadeREST(); 
        整数customerId = entity.getCustomerId();
         String custData = custFacade.find(customerId).toString();
         String successMessage =“实体已添加到服务器”;
        尝试{
             ConnectionFactory factory = new ConnectionFactory();
             factory.setHost( “本地主机”);
            连接连接= 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){
         }
        终于{
         }
     }    
 }

如果您使用的是Spring事务,那么代码将非常相似,只需更改一些类名。

一些指示:

  • ScheduledExecutorService Javadoc ,用于触发异步操作。

  • 与JTA的事务同步: 事务Javadoc和同步Javadoc

  • EJB事务划分

  • Spring等价物: TransactionSynchronizationManager Javadoc和TransactionSynchronization Javadoc 。

  • 关于Spring事务的一些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; } }