I have a RESTful API that makes use of an entity class annotated with @EntityListners. And in the EntityListner.java, I have a method annotated with @PostPersist. So, when that event fires, I want to extract all the information regarding the entity that just got persisted to the database. But when I try to do that, Glassfish is generating an exception and the method in EntityListner class is not executing as expected. Here is the code
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{
}
}
}
If I send the commented out successMessage message instead of custData, everything works fine.
http://www.objectdb.com/java/jpa/persistence/event says the following regarding the entity lifecycle methods, and I am wondering if that is the situation here.
To avoid conflicts with the original database operation that fires the entity lifecycle event (which is still in progress) callback methods should not call EntityManager or Query methods and should not access any other entity objects
Any ideas?
As that paragraph says, the standard does not support calling entity manager methods from inside entity listeners. I strongly recommend building custData
from the persisted entity, as Heiko Rupp says in his answer. If that is not feasible, consider:
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 { } } }
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 { } } }
If you are using Spring transactions, the code will be very similar, with just some class name changes.
Some pointers:
ScheduledExecutorService Javadoc, for triggering asynchronous actions.
transaction synchronization with JTA: Transaction Javadoc and Synchronization Javadoc
EJB transaction demarcation
the Spring equivalents: TransactionSynchronizationManager Javadoc and TransactionSynchronization Javadoc.
And some Spring documentation on Spring transactions
I guess you may be seeing a NPE, as you may be violating the paragraph you were citing:
String custData = custFacade.find(customerId).toString();
The find
seems to implicitly querying for the object (as you describe), which may not be fully synced to the database and thus not yet accessible.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With