How to include JMS Producer in a transaction when JMS Prod lives in a POJO helper class

Short question: is there a way to force a POJO called stateless email to live in an EJB context so that transactions and resource investments work in a POJO?

In particular, in the context of what I'm trying to do: how can I include a POJO JMS Producer in an EJB transaction that stores some data in a database before calling POJO to send a message, for example, if the message cannot be sent because exceptions, database transaction will also roll back? I want to send mail asynchronously.

This is a happy path (starting from a session without a bean state):

  • save data to database // it works
  • select the data from the data that was saved and put it in the custom 'message' class (really dto)
  • Call the sendEmail method of the EmailQueueMessenger POJO service that passes the message object to it.
  • sent to MDB for processing and sending email (not part of the question, just for completeness)

The code below works, it just won’t roll back the persist database in the calling class if I make an error, say, contextual search. By the way, I can't get @ Resource injection to work.

//In the EJB EmailQueueMessenger eqm = new EmailQueueMessenger(); eqm.sendEmail(messageObject); // mailObject will be translated into an email message at the other end of the queue. /******************** POJO Below ************/ public class EmailQueueMessenger implements Serializable { // Resource injection doesn't work... using 'lookup' below, which does work. // @Resource(name = "jms/EmailerQueueConnectionFactory") // private ConnectionFactory connectionFactory; // @Resource(name = "jms/EmailerQueue") // private Destination EmailerQueue; public EmailQueueMessenger() { } public void sendEmail(MailMessageDTO theMessage) { Context ctx = null; try { ctx = new InitialContext(); ConnectionFactory connectionFactory = (ConnectionFactory) ctx.lookup("jms/EmailerQueueConnectionFactory"); System.out.println("JMS Producer CTX Name In Namespace: " + ctx.getNameInNamespace()); //Destination EmailerQueue = (Destination) ctx.lookup("jms/ERROR"); // forces exception Destination EmailerQueue = (Destination) ctx.lookup("jms/EmailerQueue"); // normal working code try { Connection con = connectionFactory.createConnection(); Session session = con.createSession(false, Session.AUTO_ACKNOWLEDGE); MessageProducer msgProd = session.createProducer(EmailerQueue); ... 

I tried to add:

 @TransactionAttribute(TransactionAttributeType.MANDATORY) @Stateless 

to define a POJO, but that doesn't matter.

FWIW I use a separate class for MailQueueMessenger, because there will be other parts of the application that will have to send random emails, so you do not want to duplicate the code.


It should be mentioned that I did a test where I moved all the JMS materials to the first EJB and it works correctly ... but I need this to work in a separate class for use by other parts of the application.

+2
source share
1 answer

I think you have 2 questions:

  • You need to make your pojo SLSB. It should be injected into your jms listener, which is not called directly, so you are dealing with a proxy link. It can still be used as a simple pojo, as annotations will be ignored if they are not deployed in the container.

  • You are creating a jms session using AUTO_ACKNOWLEDGE, but you need to execute it. Also, make sure the jms connection comes from the JCA transaction source, as this will connect the session to the transaction.

========= Update =========

Hey Bill;

Sorry, I thought the external bean was a JMS listener for some reason ..... In any case, the problem is the same.

If you want MailQueueMessenger to behave according to the annotations that you post on it (transactional, injections, etc.), you need to refer to it as an EJB, not a simple pojo. Accordingly, your external bean session should look like this:

 @EJB // key difference private EmailQueueMessenger eqm; @TransactionAttribute(TransactionAttributeType.REQUIRED) public void sendMessage(Object messageObject) { eqm.sendEmail(messageObject); } 

Now your

 @Resource(name = "jms/EmailerQueueConnectionFactory") @Resource(name = "jms/EmailerQueue") 

and

 @TransactionAttribute(TransactionAttributeType.MANDATORY) @Stateless 

annotations will be executed.

Finally, your JMS sender will be registered in the transaction at the point of call, and you need to make sure that the transaction manager knows that you are picking up the second resource manager in the transaction (first the database, and now the JMS). I am not familiar with the glass board, but there seems to be a configuration screen with a switch that allows you to specify the level of transactional support for the factory connection .

I would change the sender code to:

 Session session = con.createSession(true, Session.SESSION_TRANSACTED); 

Technically, you can cache an instance of a JMS connection in an instance of EmailQueueMessenger. Your code should not close the JMS session, as it will be processed when the transaction completes (although I saw deviations between the JMS / JTA implementations at this point).

I hope this clears, and I really hope it works!

+1
source

Source: https://habr.com/ru/post/1411781/


All Articles