如何使用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; } }