¿Cómo puedo incluir un productor JMS en una transacción cuando JMS Prod vive en una clase auxiliar POJO?

La pregunta breve: ¿hay alguna manera de obligar a un POJO llamado por un EJB sin estado a vivir en el contexto del EJB para que las transacciones y la inyección de recursos funcionen en el POJO?

Específicamente en el contexto de lo que estoy tratando de hacer: ¿cómo puedo incluir un POJO JMS Producer en la transacción de un EJB que persiste algunos datos en una base de datos antes de llamar al POJO para enviar el mensaje, de modo que si el mensaje no puede se enviará debido a una excepción, la transacción de la base de datos también se revertirá? Quiero enviar el correo de forma asíncrona.

Este es el camino feliz (comenzando dentro del bean de sesión sin estado):

  • guardar datos en la base de datos // esto funciona
  • extraiga datos seleccionados de los datos que se conservaron y colóquelos en una clase de 'mensaje' personalizada (realmente un dto)
  • llame al método sendEmail de EmailQueueMessenger POJO pasándole el objeto del mensaje.
  • el mensaje se envía al MDB para procesar y enviar el correo electrónico (no es parte de la pregunta, solo aquí para completar)

El siguiente código funciona, simplemente no revertirá la base de datos "persistente" en la clase de llamada si fuerzo un error en, por ejemplo, una búsqueda de contexto. Por cierto, tampoco puedo hacer que la inyección de @Resource funcione.

//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);

              ...

He intentado agregar:

@TransactionAttribute(TransactionAttributeType.MANDATORY)
@Stateless

a la definición de POJO pero no hace la diferencia.

FWIW, estoy usando una clase separada para EmailQueueMessenger porque habrá otras partes de la aplicación que necesitarán enviar correos electrónicos ocasionales, así que no quiero duplicar el código.


Debería mencionar que hice una prueba en la que moví todas las cosas de JMS dentro del primer EJB y funcionó correctamente... pero necesito que esto funcione en una clase separada para que lo usen otras partes de la aplicación.

preguntado el 03 de mayo de 12 a las 20:05

¿Por qué no conviertes el servicio de mensajería por correo electrónico en SLSB? De esa manera, inyectará recursos en él y también propagará su contexto de transacción. -

Como mencioné hacia abajo, hice eso y mientras se ejecutaba, todavía no recogía la transacción (o revertía el db tx en el ejb que llamaba cuando el productor fallaba), ni funcionaba la inyección de recursos. En el último caso, obtendría excepciones de puntero nulo cuando intentara instanciar la conexión ('Connection con = connectionFactory...' Admito completamente que tal vez no estoy creando el SLSB correctamente, pero que yo sepa con anotaciones todo lo que Lo que tengo que hacer es anotar la definición de POJO. Así es como los he creado hasta ahora. -

Tal vez me perdí algo. Leí sus preguntas diciendo que intentó hacer del POJO un SLSB. Estoy diciendo que deberías hacer que el servicio de correo electrónico sea un SLSB. -

1 Respuestas

Creo que tienes 2 problemas:

  1. Necesitas hacer que tu pojo sea un SLSB. Debe inyectarse en su oyente jms, no llamarse directamente para que esté tratando con la referencia del proxy. Todavía se puede reutilizar como un pojo simple, ya que las anotaciones se ignorarán si no se implementan en un contenedor.

  2. Está creando una sesión jms usando AUTO_ACKNOWLEDGE pero debe tramitarse. Además, asegúrese de que la conexión jms provenga de una fuente JCA transaccional, ya que eso asociará la sesión a la transacción.

========= Actualización =========

Hola Bill;

Disculpas, pensé que el frijol externo era un oyente JMS por alguna razón... De todos modos, el problema es el mismo.

Si desea EmailQueueMessenger para comportarse de acuerdo con las anotaciones que coloca en él (transaccional, inyecciones, etc.), debe hacer referencia a él como un EJB, no como un simple pojo. En consecuencia, su bean de sesión externo debería verse así:

@EJB   // key difference
private EmailQueueMessenger eqm;

@TransactionAttribute(TransactionAttributeType.REQUIRED)
public void sendMessage(Object messageObject) {
   eqm.sendEmail(messageObject);
}

Ahora tu

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

y

@TransactionAttribute(TransactionAttributeType.MANDATORY)
@Stateless

se respetarán las anotaciones.

Por último, su remitente JMS se inscribirá en una transacción en el punto de invocación y debe asegurarse de que el administrador de transacciones sepa que está inscribiendo a un segundo administrador de recursos en la transacción (primero DB y ahora JMS). No estoy tan familiarizado con glassfish, pero parece que hay una pantalla de configuración con un interruptor que le permite especificar el nivel de soporte transaccional para una fábrica de conexiones.

Cambiaría el código del remitente a:

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

Técnicamente, puede almacenar en caché la instancia de conexión JMS en la instancia de EmailQueueMessenger. Su código no debe cerrar la sesión de JMS, ya que esto se manejará cuando se complete la transacción (aunque he visto variaciones entre las implementaciones de JMS/JTA en este punto).

Espero que eso lo aclare, y yo realmente espero que funcione !

contestado el 23 de mayo de 17 a las 13:05

No estoy seguro de entender lo que quiere decir con "inyectado en su oyente jms". Me esforzaré por resolver esto, pero si puede aclararlo, se lo agradecería. La conexión proviene de glassfish, por lo que supongo que es transaccional. - Bill Rosmus

En cuanto a AUTO_ACKNOWLEDGE, puede ser, pero si nota que puse un error para que falle incluso antes de que cree una conexión, entonces no creo que eso esté jugando un papel. Jugaré con el reconocimiento de la transacción del cliente, pero paso a paso. :) Gracias por las ideas. Espero su aclaración. - Bill Rosmus

Sí, eso fue todo... No agregué el @EJB. [Bill sacude la cabeza con tristeza]. Muchas gracias por la captura. :) Estoy usando la cola por cierto para desacoplar el servicio que envía los mensajes de los que piden que se envíen. Para mantenerlos desacoplados, creo que es mejor hacer una utilidad en sentido descendente para monitorear el DMQ y el servicio de correo electrónico en lugar de retrasar al creador. Así que mantendré AUTO_ACKNOWLEDGE por ahora. Pero quiero asegurarme de que todo en cada extremo esté bien abotonado, así que esto es genial. ¡Gracias de nuevo! - Bill Rosmus

No es la respuesta que estás buscando? Examinar otras preguntas etiquetadas or haz tu propia pregunta.