sábado, 6 de noviembre de 2010

CMT, BMT y métodos callback de SessionSynchronization

Para este ejemplo estoy usando Netbeans 6.9, Ubuntu 10.04, Glassfish y MySQL Database
En este tutorial vamos a crear Transacciones del tipo BMT(Bean Managed Transaction) y del tipo CMT(Container Managed Transaction)
Para esto creamos un proyecto empresarial llamado Transacciones:


Como primer paso vamos a crear una base de datos en mysql llamada DBTransacciones, para esto usamos el comando create database DBTransacciones desde la linea de comando de MySQL.

Una vez creada la base de datos, vamos a agregar la referencia de la libreria de MySQL a nuestro proyecto de Netbeans.
Luego creamos la unidad de persistencia desde Netbeans.
Damos boton derecho sobre el proyecto EJB y seleccionamos New/Other/persistence/Persistence Unit. Le damos el nombre Transacciones-ejbPU, como proveedor de persistencia seleccionamos TopLink y en DataSource seleccionamos new DataSource y creamos un nuevo datasource a DBTransacciones, una vez hecho esto seleccionamos como Table Generation Strategy Drop and Create y damos click en Finish.

Hecho esto vamos a crear tres paquetes uno llamado com.ejemplo.cmt, otro paquete llamado com.ejemplo.bmt y uno llamado com.ejemplo.entity, dentro del proyecto EJB

Hagamos unas puntualizaciones con respecto a CMT y BMT, el primero son transacciones que se ejecutan a nivel del contenedor, con el uso de anotaciones a nivel de los métodos o a nivel de las clases. Mientras que en BMT usamos el tipico begin, commit y rollback en nuestro código.
CMT soporta los siguientes tipos de anotaciones:
REQUIRED: Usado en métodos que necesitan ejecutarse en una transacción pero no necesariamente una nueva transaccion.
REQUIRES_NEW: Si una transacción se deben ejecutar en una nueva transacción
SUPPORTS:Cuando los métodos pueden tener codigo que les permita soportar una transaccion.
MANDATORY: Cuando el método debe ejecutarse obligatoriamente en la transacción.
NOT_SUPPORTED:Para métodos que no deben soportar una transacción.
NEVER:Para métodos que no deben ser llamados dentro de una transaccion.

Para este ejemplo de CMT vamos a crear un método con anotaciones de tipo REQUIRED y otro método del tipo REQUIRES_NEW que haremos que falle.
En com.ejemplo.entity creamos un Entity Class llamado persona. Esta clase tendra las variables miembro nombre, direccion, telefono del tipo String, con sus respectivos getters y setters.
Dentro de com.ejemplo.cmt creamos un session bean del tipo stateless, con interface remota llamado UtilitarioCMT. Agregaremos dos business methods, uno llamado agregarPersona y otro llamado cambiarTelefono.Tambien agregamos una inyeccion de Persistence Context con su respectivo EntityManager.
Agregamos luego de la anotacion de stateless la anotacion @TransactionManagement(TransactionManagementType.CONTAINER), para indicar que vamos a utilizar CMT. Agregamos la anotación @TransactionAttribute(TransactionAttributeType.REQUIRED) antes de la función agregarPersona y la anotación @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW) antes de cambiarTelefono. Codificamos agregarPersona para que persista un objeto en la base de datos y cambiarTelefono consultará un registro de la base de datos(dato que no existe) y lo actualizará, entonces fallará y no se ejecutará esta transacción, pero la de agregarPersona si se ejecutará.

Entonces el código quedará de la siguiente manera:

package com.ejemplo.cmt;

import com.ejemplo.entity.Persona;
import javax.ejb.Stateless;
import javax.ejb.TransactionAttribute;
import javax.ejb.TransactionAttributeType;
import javax.ejb.TransactionManagement;
import javax.ejb.TransactionManagementType;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;

@Stateless
@TransactionManagement(TransactionManagementType.CONTAINER)
public class UtilitarioCMT implements UtilitarioCMTRemote {
@PersistenceContext EntityManager em;
@TransactionAttribute(TransactionAttributeType.REQUIRED)
public void agregarPersona() {
Persona p=new Persona();
p.setId(1L);
p.setDireccion("Amazonas");
p.setNombre("Juliana");
p.setTelefono("7654321");
em.persist(p);
}
@TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
public void cambiarTelefono() {
Persona p=em.find(Persona.class, 12L);
p.setTelefono("1234567");
em.merge(p);
}
}

Luego dejamos el Main del proyecto cliente de la siguiente manera:

package transacciones;

import com.ejemplo.cmt.UtilitarioCMTRemote;
import javax.ejb.EJB;

public class Main {

@EJB private static UtilitarioCMTRemote util;
public static void main(String[] args) {
util.agregarPersona();
util.cambiarTelefono();
}

}

