Documentos de Académico
Documentos de Profesional
Documentos de Cultura
1 (I)
Publicado el 08 de Marzo de 2011
Con este artículo comienza un tutorial que describe el estandar EJB 3.1 de manera
introductoria. El tutorial contiene tanto material teórico como práctico, de manera que
según se vayan introduciendo nuevos conceptos se irán reflejando en código. Es muy
recomendable que antes de seguir leyendo visites el siguiente anexo donde se explica
como poner en marcha un entorno de desarrollo compatible con EJB 3.1, de manera
que puedas seguir todos los ejemplos que se van a presentar en el tutorial, así como
desarrollar los tuyos propios.
La siguiente lista muestra el contenido de los primeros 6 artículos de los que constará
el tutorial. Esta lista se actualizará si se publica contenido adicional:
Una vez escrita nuestra aplicación EJB, podemos desplegarla en cualquier contenedor
compatible con EJB, beneficiandonos de toda el trabajo tras bastidores que el
contenedor gestiona por nosotros. De esta manera la lógica de negocio se mantiene
independiente de otro código que pueda ser necesario, resultando en código que es
más fácil de escribir y mantener (además de ahorrarnos mucho trabajo).
Como se ha dicho, los POJO's son faciles de testear (siempre que estén bien
diseñados). Al final de este primer artículo se verá un sencillo ejemplo de
programación de un EJB mediante Test-Driven Development (Desarrollo Dirigido por
Tests). TDD es una metodología de desarrollo en la cual cada bloque de código está
respaldado por uno o más tests que han sido escritos con anterioridad. De manera
muy resumida, TDD nos permite enfocar de manera efectiva el problema que
deseamos resolver de la siguiente manera:
Escribir el test antes que la lógica de negocio y mantenerlo lo más simple posible nos
obliga a escribir código independiente de otro código, con responsabilidades bien
definidas (en resumen, buen código). Usado de forma correcta, TDD permite crear
sistemas que son escalables, y con niveles de bugs muy bajos. TDD es un tema tan
amplio en si mismo que no tiene cabida en este tutorial (a excepción del citado
ejemplo que veremos al final del artículo y que servirá solamente para demostrar que
el modelo EJB es un buen modelo de programación), y en el que te animo que
profundices si no lo conoces; las ventajas que ofrece para escribir código de calidad
son muchas, independientemente del uso de EJB o no.
Por otro lado, el uso de POJO's para encapsular nuestra lógica de negocio nos
proporciona un modelo simple que es altamente reutilizable (recuerda que la
reutilización de clases es un concepto básico y esencial en programación orientada a
objetos). Debes tener en cuenta que un POJO no actuará como un componente EJB
hasta que haya sido empaquetado, desplegado en un contenedor EJB y accedido por
dicho contenedor (por ejemplo a petición de un usuario). Una vez que un POJO
definido como EJB haya sido desplegado en el contenedor, se convertirá en uno de los
tres siguientes componentes (dependiendo del como lo hayamos definido):
- Session Bean
- Message-Driven Bean
- Entity Bean
Veamos una breve descripción de cada tipo de componente (en capítulos posteriores
se explicará cada tipo de componente con más detalle).
Stateless Session Beans (SLSB - Beans de Sesión sin Estado) son componentes que no
requieren mantener un estado entre diferentes invocaciones. Un cliente debe asumir
que diferentes solicitudes al contenedor de un mismo SLSB pueden devolver vistas a
objetos diferentes. Dicho de otra manera, un SLSB puede ser compartido (y
probablemente lo será) entre varios clientes. Por todo esto, los SLSB son creados y
destruidos a discrección del contenedor, y puesto que no mantienen estado son muy
eficientes a nivel de uso de memoria y recursos en el servidor.
Stateful Session Beans (SFSB - Beans de Sesión con Estado), al contrario que SLSB, si
que mantienen estado entre distintas invocaciones realizadas por el mismo cliente.
Esto permite crear un estado conversacional (como el carrito de la compra en una
tienda online, que mantiene los objetos que hemos añadido mientras navegamos por
las diferentes páginas), de manera que acciones llevadas a cabo en invocaciones
anteriores son tenidas en cuenta en acciones posteriores. Un SFSB es creado justo
antes de la primera invocación de un cliente, mantenido ligado a ese cliente, y
destruido cuando el cliente invoque un método en el SFSB que esté marcado como
finalizador (también puede ser destruido por timeout de sesión). Son menos eficientes
a nivel de uso de memoria y recursos en el servidor que los SLSB.
Los Singleton son un nuevo tipo de Session Bean introducido en EJB 3.1. Un Singleton
es un componente que puede ser compartido por muchos clientes, de manera que una
y solo una instancia es creada. A nivel de eficiencia en uso de memoria y recursos son
indiscutíblemente los mejores, aunque su uso está restringido a resolver ciertos
problemas muy específicos.
Al igual que los Stateless Session Beans, los Message-Driven Beans no mantienen
estado entre invocaciones.
Para comenzar inicia Eclipse (si aún no lo has hecho) y crea un nuevo proyecto EJB:
Ahora es el momento de crear un test que defina y respalde nuestro primer EJB. Lo
primero es crear una carpeta dentro del proyecto donde almacenaremos todos los
tests que escribamos. En la pestaña 'Project Explorer' haz click con el botón derecho
sobre el proyecto EJB y selecciona:
package es.davidmarco.ejb.slsb;
import org.junit.Test;
import static org.junit.Assert.*;
El test (un método que debe ser void, no aceptar parámetros, y estar anotado con la
anotación de JUnit @Test) declara las intenciones (el contrato) del código que estamos
diseñando: crear un objeto de la clase PrimerEJB con un método saluda() que acepte
un argumento de tipo String y devuelva un mensaje con el formato 'Hola argumento'.
Aquí empezamos a ver las ventajas de EJB: podemos testear nuestro código de
negocio directamente, sin tener que desplegar el componente EJB en un contenedor y
entonces hacer una llamada a este, con toda la parafernalia que esto requiere.
Y ahora si (por fin) vamos a escribir nuestro primer EJB. Nuestra clase de negocio ira
en un paquete con el mismo nombre que el utilizado para almacenar las clase de tests,
pero en una carpeta diferente. De esta manera mantenemos ambos tipos de clases
separadas físicamente en disco (por motivos de organización y por claridad), pero
accesibles gracias a que virtualmente pertenecen al mismo paquete, y por tanto entre
ellos hay visibilidad de tipo package-default (esto puede resultarnos útil si
necesitamos, por ejemplo, acceder desde la clase de tests a métodos en las
respectivas clases de negocio que han sido declarados como 'protected'). En la
pestaña 'Project Explorer' haz click con el botón derecho en la carpeta 'ejbModule' (la
carpeta por defecto que crea Eclipse en un proyecto EJB para almacenar nuestras
clases) y selecciona:
package es.davidmarco.ejb.slsb;
Si en este punto ejecutamos el test que hemos escrito (haciendo click con el botón
derecho sobre el editor donde tenemos el código del test y seleccionando 'Run As >
JUnit Test') este fallará (verás una barra de color rojo que indica que al menos un
tests no ha pasado correctamente). Si observas la ventana 'Failure trace' (seguimiento
de fallos) de la pestaña de resultados de JUnit, verás un mensaje que, traducido a
español, indica que se esperaba como respuesta 'Hola Usuario' pero se recibió 'null'.
Debajo de este mensaje puedes ver la pila de llamadas que ha generado el error, en
nuestro caso ha sido la función estática AssertEquals de JUnit. Volvamos al editor
donde tenemos la clase de negocio y arreglemos el código que está fallando:
package es.davidmarco.ejb.slsb;
@Test
public void testSaludaConNull() {
PrimerEJB ejb = new PrimerEJB();
assertEquals("Hola desconocido", ejb.saluda(null));
}
}
Como se dijo previamente, mediante TDD estamos dejando claras las intenciones de
nuestro código antes incluso de escribirlo, como se puede ver en el segundo tests. En
él, esperamos recibir como respuesta la cadena de texto 'Hola desconocido' cuando
invoquemos el método saluda() con un argumento de tipo null. Y mientras tanto, el
primer test (que hemos renombrado para darle más claridad y expresividad a nuestros
tests) debe seguir pasando, por supuesto. Ejecutamos los tests ('Run As > JUnit Test')
y el nuevo test que hemos escrito falla (podemos ver en la ventana a la izquierda de la
pestaña de JUnit el/los test/s que ha/n fallado marcados con una cruz blanca sobre
fondo azul). Volvamos al editor donde estamos escribiendo la lógica de negocio e
implementemos la nueva funcionalidad:
public class PrimerEJB {
public String saluda(String nombre) {
if(nombre == null) {
return "Hola desconocido";
}
Ahora ambos tests pasan. Para terminar, ¿que ocurriría si en lugar de la cadena de
texto 'usuario' pasamos al métodosaluda() una cadena de texto distinta?. Añadamos
un test que pruebe esta condición:
@Test
public void testSaludaConOtroNombre() {
PrimerEJB ejb = new PrimerEJB();
assertEquals("Hola Pedro", ejb.saluda("Pedro"));
}
@Test
public void testSaludaConNull() {
PrimerEJB ejb = new PrimerEJB();
assertEquals("Hola desconocido", ejb.saluda(null));
}
}
Este último test demuestra, al ejecutarse y fallar, que nuestra lógica de negocio
contiene un bug: siempre que invocamos el método saluda() con un parámetro de
tipo String (diferente de null) obtenemos la cadena de texto 'Hola usuario',
ignorando así el parametro que le hemos pasado. He aquí otra ventaja más que surje
del uso de TDD: descubrir bugs lo antes posible. Cuanto más tiempo tardemos en
descubrir un bug, más dificil nos resultará encontrarlo y solucionarlo. Vamos a
resolver este último error en nuestra lógica de negocio:
package es.davidmarco.ejb.slsb;
import javax.ejb.Stateless;
@Stateless
public class PrimerEJB {
// ...
}
La anotación @Stateless define nuestro POJO como un Session Bean de tipo Stateless
y una vez desplegado en un contenedor EJB, este lo reconocerá como un componente
EJB que podremos usar. ¡Así de simple!. Sin embargo, debemos añadir una segunda
anotación a nuestro (ahora si) componente EJB:
package es.davidmarco.ejb.slsb;
import javax.ejb.Remote;
import javax.ejb.Stateless;
@Remote
@Stateless
public class PrimerEJB {
public String saluda(String nombre) {
if(nombre == null) {
return "Hola desconocido";
}
La anotación @Remote permite a nuestro EJB ser invocado remotamente (esto es,
desde fuera del contenedor). Si omitimos esta anotación, el EJB sería considerado
como 'Local' (concepto que veremos en el próximo artículo) y solo podría ser invocado
por otros componentes ejecutandose dentro del mismo contenedor. Nosotros la hemos
incluido pues en la próxima sección vamos a escribir un cliente Java externo al
contenedor que solicitará a este el componente que estamos diseñado. De manera
adicional, todos los componentes que sean de tipo remoto (como este) deben extender
una interface (o de lo contrario se producirá un error durante el despliegue):
package es.davidmarco.ejb.slsb;
Por último modificamos nuestro EJB para que implemente la interface y el despliegue
sea correcto:
@Remote
@Stateless
public class PrimerEJB implements MiInterfaceEJB {
// ...
}
La necesidad de una interface para componentes de tipo remoto, aunque que a priori
pueda parecer una restricción (o una limitación), es necesaria para que el contenedor
pueda construir la vista/proxy que será enviada a los clientes remotos (externos al
contenedor) por motivos que no vienen al caso. Además, se considera una buena
práctica que nuestras clases y métodos de negocio se construyan sobre interfaces: de
esta manera los clientes que usan nuestro código trabajan con la interface, ignorando
la implementación concreta. De esta manera podemos cambiar dicha implementación
en el futuro sin romper el código de nuestros clientes.
Durante el primer despliegue nos aparecerá una ventana donde podemos seleccionar
el servidor donde deseamos realizar el despliegue (aparecerá por defecto el que
definimos al crear el proyecto). Seleccionamos la casilla 'Always use this server when
running this project' (Usar siempre este servidor cuando se ejecute este proyecto) y
hacemos click en el botón 'Finish'. La pestaña 'Console' se volverá activa y en ella
veremos multitud de información relativa al proceso de arranque del servidor (puesto
que no estaba arrancado). Tras unos momentos (30-40 segundos en mi equipo) el
contenedor se habra levantado, y con él nuestra aplicación EJB. Entre los últimos
mensajes de arranque del servidor puedes ver los siguientes:
Esas dos lineas nos indican dos referencia JNDI válidas al componente EJB que hemos
desplegado, y las necesitaremos cuando escribamos el cliente para que el contenedor
nos devuelva el objeto correcto.
Antes de finalizar esta sección, veamos un último asunto relativo al despliegue. Una
vez que ya tenemos desplegada nuestra aplicación en JBoss, si realizamos un cambio
en nuestra lógica de negocio y deseamos volver a desplegar la aplicación en el
contenedor, debemos hacerlo desde la pestaña 'Servers' de Eclipse (y no desde la
pestaña 'Project Explorer' como hicimos la primera vez que desplegamos la
aplicación). Para ello, primero abrimos la pestaña 'Servers' si no es visible en el
Workbench de Eclipse:
Por otro lado, cuando iniciemos el IDE y queramos acceder a una aplicación
desplegada con anterioridad (por ejemplo desde un cliente como el que vamos a
construir en la próxima sección) debemos iniciar primero el servidor, evidentemente.
Para ello, haz click con el botón derecho sobre el nombre del servidor en la pestaña
'Servers' y selecciona 'Start'.
En la ventana que nos aparecerá le damos un nombre a la clase (en mi caso 'Cliente') y
marcamos la casilla 'public static void main(String[] args)' para que nos cree un
método main() automáticamente, y hacemos click en el botón 'Finish'.
Antes de mostrar el código del cliente es preciso mencionar que para ejecutarlo se
necesitan ciertas librerias que, por motivos de simplicidad, vamos a obtener del primer
proyecto (la aplicación EJB). Para ello, en la pestaña 'Project Explorer' haz click con el
botón derecho sobre el nombre del proyecto que hace de cliente y selecciona:
package es.davidmarco.ejb.cliente;
import java.util.Properties;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import es.davidmarco.ejb.slsb.MiInterfaceEJB;
Una vez que tenemos la vista podemos ejecutar cualquiera de los métodos que
definimos en su interface asociada (MiInterfaceEJB). Para ejecutar el cliente nada
tan sencillo como hacer click con el botón derecho sobre el editor donde lo tenemos y
seleccionar:
1.9 RESUMEN
Este primer artículo del tutorial de introducción a EJB 3.1 ha sido muy fructífero. En el
hemos visto de manera superficial los conceptos básicos de la especificación EJB, una
breve introducción al contenedor EJB, los tipos de componentes que podemos
producir, un sencillo ejemplo de Test-Driven Development + EJB, la forma de
desplegar dicho ejemplo en un contenedor compatible con EJB, y como acceder al
componente (a través del contenedor) desde un cliente Java.
2.1 METADATOS
Como vimos en la primer artículo del tutorial, para declararar nuestros POJO's como
verdaderos componentes EJB necesitamos usar metadatos. Estos metadatos pueden
ser de dos tipos:
- Anotaciones
- XML
El uso de anotaciones es, a priori, el más sencillo y expresivo. Esta forma de aplicar
información a nuestros componentes está disponible en la plataforma JavaEE desde su
versión 5, y por tanto disponible también en JavaEE 6 (plataforma de la cual forma
parte la especificación EJB 3.1). El único punto en contra del uso de anotaciones es
que, si deseamos cambiar el comportamiento de un componente, debemos recompilar
nuestro código (ya que la anotación acompaña al código Java). Por motivos de
simplicidad, esta será la forma de metadatos que se utilizará durante todo el tutorial.
Puedes consultar la referencia completa (en inglés) de las anotaciones soportadas en
la especificación EJB 3.0 en la siguiente dirección.
2.2 EL POOL
Un pool es, expresado de forma básica, un almacén de objetos. El contenedor EJB
mantiene uno o varios pools donde almacena componentes que están listos para ser
servidos a un cliente que los solicite. De esta manera, el contenedor gestiona la
creación, mantenimiento, y destrucción de componentes en segundo plano y de
manera transparente a la aplicación EJB (mejorando así el rendimiento de esta última
y de sus clientes).
Cuando un cliente obtiene una referencia, ya sea local (mediante @EJB, como veremos
en la sección 2.5), o remota (mediante JNDI, como vimos en el ejemplo al final
del artículo anterior), esta no apunta a ninguna instancia del componente en cuestión.
Solamente cuando se invoca un método en dicha referencia, el contenedor extrae una
instancia del pool y la asigna a dicha referencia, de manera que la invocación pueda
tener efecto.
@Local
@Remote
@Stateless
public class MiBean implements MiInterfaceRemote {
// esta clase produce un error al desplegar
}
En el ejemplo anterior, la clase MiBean actua como interface local y remota al mismo
tiempo, lo cual no está permitido. Sin embargo, lo siguiente si que es válido:
@Local
public interface MiBeanLocalInterface {
// declaración de métodos locales
}
@Remote
public interface MiBeanRemoteInterface {
// declaración de métodos remotos
}
@Stateless
public class MiBean implements MiBeanLocalInterface,
MiBeanRemoteInterface {
// implementación de métodos locales y remotos
}
@Local(PrimerBeanLocalInterface.class)
@Remote(PrimerBeanRemoteInterface.class)
@Stateless
public class MiBean implements MiBeanRemoteInterface {}
En el ejemplo anterior, hemos eliminado las anotaciones en las interfaces y las hemos
aplicado a la clase MiBean. Añadiendo los argumentos correspondientes
en @Local y @Remote le indicamos al contenedor que interface actuará como local y
cual como remota. Seguimos teniendo que implementar la interface remota en la
declaración de la clase (implements MiBeanRemoteInterface), pues como se vio en
el artículo anterior, todo Session Bean declarado como remoto necesita implementar
una interface de manera obligatoria (dicha interface es necesaria para la creación del
proxy/vista).
@Stateless
@Local(MiInterfaceLocal.class)
public class MiBeanLocal extends MiBeanBase implements MiInterfaceLocal
{
// implementación concreta para acceso local
}
@Stateless
@Remote(MiInterfaceRemote.class)
public class MiBeanRemote extends MiBeanBase implements MiInterfaceRemote
{
// implementación concreta para acceso remoto
}
En este tutorial los ejemplos se mantendrán siempre lo más simple posibles, pues a
efectos lectivos toda la parafernalia del ejemplo anterior no es necesaria (de hecho es
contraproducente). El aspecto clave a recordar estriba en no declarar una misma
interface (repito, en el sentido de contrato) de tipo local y remota al mismo tiempo.
2.4 EJB CONTEXT Y SESSION CONTEXT
A veces, necesitamos acceder al contexto de ejecución del componente que se está
usando. EJBContext (Contexto de EJB) es una interface que provee acceso al contexto
de ejecución asociado a cada instancia de un componente EJB. A través de él podemos
acceder, por ejemplo, al servicio de seguridad: imaginemos que invocamos un método
en un Session Bean dentro de una aplicación donde hay restricciones de seguridad, de
manera que el Session Bean necesita comprobar si el cliente que lo está invocándo
está autorizado o no a hacerlo. A través del contexto de ejecución de dicho Session
Bean podemos acceder al servicio de seguridad y realizar dicha comprobación. Otros
ejemplos del uso del contexto de ejecución son el acceso a transacciones, u obtener el
Timer Service (Servicio de Reloj, necesario para programar eventos mediante unidades
de tiempo, como veremos en un artículo posterior).
@Local
@Stateless
public class MiBean {
@Resource
private SessionContext contexto;
// ...
}
@Local
@Stateless
public class MiBean {
private SessionContext contexto;
@Resource
public void setContexto(SessionContext contexto) {
this.contexto = contexto;
}
// ...
}
@Stateless
public class UnComponente {
private OtroComponente dependencia;
@Local
@Stateless(name="otroComponente")
public class OtroComponente {
public String otroMetodo() {
// ...
}
}
@Stateless
public class UnComponente {
@EJB(beanName="otroComponente")
private OtroComponente dependencia;
En este artículo veremos los dos primeros tipos de Session Beans, y dejaremos el
componente Singleton y los Message-Driven Beans para el próximo artículo. Ahora
otra buena pregunta sería: ¿Que NO es un componente del lado del servidor? La
respuesta es: Entity Beans (EB - entidades). Los EB viajan entre el cliente y el servidor
siendo siempre la representación del mismo objeto Java en ambos lados. Todo lo
relacionado con EB se tratará en el capítulo relacionado con persistencia (si estás
interesado en este tema puedes visitar un tutorial sobre Java Persistent API (JPA -
API de Persistencia en Java) publicado en esta misma web en la siguiente dirección).
Es importante destacar que un SLSB si puede mantener un estado interno, aunque este
no debe escapar nunca hacia el cliente. Un ejemplo de este estado interno sería una
variable que almacenara el número de veces que una instancia en concreto ha sido
invocada: un cliente que dependa del valor de esta variable podría obtener un
resultado distinto en cada llamada al SLSB, ya que el contenedor no garantiza devolver
la misma instancia al mismo cliente. La regla es: si un SLSB mantiene estado, este
debe ser interno (invisible para el cliente).
Por último, el contenedor ejecutará un método dentro del SLSB que esté anotado
con @PostConstruct (Post construcción), si existe. Esta anotación declara dicho
método (que puede ser uno y solo uno) como un método callback, esto es, un método
que reacciona ante cierto evento (en este caso, a la construcción de la instancia, como
su propio nombre indica). Dentro de este método tenemos la oportunidad de adquirir
recursos adicionales necesarios para el SLSB, como una conexión de red o de base de
datos. Este método debe ser void, no aceptar parámetros, y no lanzar ninguna
excepción de tipo checked. Al contrario que la inyección de recursos, la ejecución del
método@PostConstruct se ejecuta una y solo una vez (al final de la instanciación del
componente).
Cuando el contenedor no necesita una instancia de SLSB, ya sea porque decide reducir
el número de instancias en el pool, o porque se está produciendo un shutdown del
servidor, se realiza una transición en sentido inverso: del estadopreparado en pool al
estado no existe. Durante esta transición se ejecutará, si existe, un método anotado
con@PreDestroy (Pre destrucción), el cual es también un método callback, y el cual
nos da la oportunidad de liberar cualquier recurso adquirido durante la construcción
de la instancia (adquisición que hicimos en @PostConstruct). Al igual que ocurría
con @PostConstruct, un método anotado con @PreDestroy es opcional, debe ser void,
no aceptar parámetros, no lanzar ninguna excepción de tipo checked, solo puede ser
usado en un único método, y es ejecutado una y solo una vez (durante la destrucción
de la instancia).
package es.davidmarco.ejb.slsb;
import javax.ejb.Stateless;
import es.davidmarco.ejb.modelo.Cuenta;
@Stateless
public class OperacionesConEfectivo {
public void ingresarEfectivo(Cuenta cuenta, double cantidad) {
// ingresar cantidad en cuenta
}
¿A que nos referimos cuando decimos que un SFSB mantiene un estado? La cuestión
es simple: un SFSB almacena información a consecuencia de las operaciones que
realiza en él su cliente asociado, y dicha información (estado) debe estar disponible en
invocaciones posteriores. De esta manera, un SFSB puede realizar una acción compleja
mediante multiples invocaciones. Un ejemplo muy común es el carrito de la compra de
una tienda online; añadimos y eliminamos artículos del carrito en diferentes
invocaciones, lo cual sería imposible de realizar con un SLSB.
Por último, el contenedor ejecutará un método dentro del SFSB que esté anotado
con @PostConstruct (Post construcción), si existe. Esta anotación declara dicho
método (que puede ser uno y solo uno) como un método callback, esto es, un método
que reacciona ante cierto evento (en este caso, a la construcción de la instancia, como
su propio nombre indica). Al igual que con un SLSB, la función de este método es
adquirir recursos adicionales que sean necesarios para el SFSB, como una conexión de
red o de base de datos. Y también al igual que su homónimo en un SLSB, este método
debe ser void, no aceptar parámetros, no lanzar ninguna excepción de tipo checked, y
no serstatic ni final. La ejecución del método @PostConstruct se ejecuta una y solo
una vez, al final de la instanciación del componente. En este momento, el SFSB ya se
encuentra en el estado preparado, y procede a servir la llamada del cliente que ha
generado la creación de la instancia.
Por último, un SFSB en estado preparado puede realizar una transición a cualquiera de
los otros dos estados de su ciclo de vida: no existe o pasivo. La transición al estado no
existe ocurre cuando:
Por otro lado, la transición al estado pasivo se produce cuando el contenedor decide
liberar recursos, persistiendo de manera temporal el estado del SFSB. La pasivación y
posterior activación de un SFSB requiere una sección exclusiva que veremos
enseguida.
Por finalizar con el ciclo de vida de un componente SFSB, ten presente que si este
lanza una excepción de sistema (cualquiera de tipo unchecked cuya definición no esté
anotada con @ApplicationException), se producirá una transición al estado no
existe sin llamar a su método @PreDestroy. Este comportamiento no está muy bien
comprendido entre cierta parte de la comunidad, pues a todas luces no parece muy
correcto, pero es así y debes tenerlo en cuenta.
Estas reglas son las que se han aplicado a todos los métodos callback vistos hasta
ahora, por lo que una vez aprendidas podrás aplicarlas fácilmente cuando escribas
cualquier método callback.
package es.davidmarco.ejb.sfsb;
import java.io.Serializable;
import java.util.HashMap;
import java.util.Map;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.ejb.PostActivate;
import javax.ejb.PrePassivate;
import javax.ejb.Remove;
import javax.ejb.Stateful;
import es.davidmarco.ejb.modelo.Articulo;
import es.davidmarco.ejb.util.BaseDeDatos;
@Stateful
public class Carrito implements Serializable {
private Map articulosEnCarrito = new HashMap();
private BaseDeDatos bbdd;
@Remove
public void finalizarCompra() {
// procesar el pedido
}
@PostConstruct
@PostActivate
private void inicializar() {
// obtener conexión con la base de datos
}
@PrePassivate
@PreDestroy
private void detener() {
// liberar conexión con la base de datos
}
}
Como puedes ver, la clase Carrito está anotada con @Stateful y marcada
como Serializable. Dentro de ella tenemos dos campos: artículosEnCarrito, donde
se almacena el estado requerido para el correcto funcionamiento del proceso de
compra (HashMap es de tipo Serializable y por tanto es persistida de forma
automática en caso de pasivación), yBaseDeDatos, que es una clase ficticia que
supuestamente nos permite realizar operaciones sobre una base de datos, pero no
puede ser pasivada (supongamos que mantiene una conexión con una base de datos
real que no puede permanecer abierta durante la pasivación).
Dentro de la clase, los cuatro primeros métodos nos permiten realizar operaciones de
lógica de negocio, como añadir cierta cantidad de cierto artículo al carrito, vaciar el
carrito completamente, o procesar el pedido una vez añadidos los artículos deseados.
El cuarto método (finalizarCompra), de manera adicional, indica al contenedor que
hemos terminado de trabajar con el componente SFSB y que por tanto puede
eliminarlo (gracias a la anotación @Remove). Estos cuatro métodos son la interface que
mostramos al cliente, y por tanto todos ellos son declarados public.
2.14 RESUMEN
En este segundo artículo del tutorial de EJB hemos visto conceptos relacionados con la
tecnología EJB, como la definición del pool, local vs remoto, metadatos, inyección de
dependencias, y contexto de sesión. Ademas, hemos visto en cierta profundidad los
dos tipos de Session Bean más comunes: Stateless y Stateful.
En el próximo artículo veremos el tercer y último tipo de Session Bean (Singleton) así
como un nuevo tipo de componente: Message-Driven Beans. Hasta entonces, ¡feliz
inyección de dependencias!
class ClaseNoConcurrente {
private int numeroDeInvocacionesDeEstaInstancia = 0;
import javax.ejb.ConcurrencyManagement;
import javax.ejb.ConcurrencyManagementType;
import javax.ejb.Singleton;
@Singleton
@ConcurrencyManagement(ConcurrencyManagementType.CONTAINER)
class MiSingleton {
// ...
}
Por otro lado, cuando un método realiza solo operaciones de tipo read (lectura), de
manera que no se altera el estado del componente, podemos indicarle al contenedor
que no bloquee ninguna invocación a dicho método. De esta manera, el método pueda
ser accedido por múltiples hilos de ejecución simultaneamente (puesto que no
modificamos el estado del componente, el acceso en paralelo es seguro). Tenemos que
indicar que un método es de tipo readmediante la anotación @Lock:
import javax.ejb.Lock;
import javax.ejb.LockType;
// ...
@Lock(LockType.READ)
public String metodo() {
// ...
}
Aunque, como ya se ha dicho, el tipo de bloqueo write se aplica por defecto, podemos
expresarlo de forma explícita mediante @Lock(LockType.WRITE). Otra opción que nos
brinda CMC es la liberación de un bloqueo automáticamente si este no ocurre tras un
tiempo preestablecido (timeout):
import java.util.concurrent.TimeUnit;
import javax.ejb.AccessTimeout;
// ...
@AccessTimeout(value=5, unit=TimeUnit.SECONDS)
public String metodo() {
// ...
}
import javax.ejb.ConcurrencyManagement;
import javax.ejb.ConcurrencyManagementType;
import javax.ejb.Singleton;
@Singleton
@ConcurrencyManagement(ConcurrencyManagementType.BEAN)
class OtraClaseConcurrente {
// Gestión explícita de la concurrencia
}
La concurrencia en Java es un tema largo y complejo que no puede ser tratado en este
tutorial. Un buen lugar para empezar (en inglés) es The Java Tutorials. Estoy seguro
que existen otras buenas fuentes de información en español, y si estás interesado tu
buscador favorito te llevará hasta ellas.
import javax.ejb.Singleton;
import javax.ejb.Startup;
@Singleton
@Startup
public class MiSingleton {
// ...
}
@Singleton
public class MiSingletonA {
// ...
}
@Singleton
@DependsOn("MiSingletonA")
public class MiSingletonB {
// ...
}
Para terminar con el ciclo de vida de los componentes Singleton, indicar que, al igual
que con los componentes SLSB y SFSB, disponemos de las
anotaciones @PostConstruct y @PreDestroy para responder a los eventos de creación
y destrucción (respectivamente) del componente:
@Singleton
public class MiSingletonA {
@PostConstruct
public void inicializar() {
// Cualquier operación/es necesaria/s para la creación del
componente,
// como obtención de recursos
}
@PreDestroy
public void detener() {
// Cualquier operación/es necesaria/s antes de la destrucción del
componente,
// como liberación de recursos
}
package es.davidmarco.ejb.singleton;
import java.io.FileWriter;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.ejb.Lock;
import javax.ejb.LockType;
import javax.ejb.Singleton;
@Singleton
public class SistemaDeLog {
private FileWriter writer;
@PostConstruct
protected void inicializar() throws IOException {
writer = new FileWriter("aplicacion.log", true);
}
@PreDestroy
protected void detener() throws IOException {
writer.flush();
writer.close();
}
@Lock(LockType.WRITE)
public void debug(String mensaje) {
escribirMensajeEnArchivo(Nivel.DEBUG, mensaje);
}
@Lock(LockType.WRITE)
public void info(String mensaje) {
escribirMensajeEnArchivo(Nivel.INFO, mensaje);
}
@Lock(LockType.WRITE)
public void error(String mensaje) {
escribirMensajeEnArchivo(Nivel.ERROR, mensaje);
}
return cabecera.toString();
}
}
El ejemplo anterior, aunque más desarrollado que los vistos hasta ahora (es
completamente funcional), sigue siendo un ejemplo de juguete; sin embargo, es
bastante interesante para explicar el componente Singleton. Existen verdaderos
frameworks de logging que puedes (¡y debes!) usar en tus aplicaciones.
Lo segundo que nos interesa del ejemplo son los tres métodos que se exponen al
cliente: debug, info, y error. A traves de ellos el cliente puede realizar las
operaciones de logging. Los tres han sido marcados como
métodos writemediante @Lock(LockType.WRITE), y aunque este comportamiento es
definido por defecto para todos los métodos de un Singleton (y por tanto la anotación
es redundante), se han incluido para diferenciarlos del resto (siempre es aconsejable
escribir código expresivo).
Una vista sin interface (no-interface view) es una variación del concepto de
componente local, e introducida en la especificación EJB 3.1. Como su nombre indica,
se trata de un componente que no está anotado con @Local ni @Remote, ni implementa
ninguna interface de negocio donde se hayan aplicado cualquiera de las anotaciones
anteriores. Si no implementamos ninguna interface de negocio, ¿como sabe el
contenedor que métodos del componente debe exponer a sus clientes? La respuesta es
sencilla: todos aquellos declarados public, incluyendo los de sus superclases y los de
tipo callback (en el ejemplo de la sección anterior hemos declarado los
métodos callback con un nivel de visibilidad menor a public precisamente para evitar
que sean expuestos). Aunque a efectos prácticos un componente de este tipo es
tratado como uno local, puedes encontrar los detalles concretos sobre la vista sin
interface en laespecificación EJB 3.1.
Las llamadas asíncronas son de gran utilidad en situaciones en las que un método
realiza una operación que necesita un tiempo considerable para completarse, y no
deseamos bloquear al cliente mientras dicha operación se realiza. Veamos primero el
ejemplo más sencillo posible de llamada asíncrona:
package es.davidmarco.ejb.slsb;
import javax.ejb.Asynchronous;
import javax.ejb.Stateless;
@Stateless
public class ClaseAsincrona {
@Asynchronous
public void metodoLento() {
try {
Thread.sleep(15000);
} catch(InterruptedException ie) {
throw new RuntimeException(ie);
}
}
}
import java.util.concurrent.Future;
import javax.ejb.AsyncResult;
import javax.ejb.Asynchronous;
// ...
@Asynchronous
public Future metodoLentoConResultado() {
try {
Thread.sleep(15000);
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import javax.naming.NamingException;
// ...
while(true) {
if(!resultado.isDone()) {
Thread.sleep(2000);
} else {
System.out.println("Método asincrono devuelve el resultado " +
resultado.get());
break;
}
}
La interface Future incluye otros métodos con los que podemos cancelar la ejecución
del método asíncrono, o comprobar si dicha ejecución ha sido cancelada por el
contenedor (por ejemplo en caso de excepción). Es recomendable que visites la API
de Future si vas a trabajar con ella.
Antes de terminar con los métodos asíncronos, quiero hacer constar que en la
implementación del contenedor EJB que estamos usando en este tutorial (JBoss 6.0.0
Final) las llamadas asíncronas aún no están completamente implementadas (el hilo de
ejecución del cliente se bloquea al llamar al método asíncrono hasta que este ha
terminado de ejecutarse; en otras palabras, las llamadas asíncronas se comportan
como llamadas síncronas). En otros contenedores compatibles con EJB 3.1, como
Glassfish v3, el comportamiento de las llamadas asíncronas si es el correcto (no lo he
probado personalmente, pero así aparece indicado por usuarios de ambos servidores).
3.9 SESSION BEANS EN GENERAL: RESUMEN
Con esta sección terminamos de ver los componentes de tipo Session Bean, que son
aquellos que contienen la lógica de negocio de una aplicación EJB que puede ser
invocada por los clientes de dicha aplicación (ya sea en cualquiera de sus tres
variaciones: Stateless, Stateful, o Singleton). Ahora es el momento de pasar a un
nuevo tipo de componente con una misión totalmente diferente: Message-Driven
Beans.
- No mantienen estado
- Son gestionados por el contenedor (transacciones, seguridad, concurrencia, etc)
- Son clases puras que no implementan interfaces de negocio (puesto que son
invocados por un cliente)
En lo que refiere a este tutorial, el cliente consumidor será el propio contenedor EJB, el
cual distribuirá los mensajes que consuma a los MDB correspondientes.
Los tres primeros subsistemas (clientes y broker) forman parte del servicio de
mensajería, que en la especificación EJB es gestionado por defecto mediante JMS. En
este momento es preciso desviarnos del camino para explicar con un mínimo de
detalle que es y cómo funciona JMS.
Por otro lado, la misión del proveedor JMS es dirigir y enviar los mensajes que le
llegan a traves de un broker (esto es algo bastante más complejo, pero por simplicidad
vamos a pensar que tenemos un subsistema llamado brokerdonde se almacenan los
mensajes enviados hasta que son servidos a todos sus consumidores). JMS
proporciona un tipo de mensajería asíncrona: los clientes JMS envían mensajes a
traves del broker sin esperar una respuesta. Es responsabilidad del broker hacer llegar
el mesaje a los clientes JMS que deban consumir el mensaje. De esta manera JMS
proporciona un sistema de comunicación muy poco acoplado, pues el productor y el
consumidor no saben el uno del otro en ningún momento.
En el modelo p2p, un cliente JMS de tipo productor publica sus mensajes en un canal
virtual llamado queue (cola). De manera similar al modelo pub/sub, pueden existir
multiples consumidores conectados al queue. Sin embargo, el queue no enviará
automáticamente los mensajes que le lleguen a todos los consumidores, si no que son
estos últimos los que deben solicitarlos al queue. De manera adicional, solo un
consumidor consumirá cada mensaje publicado: el primero en solicitarlo. Cuando esto
ocurra, el mensaje se borrará del queue, y el resto de consumidores no será siquiera
consciente de la anterior existencia del mensaje. Por todo esto, el modelo p2p es de
tipo uno-a-uno (un productor, un consumidor).
En versiones anteriores a JMS 1.1 cada uno de estos modelos usaba su propio conjunto
de interfaces y clases para para el envio y recepción de mensajes. Desde la citada
versión de JMS, sin embargo, está disponible una API unificada que es válida para
ambos modelos de mensajería.
Ahora es el momento de volver al camino que dejamos dos secciones atrás, y seguir
con nuestros amados componentes MDB.
3.13 MESSAGE-DRIVEN BEANS: EL CICLO DE VIDA
Tal como vimos en la sección 3.10, los componentes MDB no mantienen estado entre
invocaciones (son stateless, pero no confundir con los componentes SLSB). Por tanto,
su ciclo de vida es similar a los SLSB, constando de dos estados:
Un MDB en el primer estado es aquel que no ha sido creada aún, y por tanto no existe
en memoria. Un MDB en el segundo estado representa una instancia que ha sido
instanciada e inicializada por el contenedor. Se llega a este estado cuando se inicia el
servidor (que puede decidir crear cierto número de instancias del MDB para procesar
mensajes), o cuando el número de instancias en el pool sea insuficiente para atender
todos los mensajes que se estén recibiendo. La especificación EJB no fuerza a que
exista un pool de MDB's, de manera que una implementación concreta de contenedor
EJB ppdría decidir crear una instancia cada vez que se reciba un mensaje (y eliminar
esta instancia al terminar de procesar el mensaje) en lugar de mantener un pool de
instancias ya preparadas. Este último aspecto, sea como sea, no nos afecta como
programadores (al diseñar un MDB) ni como clientes; la forma en que sea gestionada
nuestra aplicación es, a priori, responsabilidad exclusiva del contenedor.
Por último, el contenedor ejecutará, si existe, un método dentro del MDB anotado
con @PostConstruct (Post construcción). En este método podemos obtener recursos
adicionales necesarios para el MDB (como conexiones de red, etc), recursos que
permanecerán abiertos hasta la destrucción del MDB. Las reglas de declaración de los
métodos callback como @PostConstruct se vieron una y otra vez en el artículo
anterior.
Cuando el contenedor no necesita una instancia del MDB (ya sea porque decide reducir
el número de instancias en el pool, o porque se esta produciendo un shutdown del
servidor), se realiza una transición en sentido inverso: del estado preparado en pool al
estado no existe. Durante esta transición se ejecutará, si existe, un método anotado
con@PreDestroy (Pre destrucción), donde podemos liberar los recursos adquiridos
en @PostConstruct. Es importante tener presente que dentro del
método @PreDestroy todavía tenemos acceso al contexto del MDB (lo mismo es válido
para todos los Session Bean).
package es.davidmarco.ejb.mdb;
import javax.ejb.MessageDriven;
import javax.jms.Message;
import javax.jms.MessageListener;
@MessageDriven()
public class PrimerMDB implements MessageListener {
public void onMessage(Message message) {
// procesar el mensaje
}
}
@MessageDriven(activationConfig={
@ActivationConfigProperty(propertyName="destinationType",
propertyValue="javax.jms.Topic"),
@ActivationConfigProperty(propertyName="destination",
propertyValue="topic/MiTopic")})
public class PrimerMDB implements MessageListener {
// ...
}
@ActivationConfigProperty(propertyName="subscriptionDurability",
propertyValue="Durable")
Al igual que los componentes Session Bean, los componentes MDB funcionan dentro de
un contexto de ejecución. Y por tanto, al igual que los componentes Session Bean,
nuestros MDB pueden acceder a su contexto de ejecución si necesitan comunicarse
con el contenedor:
import javax.annotation.Resource;
import javax.ejb.MessageDriven;
import javax.ejb.MessageDrivenContext;
@MessageDriven(/*...*/)
public class PrimerMDB implements MessageListener {
@Resource
private MessageDrivenContext context;
// ...
}
Por otro lado, un MDB también puede referenciar un Session Bean para realizar el
procesamiento del mensaje (mediante la anotación @EJB, sección 2.5 del artículo
anterior), así como enviar él mismo sus propios mensajes mediante JMS (al final de la
próxima sección veremos un ejemplo de un MDB procesando mensajes, y a su vez
enviando nuevos mensajes que serán procesador por otro MDB).
El primer paso lógico es crear un canal virtual donde poder enviar mensajes (un topic
para modelos pub/sub o un queue para modelos p2p). En JBoss 6.0.0 Final (el servidor
de aplicaciones que instalamos en el anexo que acompaña este tutorial), el proveedor
JMS incorporado es HornetQ 2.1.2 Final. Para declarar un canal virtual de tipo topic,
edita el archivo llamado hornetq-jms.xml del
directorio server/default/deploy/hornetq/ de tu instalación de JBoss y añade lo
siguiente (default es la configuración por defecto al crear el servidor en Eclipse; si
seleccionaste otra configuración, modifica la ruta anterior a la que corresponda):
<configuration xmlns="urn:hornetq"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="urn:hornetq /schema/hornetq-jms.xsd">
<topic name="PrimerTopic">
<entry name="/topic/PrimerTopic" />
</topic>
</configuration>
En el archivo XML anterior hemos definido un canal virtual de tipo topic (mediante el
elemento <topic>) con nombrePrimerTopic, y lo hemos asociado a la dirección
JNDI /topic/PrimerTopic (es necesario reiniciar JBoss si se encuentra levantado). La
forma de configurar un canal virtual puede ser diferente entre distintos contenedores
(incluso entre distintas implementaciones de un mismo contenedor), por lo que si
estás usando un servidor de aplicaciones diferente a JBoss 6.0.0 Final o un proveedor
JMS diferente a HornetQ 2.1.2 Final, quizá necesites revisar la documentación
correspondiente para declarar el canal virtual.
Aunque este ejemplo se basará en un canal virtual de tipo topic, puedes declarar un
queue (para montar un modelo de mensajería p2p) de la siguiente manera:
<configuration xmlns="urn:hornetq"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="urn:hornetq /schema/hornetq-jms.xsd">
<queue name="PrimerQueue">
<entry name="/queue/PrimerQueue" />
</queue>
</configuration>
El siguiente paso lógico sería escribir un componente MDB que procese los mensajes
del topic. Para ello, necesitamos crear un proyecto EJB en Eclipse y declarar una clase
como la siguiente:
package es.davidmarco.ejb.mdb;
import javax.ejb.ActivationConfigProperty;
import javax.ejb.MessageDriven;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageListener;
import javax.jms.TextMessage;
@MessageDriven(activationConfig={
@ActivationConfigProperty(propertyName="destinationType",
propertyValue="javax.jms.Topic"),
@ActivationConfigProperty(propertyName="destination",
propertyValue="topic/PrimerTopic")})
public class PrimerMDB implements MessageListener {
@Override
public void onMessage(Message message) {
if(message instanceof TextMessage) {
try {
String contenidoDelMensaje =
((TextMessage)message).getText();
System.out.println("PrimerMDB ha procesado el mensaje: "
+ contenidoDelMensaje);
} catch (JMSException jmse) {
throw new RuntimeException("Error al procesar un
mensaje");
}
}
}
}
Es interesante explicar con un mínimo detalle las operaciones que se realizan dentro
de nuestro método onMessage. Este método requiere un parámetro de
tipo javax.jms.Message, que es una interface base de la cual extienden otras
interfaces más específicas. Una de esas subinterfaces es TextMessage, la cual provee
de métodos para trabajar con mensajes cuyo contenido es texto. Por tanto, este MDB
está diseñado para recibir mensajes desde el topic asociado, y procesarlos si son de
tipo TextMessage (en caso afirmativo, se imprime en el log del servidor el contenido
del mensaje). Si durante dicho procesamiento se produce un error, el MDB lanzará una
excepción. Lo ideal es que el topic asociado solo reciba este tipo de mensajes, y que
mensajes con otro tipo de contenido sean almacenados en otros canales virtuales (de
esta manera no se crearán instancias que finalmente no procesarán el mensaje).
Tras desplegar el proyecto EJB con nuestro primer componente MDB, el último paso
lógico sería crear un cliente JMS que produjera mensajes y los enviara al topic. Este
cliente podría ser, por ejemplo, un proyecto Java normal y corriente. Puesto que
necesitamos las librerias de JMS (las cuales son parte de EJB 3.x), debemos incluirlas
al crear nuestro proyecto. La forma más sencilla sería pulsando Next en la pantalla de
creación del proyecto Java en Eclipse, seleccionando la pestaña Libraries y
añadiendo las librerias del servidor JBoss:
Add Library > Server Runtime > Botón Next > JBoss 6.0 Runtime > Botón Finish >
Botón Finish
import java.util.Properties;
import javax.jms.Connection;
import javax.jms.ConnectionFactory;
import javax.jms.JMSException;
import javax.jms.MessageProducer;
import javax.jms.Session;
import javax.jms.TextMessage;
import javax.jms.Topic;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
conexion.close();
}
}
Ahora viene la parte interesante: creamos un objeto Topic que está asociado al canal
virtual que hemos declarado en el primer paso lógico de esta sección (mediante su
dirección JNDI), creamos una conexión con el broker mediante el objeto factoría,
iniciamos una nueva sesión dentro de la conexión recién creada, creamos un
objeto MessageProducerasociado al objeto Topic, iniciamos la conexión para poder
enviar un mensaje, creamos un mensaje de tipo texto, lo enviamos, y finalmente
cerramos la conexión.
Por último, veamos como hacer que un MDB sea también productor:
package es.davidmarco.ejb.mdb;
import javax.annotation.Resource;
import javax.ejb.ActivationConfigProperty;
import javax.ejb.MessageDriven;
import javax.ejb.MessageDrivenContext;
import javax.jms.Connection;
import javax.jms.ConnectionFactory;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageListener;
import javax.jms.MessageProducer;
import javax.jms.Session;
import javax.jms.TextMessage;
import javax.jms.Topic;
@MessageDriven(activationConfig={
@ActivationConfigProperty(propertyName="destinationType",
propertyValue="javax.jms.Topic"),
@ActivationConfigProperty(propertyName="destination",
propertyValue="topic/PrimerTopic")})
public class PrimerMDB implements MessageListener {
@Resource
private MessageDrivenContext contexto;
@Override
public void onMessage(Message message) {
if(message instanceof TextMessage) {
try {
String contenidoDelMensaje =
((TextMessage)message).getText();
System.out.println("PrimerMDB ha procesado el mensaje: "
+ contenidoDelMensaje);
conexion.close();
}
}
Ahora podemos declarar un segundo MDB que procese los mensajes del segundo
topic:
package es.davidmarco.ejb.mdb;
import javax.ejb.ActivationConfigProperty;
import javax.ejb.MessageDriven;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageListener;
import javax.jms.TextMessage;
@MessageDriven(activationConfig={
@ActivationConfigProperty(propertyName="destinationType",
propertyValue="javax.jms.Topic"),
@ActivationConfigProperty(propertyName="destination",
propertyValue="topic/SegundoTopic")})
public class SegundoMDB implements MessageListener {
@Override
public void onMessage(Message message) {
if(message instanceof TextMessage) {
try {
String contenidoDelMensaje =
((TextMessage)message).getText();
System.out.println("SegundoMDB ha procesado el mensaje: "
+ contenidoDelMensaje);
} catch (JMSException jmse) {
throw new RuntimeException("Error al procesar un
mensaje");
}
}
}
}
3.16 RESUMEN
En este tercer artículo del tutorial de EJB hemos terminado de ver los componentes de
lado del servidor, además de algunas características interesantes que son nuevas en la
especificación EJB 3.1: llamadas asíncronas y vista sin interface (ambas son aplicables
a componentes Session Bean, pero no a Message-Driven Beans).
En los artículos anteriores del tutorial de introducción a EJB 3.1, hemos visto como
declarar y trabajar con componentes de lado del servidor (Session Beans y Message-
Driven Beans). El último componente que nos queda por ver es Entity Beans (EB -
Beans de Entidad; a partir de ahora nos referiremos a ellos como entidades).
Aunque las entidades se consideran componentes EJB, hasta la version JavaEE 1.4
pertenecían a una especificación independiente llamada Java Persistence API (JPA -
API de Persistencia en Java). Fue a partir de la versión 5 de JavaEE que la
especificación EJB absorvió a la especificación JPA. Sin embargo, podemos trabajar
con entidades en una aplicación no-EJB, aunque, tras bambalinas, se seguirán
ejecutando dentro de un contenedor EJB. Desde ahora usaremos el término aplicación
JPA para referirnos a aplicaciones que realizan persistencia, y el término aplicación
EJB cuando exista integración de ambas tecnologías.
- Gestionada (Attached)
- No gestionada (Detached)
</persistence-unit>
</persistence>
Podemos definir más de una unidad de persistencia por aplicación, declarando cada
una de ellas mediante el elemento XML <persistence-unit>. Cada unidad de
persistencia debe seguir estas dos reglas:
- Debe proporcionar un nombre (identidad) a través del cual pueda ser llamado
- Debe conectar a una sola fuente de datos (data source)
Dependiendo del tipo de paquete que estemos construyendo (EAR, JAR, etc), el
archivo persistence.xml deberá encontrarse en una localización u otra. En la sección
4.6 veremos un ejemplo completo de este archivo, así como la información necesaria
para su correcto despliegue dentro de una aplicación EJB.
import javax.ejb.Stateless;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
@Stateless
public class MiSlsb {
@PersistenceContext(unitName="introduccionEJB")
private EntityManager em;
import javax.persistence.PersistenceContextType;
// ...
@PersistenceContext(unitName="introduccionEJB",
type=PersistenceContextType.EXTENDED)
private EntityManager em;
En lo que se refiere a este tutorial, la integración de EJB con JPA termina aquí. Por
tanto, podemos pasar a ver un ejemplo donde conectaremos todas las piezas para
realizar persistencia mediante EJB 3.1.
Nuestro primer paso va a ser definir una fuente de datos que conectará nuestro
contenedor EJB con nuestra base de datos. Siguiendo el entorno de desarrollo
configurado en el anexo que acompaña este tutorial, escribimos un archivo
llamado derby-ds.xml y lo guardamos en el directorio server\default\deploy de
nuestra instalación de JBoss:
En el archivo XML anterior definimos una fuente de datos (data source) limitado a
transacciones locales, esto es, dentro del propio contenedor (existe otro ambito de
ejecución de una transacción llamado extendido, capaz de realizar su trabajo a través
de múltiples contenedores). A esta fuente de datos le hemos dado un nombre JNDI
desde la que poder invocarla, así como los parámetros de conexión con nuestra base
de datos subyacente (url, usuario, y password). Si JBoss 6.0.0 Final está arrancado,
nada más guardar el archivo anterior la fuente de datos será activada y se producirá la
conexión con Derby, así que deberás tener la base de datos iniciada o se producirá un
error; nosotros vamos a considerar que en este preciso momento tanto JBoss como
Derby están parados.
La operación anterior añade a nuestro proyecto las librerias del servidor embebido
Derby, de manera que podamos conectar con él. A continuación levantamos la base de
datos haciendo click con el botón derecho sobre el nombre del proyecto EJB en la
pestaña Proyect Explorer y seleccionando:
Ahora vamos a crear un componente SLSB remoto, de manera que podamos llamarlo
desde un cliente Java normal. Como recordarás, un componente remoto requiere de
manera obligatoria implementar una interface:
package es.davidmarco.ejb.slsb;
import es.davidmarco.ejb.entidad.Cuenta;
package es.davidmarco.ejb.slsb;
import javax.ejb.Remote;
import javax.ejb.Stateless;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import es.davidmarco.ejb.entidad.Cuenta;
@Remote
@Stateless
public class OperacionesConCuentasRemote implements OperacionesConCuentas
{
@PersistenceContext(unitName="introduccionEJB")
private EntityManager em;
@Override
public void crearCuenta(Cuenta nuevaCuenta) {
em.persist(nuevaCuenta);
}
@Override
public Cuenta obtenerCuenta(Long id) {
return em.find(Cuenta.class, id);
}
@Override
public void borrarCuenta(Cuenta cuenta) {
em.remove(cuenta);
}
}
El componente anterior es extremadamente sencillo: en él se inyecta una instancia
de EntityManager, la cual es usada en los métodos del Session Bean para realizar las
tareas de persistencia. Estos métodos usan como parámetros o tipos de retorno
instancias de la clase Cuenta (la cual define cuentas bancarias) y que será nuestra
entidad:
package es.davidmarco.ejb.entidad;
import java.io.Serializable;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
@Entity
public class Cuenta implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue
private Long id;
private String numeroDeCuenta;
private String nombreDelTitular;
private Double saldo;
Ahora que tenemos un componente EJB y una entidad, vamos a crear la unidad de
persistencía asociada a la fuente de datos y nuestra entidad. Crea un archivo
llamado persistence.xml en el directorio META-INF del proyecto EJB:
<jta-data-source>java:/DerbyDS</jta-data-source>
<class>es.davidmarco.ejb.entidad.Cuenta</class>
<properties>
<property name="hibernate.dialect"
value="org.hibernate.dialect.DerbyDialect" />
<property name="hibernate.hbm2ddl.auto" value="update"/>
</properties>
</persistence-unit>
</persistence>
El último paso necesario para probar nuestro ejemplo es crear un cliente Java que
pasará una instancia ya inicializada de la entidad Cliente al componente EJB. Para
ello, y para mantener las cosas sencillas, creamos un proyecto Java en Eclipse y le
añadimos las librerias del proyecto EJB haciendo click con el botón derecho del ratón
en el nombre del proyecto Java y seleccionando:
package es.davidmarco.ejb.cliente;
import java.util.Properties;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import es.davidmarco.ejb.entidad.Cuenta;
import es.davidmarco.ejb.slsb.OperacionesConCuentas;
OperacionesConCuentas occ =
(OperacionesConCuentas)context.lookup(JNDI_BEAN);
occ.crearCuenta(cuenta);
}
}
connect 'jdbc:derby://localhost:1527/introduccionEJB;'
Esto demuestra que nuestra entidad (un POJO Java) ha sido almacenado en una base
de datos relacional (tablas y columnas) de manera transparente para nosotros. Misión
cumplida. Si deseáramos realizar el proceso inverso (obtener un objeto Java desde la
información almacenada en la base de datos):
// ...
OperacionesConCuentas occ =
(OperacionesConCuentas)context.lookup(JNDI_BEAN);
// occ.crearCuenta(cuenta);
Cuenta cuenta = occ.obtenerCuenta(1L);
System.out.println("Titular de la cuenta "
+ cuenta.getNumeroDeCuenta()
+ " con saldo "
+ cuenta.getSaldo()
+ ": "
+ cuenta.getNombreDelTitular());
}
}
En el ejemplo anterior, hemos comentado las lineas que crean y persisten un cliente
(de otra manera se insertará un nuevo registro con los misma información en la base
de datos pero con ID con valor 2), y en su lugar hemos llamado al
método obtenerCuenta() del SLSB, para así obtener una cuenta desde la base de
datos en base a su ID. Si una cuenta con ese ID no existe, obtendremos como
respuesta un valor null. Te invito a que, a modo de práctica, elimines de la base de
datos la cuenta que hemos creado llamando al método borrarCuenta() del SLSB; para
verificarlo, una vez hayas ejecutado esta operación vuelve a realizar la consulta SQL
contra la base de datos a través de la consola de ij:
4.7 RESUMEN
Como hemos visto en este artículo, podemos realizar persistencia de manera
extremadamente sencilla en nuestras aplicaciones EJB. Las entidades son simples
POJO's, la configuración con la base de datos se configura mediante archivos XML, y
en nuestros componentes EJB solo tenemos que inyectar una unidad de persistencia y
ejecutar sus métodos.
En este punto ya hemos visto todos los componentes EJB (Session Beans, Message-
Driven Beans, y Entities). Los dos próximos artículos estarán dedicados a los diversos
servicios que ofrece el contenedor, servicios que nuestros componentes pueden usar
para realizar taréas más complejas (y que son necesarias en aplicaciones reales).