9

我有一个 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 方法,并且不应访问任何其他实体对象

有任何想法吗?

4

3 回答 3

9

正如那段所说,该标准不支持从内部实体侦听器调用实体管理器方法。正如 Heiko Rupp 在他的回答中所说,我强烈建议从持久实体构建。custData如果这不可行,请考虑:

  • 异步通知。我真的不推荐这样做,因为它可能取决于正常工作的时间:
公共类 EntityListener {
    私人最终静态字符串QUEUE_NAME =“客户”;

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

    私人无效doNotifyOtherInNewTransaction(客户实体){
        // 为了让这一切正常工作,
        // 你应该执行你的通知
        // 在一个新事务中。你可能
        // 发现以声明方式执行此操作更容易
        // 通过调用一些划分的方法
        // 使用 REQUIRES_NEW
        尝试 {
            //(开始交易)
            doNotifyOther(实体);
            //(提交事务)
        } 捕捉(异常前){
            //(回滚事务)
        }
    }

    @PostUpdate
    @PostPersist
    公共无效通知其他(最终客户实体){
        ScheduledExecutorService 执行者 = getExecutorService();
        // 这是“原始”版本
        // 很可能你需要调用
        // executor.schedule 并指定延迟,
        // 为了给旧事务一些时间
        // 刷新和提交
        executor.execute(new Runnable() {
            @覆盖
            公共无效运行(){
                doNotifyOtherInNewTransaction(实体);
            }
        });
    }

    // 这与您的原始代码完全相同
    公共无效doNotifyOther(客户实体){
        CustomerFacadeREST custFacade = new CustomerFacadeREST();
        整数 customerId = entity.getCustomerId();
        字符串 custData = custFacade.find(customerId).toString();
        String successMessage = "实体添加到服务器";
        尝试 {
            ConnectionFactory 工厂 = new ConnectionFactory();
            factory.setHost("localhost");
            连接连接 = factory.newConnection();
            频道 channel = connection.createChannel();
            channel.queueDeclare(QUEUE_NAME, false, false, false, null);
            channel.basicPublish("", QUEUE_NAME, null, custData.getBytes());  
            通道.close();
            连接.close();
        }
        捕捉(IOException ex){
        }
        最后 {
        }
    }    
}
  • 注册一些提交后触发器(如果 Heilo Rupp 的答案不可行,我的建议是)。这与时间无关,因为它保证在您刷新到数据库后执行。此外,它还有一个额外的好处,即如果您最终回滚交易,您不会通知您。执行此操作的方法取决于您用于事务管理的内容,但基本上您创建某个特定实例的实例,然后在某个注册表中注册它。例如,对于 JTA,它将是:
公共类 EntityListener {
    私人最终静态字符串QUEUE_NAME =“客户”;

    私人交易 getTransaction() {
        // 从某处获取当前的 JTA 事务引用
    }

    私人无效doNotifyOtherInNewTransaction(客户实体){
        // 为了让这一切正常工作,
        // 你应该执行你的通知
        // 在一个新事务中。你可能
        // 发现以声明方式执行此操作更容易
        // 通过调用一些划分的方法
        // 使用 REQUIRES_NEW
        尝试 {
            //(开始交易)
            doNotifyOther(实体);
            //(提交事务)
         } 捕捉(异常前){
            //(回滚事务)
         }
    }

    @PostUpdate
    @PostPersist
    公共无效通知其他(最终客户实体){
        交易交易 = getTransaction();
        transaction.registerSynchronization(新同步(){
            @覆盖
            公共无效 beforeCompletion() { }

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

    // 这与您的原始代码完全相同
    公共无效doNotifyOther(客户实体){
        CustomerFacadeREST custFacade = new CustomerFacadeREST();
        整数 customerId = entity.getCustomerId();
        字符串 custData = custFacade.find(customerId).toString();
        String successMessage = "实体添加到服务器";
        尝试 {
            ConnectionFactory 工厂 = new ConnectionFactory();
            factory.setHost("localhost");
            连接连接 = factory.newConnection();
            频道 channel = connection.createChannel();
            channel.queueDeclare(QUEUE_NAME, false, false, false, null);
            channel.basicPublish("", QUEUE_NAME, null, custData.getBytes());  
            通道.close();
            连接.close();
        }
        捕捉(IOException ex){
        }
        最后 {
        }
    }    
}

如果您使用的是 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 回答