java - vertical - ¿Cómo puedo incluir un productor JMS en una transacción cuando el producto JMS vive en una clase auxiliar POJO?
swing fillers java (1)
Creo que tienes 2 problemas:
Necesitas hacer que tu pojo sea SLSB. Debe ser inyectado en su oyente jms, no llamado directamente para que trate con la referencia del proxy. Todavía se puede reutilizar como un simple pojo, ya que las anotaciones se ignorarán si no se despliegan en un contenedor.
Está creando una sesión de jms usando AUTO_ACKNOWLEDGE pero necesita ser procesada. Además, asegúrese de que la conexión jms proviene de una fuente de 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 que EmailQueueMessenger se comporte de acuerdo con las anotaciones que coloca en él (transaccionales, inyecciones, etc.), debe hacer referencia a él como un EJB, no como un simple pojo. En consecuencia, su bean sesión externa debe 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
las anotaciones serán honradas.
Por último, su remitente JMS se registrará en una transacción en el punto de invocación y debe asegurarse de que el administrador de transacciones sepa que está contratando un segundo administrador de recursos en la transacción (primero el 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 JMS ya que se manejará cuando la transacción se complete (aunque he visto variaciones entre las implementaciones JMS / JTA en este punto).
Espero que eso lo aclare, ¡y realmente espero que funcione!
La breve pregunta: ¿hay alguna manera de forzar 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 productor de POJO JMS en la transacción de un EJB que persista en algunos datos en una base de datos antes de llamar al POJO para enviar el mensaje, de modo que si el mensaje no puede ser enviado debido a una excepción, la transacción de la base de datos también se retrotraerá? Quiero enviar el correo de forma asincrónica.
Esta es la ruta feliz (comenzando dentro del bean de sesión sin estado):
- guardar datos en la base de datos // esto funciona
- extraer datos seleccionados de los datos que se conservaron y colocarlos en una clase personalizada de "mensaje" (realmente un dto)
- llame al método sendEmail del POJO EmailQueueMessenger pasándole el objeto de 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 que esté completo)
El siguiente código funciona, simplemente no retrotraerá la base de datos "persistir" en la clase de llamada si forzo un error en, digamos, 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 POJO, pero no hace la diferencia.
FWIW Estoy utilizando una clase separada para EmailQueueMessenger porque habrá otras partes de la aplicación que necesitarán enviar correos electrónicos ocasionales, por lo que no quiero duplicar el código.
Debería mencionar que realicé una prueba en la que moví todas las cosas de JMS dentro del primer EJB y se ejecutó correctamente ... pero necesito que esto funcione en una clase separada para su uso en otras partes de la aplicación.