y ejecutamos el proyecto, como veremos aparcerá un error debido a la función cambiarTelefono()

Pero en la base de datos se ha guardado Persona de agregarPersona, debido a que agregarPersona y cambiarTelefono() actuan en dos transacciones diferentes.


Ahora vamos a hacer un ejemplo con BMT, en nuestro paquete com.ejemplo.bmt, creamos un SessionBean del tipo Stateless, con interface remota, llamado UtilitarioBMT. Luego de Stateless agregamos la anotación: @TransactionManagement(TransactionManagementType.BEAN)
y dentro de la clase a mas de la inyeccion de PersistenceContext con su respectivo EntityManager, agregamos una inyección de recurso @Resource UserTransaction ut. Luego creamos la fiuncion crearPersona() y usamos ut.begin() para indicar en donde empieza la transacción y ut.commit() para indicar donde termina la transacción, además de un ut.rollback() en caso de que nos de una excepción y se tengan que deshacer las operaciones hechas. A continuación coloco el respectivo código.

package com.ejemplo.bmt;

import com.ejemplo.entity.Persona;
import javax.annotation.Resource;
import javax.ejb.Stateless;
import javax.ejb.TransactionManagement;
import javax.ejb.TransactionManagementType;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.transaction.UserTransaction;

@Stateless
@TransactionManagement(TransactionManagementType.BEAN)
public class UtilitarioBMT implements UtilitarioBMTRemote {
@Resource UserTransaction ut;
@PersistenceContext EntityManager em;
public void crearPersona() {
try {
ut.begin();
Persona p = new Persona();
p.setDireccion("10 de Agosto");
p.setId(2L);
p.setNombre("Adriana");
p.setTelefono("2256776");
em.persist(p);
ut.commit();
} catch (Exception ex) {
try {
ut.rollback();
} catch (Exception ex1) {
ex1.printStackTrace();
}
ex.printStackTrace();
}
}
}

y nuestro metodo main de la clase Main del proyecto Cliente queda de la siguiente forma:

package transacciones;

import com.ejemplo.bmt.UtilitarioBMTRemote;
import javax.ejb.EJB;

public class Main {
@EJB private static UtilitarioBMTRemote util;
public static void main(String[] args) {
util.crearPersona();

}
}

y arroja la siguiente salida en la Base de Datos:



Vamos a indicar un aspecto extra con respecto a CMT usado en Stateful y es que podemos hacer la llamada a funciones callback antes y después de la ejecución del metodo de la transaccion, para esto vamos a crear el paquete com.ejemplo.cmt.stateful y dentro creamos un SessionBean llamado UtilitarioStateful, de tipo stateful, con interface remota. Dentro colocaremos una función llamada crearPersona y en las interfaces a implementar agregaremos SessionSynchronization, lo cual nos obliga a colocar los métodos afterBegin(),afterCompletion(boolean committed) y beforeCompletion(). Como podemos presumir en base a sus nombres son métodos que se ejecutan antes y después de llamar al metodo de la transacción.El código queda de la siguiente manera:

package com.ejemplo.cmt.stateful;

import com.ejemplo.entity.Persona;
import java.rmi.RemoteException;
import javax.ejb.EJBException;
import javax.ejb.SessionSynchronization;
import javax.ejb.Stateful;
import javax.ejb.TransactionAttribute;
import javax.ejb.TransactionAttributeType;
import javax.ejb.TransactionManagement;
import javax.ejb.TransactionManagementType;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;

@Stateful
@TransactionManagement(TransactionManagementType.CONTAINER)
public class UtilitarioStateful implements UtilitarioStatefulRemote,SessionSynchronization {
@PersistenceContext EntityManager em;
@TransactionAttribute(TransactionAttributeType.REQUIRED)
public void crearPersona() {
Persona p=new Persona();
p.setId(1L);
p.setDireccion("Naciones Unidas");
p.setNombre("Karla");
p.setTelefono("987654");
em.persist(p);
}

@Override
public void afterBegin() throws EJBException, RemoteException {
System.out.println("Ya empezó");
}

@Override
public void afterCompletion(boolean committed) throws EJBException, RemoteException {
System.out.println("Ya se acabó");
}

@Override
public void beforeCompletion() throws EJBException, RemoteException {
System.out.println("Antes de que se acabe");
}
}

Finalmente cambiamos nuestro metodo main de la clase Main del Proyecto Cliente a:
package transacciones;

import com.ejemplo.cmt.stateful.UtilitarioStateful;
import com.ejemplo.cmt.stateful.UtilitarioStatefulRemote;
import javax.ejb.EJB;

public class Main {
@EJB private static UtilitarioStatefulRemote util;
public static void main(String[] args) {
util.crearPersona();
}
}

y tenemos la salida en la consola de Glassfish:

No hay comentarios:

Publicar un comentario