Está en la página 1de 59

Introducción a EJB 3.

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:

- 1. Introducción a EJB y primer ejemplo


- 2. Stateless Session Beans y Stateful Session Beans
- 3. Singletons y Message-Driven Beans
- 4. Persistencia
- 5. Servicios que ofrece el contenedor (1ª parte)
- 6. Servicios que ofrece el contenedor (2ª parte)

Con todo esto dicho, ¡comencemos!

1.1 ¿QUE SON LOS ENTERPRISE JAVABEANS?


EJB (Enterprise JavaBeans) es un modelo de programación que nos permite construir
aplicaciones Java mediante objetos ligeros (como POJO's). Cuando construimos una
aplicación, son muchas las responsabilidades que se deben tener en cuenta, como la
seguridad, transaccionalidad, concurrencia, etc. El estandar EJB nos permite
centrarnos en el código de la lógica de negocio del problema que deseamos solucionar
y deja el resto de responsabilidades al contenedor de aplicaciones donde se ejecutará
la aplicación.

1.2 EL CONTENEDOR DE APLICACIONES


Un contenedor de aplicaciones es un entorno (en si mismo no es más que una
aplicación) que provee los servicios comunes a la aplicacion que deseamos ejecutar,
gestionándolos por nosotros. Dichos servicios incluyen la
creación/mantenimiento/destrucción de nuestros objetos de negocio, así como los
servicios mencionados en el punto anterior, entre otros. Aunque el contenedor es
responsable de la gestión y uso de dichos recursos/servicios, podemos interacturar
con él para que nuestra aplicación haga uso de los servicios que se ofrecen
(normalmente mediante metadados, como se verá a lo largo del tutorial).

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).

1.3 LA ESPECIFICACIÓN EJB 3.1


La especificación EJB 3.1 es parte de la plataforma JavaEE 6, desarrollada y mantenida
por Sun Microsystems (ahora parte de Oracle Corporation). JavaEE 6 provee diversas
API's para la construcción de aplicaciones empresariales, entre ellas EJB, JPA, JMS, y
JAX-WS. Cada una de ellas se centra en un area específica, resolviendo así problemas
concretos. Además, cada API/especificación está preparada para funcionar en
compañia de las demás de forma nativa, y por tanto en su conjunto son una solución
perfectamente válida para desarrollar una aplicación end-to-end (de principio a fin).

Desde la versión 3.0, EJB no impone ninguna restricción ni obligación a nuestros


objetos de negocio de implementar una API en concreto. Dicho de otro modo, podemos
escribir dichos objetos de negocio usando POJO's, facilitando entre otras cosas la
reutilización de componentes y la tarea de testearlos.

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:

- Escribimos un test que define que queremos hacer.


- Ejecutamos el test y este falla (puesto que aún no hay lógica de negocio, o lo que
es lo mismo, como queremos hacerlo).
- Escribimos la lógica de negocio que hace pasar el test (la solución más simple
posible).
- Mejoramos la lógica de negocio gradualmente, ejecutando el test después de cada
mejora para verificar que no hemos roto nada.

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).

1.4 SESSION BEANS


Los Session Beans (Beans de Sesión) son los componentes que contienen la lógica de
negocio que requieren los clientes de nuestra aplicación. Son accedidos a través de un
proxy (también llamado vista, término que utilizaré en adelante) tras realizar una
solicitud al contenedor. Tras dicha solicitud, el cliente obtiene una vista del Session
Bean, pero no el Session Bean real. Esto permite al contenedor realizar ciertas
operaciones sobre el Session Bean real de forma transparente para el cliente (como
gestionar su ciclo de vida, solicitar una instancia a otro contenedor trabajando en
paralelo, etc).

Los componentes Session Bean pueden ser de tres tipos:

- Stateless Session Beans


- Stateful Session Beans
- Singletons

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.

1.5 MESSAGE-DRIVEN BEANS


Message-Driven Beans (MDB - Beans Dirigidos por Mensajes) son componentes de tipo
listener que actuan de forma asíncrona. Su misión es la de consumir mensajes (por
ejemplo eventos que se producen en la aplicación), los cuales pueden gestionar
directamente o enviar (derivar) a otro componente. Los MDB actuan sobre un
proveedor de mensajería, por ejemplo Java Messaging System (JMS es además
soportado de forma implícita por la especificacion EJB).

Al igual que los Stateless Session Beans, los Message-Driven Beans no mantienen
estado entre invocaciones.

1.6 ENTITY BEANS


Los Entity Beans (EB - Beans de Entidad) son representaciones de datos almacenados
en una base de datos, siempre en forma de POJO's. El encargado de gestionar los EB
es EntityManager, un servicio que es suministrado por el contenedor y que está
incluido en la especificación Java Persistence API (JPA - API de Persistencia en Java).
JPA es parte de EJB desde la versión 3.0 de esta última. Para saber más sobre JPA,
puedes visitar un tutorial anterior publicado en esta misma web en la siguiente
dirección.
Al contrario que los Session Beans y los Message-Driven Beans, los Entity Beans no
son componentes del lado del servidor. En otras palabras, no trabajamos con una vista
del componente, si no con el componente real.

1.6 EJB Y TEST-DRIVEN DEVELOPMENT


Para terminar este primer artículo, dejemos de lado la teoría y escribamos una sencilla
aplicación EJB para ir abriendo el apetito. Para poder seguir este y futuros ejemplos,
debes tener en marcha un entorno compatible con EJB 3.1 (aunque la mayoría de los
ejemplos, incluido este, funcionarán en un contenedor compatible con EJB 3.0). Por
otro lado, todo el código se ajusta al estandard EJB 3.1 (no se usarán extensiones
exclusivas de un contenedor concreto). Sin embargo, ten presente que las indicaciones
relativas a la creación del proyecto, despliegue, y ejecución de los ejemplos estarán
condicionadas por el entorno concreto que se ha puesto en marcha mediante
elanexo que acompaña este tutorial; si tu entorno es diferente, ciertas acciones (como
los opciones de menús a ejecutar en tu IDE) serán otras.

Como se ha indicado en el punto 1.3, este ejemplo de desarrollará de manera puntual


mediante Test-Driven Development. En los próximos artículos solo se mostrará código
EJB, que es al fin y al cabo el tema a tratar en este turorial. Así mismo, los pasos para
crear un proyecto o como desplegarlo en el contenedor EJB se omitirán en artículos
posteriores.

Para comenzar inicia Eclipse (si aún no lo has hecho) y crea un nuevo proyecto EJB:

File > New > EJB Project

Dale un nombre al nuevo proyecto y asegúrate tanto de seleccionar en el desplegable


'Target runtime' un contenedor compatible con EJB como de seleccionar la versión 3.1
en el desplegable 'EJB module version'. Haz click en el botón 'Finish' para crear el
proyecto.

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:

New > Source Folder

Introduce el nombre del directorio en el campo 'Folder name' (utiliza un nombre


descriptivo, como 'tests') y haz click en el botón 'Finish'. Si expandes el proyecto EJB
(con la flecha negra que hay a la izquierda del nombre) verás que el nuevo directorio
se ha creado correctamente. Ahora vamos a crear la clase donde escribiremos los tests
para nuestro EJB. Haz click con el botón derecho sobre el directorio de tests y
selecciona:

New > Other

En la ventana que te aparecerá selecciona:

Java > JUnit > JUnit Test Case

En la ventana de creación de un test de JUnit selecciona la opción 'New JUnit 4 test',


introduce el nombre del paquete donde deseas alojar la clase en el campo 'Package'
(muy recomendado) y escribe un nombre para la clase de tests. En mi caso, el nombre
del paquete será 'es.davidmarco.ejb.slsb' y el nombre de la clase de tests
'PrimerEJBTest'. Haz click en el botón 'Finish'; si es el primer tests que escribes para el
proyecto (como es nuestro caso) aparecerá una ventana donde Eclipse nos informa
que la librería JUnit no está incluida en el classpath. Seleccionamos la opción 'Perform
the following action: Add JUnit4 library to the build path' (Realizar la siguiente acción:
añadir la libreria JUnit 4 al path de construcción) y haz click en el botón 'OK'. Hecho
esto, ya podemos escribir nuestro primer (y de momento único) test:

package es.davidmarco.ejb.slsb;

import org.junit.Test;
import static org.junit.Assert.*;

public class PrimerEJBTest {


@Test
public void testSaluda() {
PrimerEJB ejb = new PrimerEJB();
assertEquals("Hola usuario", ejb.saluda("usuario"));
}
}

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:

New > Class

Introduce en el campo 'Package' el nombre del paquete donde vamos a almacenar la


clase de negocio (en mi caso 'es.davidmarco.ejb.slsb') y en el campo 'Name' el nombre
de la clase de negocio; para este último caso es conveniente usar el nombre de la clase
de tests sin el sufijo 'Test' (en mi caso 'PrimerEJB') de manera que podamos asociar
visualmente en el explorador del IDE cada clase de negocio ('Xxx') con su clase de
tests ('XxxTest'). Haz click en el botón 'Finish' para crear la clase de negocio y añade
la lógica de negocio (comienza con la solución mas simple posible):

package es.davidmarco.ejb.slsb;

public class PrimerEJB {


public String saluda(String nombre) {
return null;
}
}

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;

public class PrimerEJB {


public String saluda(String nombre) {
return "Hola usuario";
}
}

Si ahora ejecutamos el test, veremos en la pestaña de JUnit que la barra es ahora de


color verde, lo cual indica que todos los tests se han ejecutado sin fallos (la ventana
'Failure Trace' esta ahora vacía, evidentemente). Ahora decidimos que, cuando un
cliente pase un argumento de tipo null a nuestra función, esta deberá devolver un
saludo por defecto. Renombremos el nombre del primer test que hemos escrito y
escribamos un segundo test que pruebe esta nueva condición (desde ahora y hasta el
final de esta sección omitiré en ambas clases las sentencias package eimport por
claridad):

public class PrimerEJBTest {


@Test
public void testSaludaConNombre() {
PrimerEJB ejb = new PrimerEJB();
assertEquals("Hola usuario", ejb.saluda("usuario"));
}

@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";
}

return "Hola usuario";


}
}

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:

public class PrimerEJBTest {


@Test
public void testSaludaConNombre() {
PrimerEJB ejb = new PrimerEJB();
assertEquals("Hola usuario", ejb.saluda("usuario"));
}

@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:

public class PrimerEJB {


public String saluda(String nombre) {
if(nombre == null) {
return "Hola desconocido";
}

return "Hola " + nombre;


}
}
Ahora todos los tests pasan. Aunque aún nos quedaría la tarea de refactorizar los tres
métodos de tests (hay código redundante en todos ellos) y tal vez añadir algún test
más (o eliminar...), vamos a dejar las cosas aquí. TDD es un tema demasiado amplio y
complejo que está fuera del propósito de este tutorial. Aunque este ejemplo ha sido
extremadamente simple/tonto/llamalo-como-quieras, nos ha servido para demostrar
lo facil que es diseñar un componente EJB paso a paso y libre de errores. Nadie quiere
software que falle, y por tanto debes tomarte la tarea de testear el código que escribes
muy en serio. Test-Driven Development es una manera muy sencilla y divertida de
diseñar software de calidad.

1.7 DESPLEGANDO NUESTRO PRIMER EJB


Hasta ahora hemos tratado la clase PrimerEJB como si fuera un componente EJB. Pero
lo cierto es que no es así (en otras palabras, te he mentido, aunque espero que puedas
perdonarme...). Para que nuestro POJO sea reconocido por nuestro contenedor como
un componente EJB verdadero tenemos que decirle que lo es:

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";
}

return "Hola " + nombre;


}
}

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;

public interface MiInterfaceEJB {


public String saluda(String nombre);
}

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.

Ahora ya podemos desplegar nuestra primera aplicación EJB en el contenedor. En la


pestaña 'Project Explorer' haz click con el botón derecho sobre el nombre del proyecto,
y selecciona:

Run As > Run on Server

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:

PrimerEJB/remote - EJB3.x Default Remote Business Interface


PrimerEJB/remote-es.davidmarco.ejb.slsb.InterfaceEJB - EJB3.x Remote Business
Interface

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:

Window > Show Views > Servers

Si miramos a la recién abierta pestaña 'Servers' veremos la instancia de JBoss


asociada a nuestro proyecto, y junto a ella una flecha. Pinchamos en esta flecha para
expandir el servidor y veremos nuestro proyecto EJB. Hacemos click con el botón
derecho sobre el nombre del proyecto y seleccionamos 'Full Publish'. En unos
segundos nuestro proyecto estará re-desplegado (puedes ver el proceso en la pestaña
'Console').

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'.

1.8 EL CLIENTE JAVA


Ahora es el momento de escribir el cliente Java, el cual va a hacer una solicitud al
contenedor mediante JNDI para obtener el Stateless Session Bean que hemos creado
en la sección 1.6 y desplegado en la sección 1.7. Con esto veremos como el contenedor
gestiona todo el ciclo de vida de una aplicación EJB así como de sus componentes,
mientras los clientes solo tienen que preocuparse de solicitar el componente que
necesiten y usarlo. Lo primero es crear un nuevo proyecto Java:

File > New > Other

Seleccionamos 'Java Project', hacemos click en 'Next', le damos un nombre al proyecto


y hacemos click en 'Finish'. En la pestaña 'Project Explorer' expandimos el proyecto
pinchando en la flecha que aparece a la izquierda de su nombre y en la carpeta 'src'
creamos un nuevo paquete (muy recomendado) haciendo click con el botón derecho y
seleccionando:

New > Package

Le damos un nombre al paquete (en mi caso 'es.davidmarco.ejb.cliente') y hacemos


click en 'Finish'. Volvemos a hacer click con el botón derecho sobre el paquete recién
creado y seleccionamos:

New > Class

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:

Build Path > Configure Build Path


En la ventana que se abrirá seleccionamos la pestaña 'Projects', hacemos click en el
botón 'Add', y marcamos la casilla correspondiente al proyecto donde está la
aplicación EJB que hemos desplegado (y que, repito, contiene todas las librerias que
necesita el cliente). Para finalizar, hacemos click en el botón 'OK', y de nuevo hacemos
click en el botón 'OK'. El código del cliente es el siguiente:

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;

public class Cliente {

private static final String JNDI_PRIMER_EJB = "PrimerEJB/remote";

public static void main(String[] args) throws NamingException {


Properties properties = new Properties();
properties.put("java.naming.factory.initial",
"org.jnp.interfaces.NamingContextFactory");
properties.put("java.naming.factory.url.pkgs",
"org.jboss.naming:org.jnp.interfaces");
properties.put("java.naming.provider.url",
"jnp://localhost:1099");
Context context = new InitialContext(properties);

MiInterfaceEJB bean = (MiInterfaceEJB)


context.lookup(JNDI_PRIMER_EJB);
String respuesta = bean.saluda("Cliente Java");
System.out.println(respuesta);
}
}

El cliente contiene una constante llamada JNDI_PRIMER_EJB a la que le hemos dado el


valor de la referencia JNDI a nuestro componente (recuerda que este valor nos los dio
el contenedor cuando desplegó la aplicación, como vimos al final de la sección 1.7).
Dentro del método main() creamos un objeto de propiedades, introducimos los valores
necesarios para acceder al contexto del contenedor EJB, y creamos dicho contexto
mediante un objeto InitialContexty el objeto de propiedades que acabamos de crear
y configurar.

A continuación viene lo realmente interesante: no obtenemos un objeto de


tipo InterfaceEJB mediante el constructornew (como haríamos en una aplicación Java
normal), si no que se lo solicitamos al contenedor EJB a traves del
método lookup() del contexto que acabamos de crear. A este método le hemos
pasado el nombre de la referencia JNDI del componente que queremos obtener.
Recuerda que cuando solicitamos al contenedor un componente de tipo Session Bean
(ya sea Stateless, Stateful, o Singleton) lo que obtenemos no es una instancia del
componente EJB (en nuestro caso PrimerEJB), si no una vista (un objeto proxy) que
sabe como alcanzar el objeto real dentro del contenedor.

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:

Run As > Java Application

En la pestaña 'Console' aparecera el resultado de la ejecución del cliente. A modo


de experimento puedes cambiar la invocación al método saluda(), pasarle un
valor null en lugar de una cadena de texto, volver a ejecutar el cliente, y ver la
respuesta del componente EJB.

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.

El uso de XML para añadir metadatos a nuestro código es la forma heredada de


versiones anteriores de la especificación JavaEE. Para ello, debemos incluir un archivo
llamado ejb-jar.xml dentro del directorio META-INF del archivo jar/ear a desplegar.
La ventaja del uso de XML como fuente de metadatos reside en que no es necesario
recompilar nuestro código para cambiar el comportamiento de nuestros componentes,
con todas las ventajas que esto puede suponer una vez que un proyecto se encuentra
en producción. De manera adicional, al encontrarse los metadatos en un archivo
externo, nuestro código Java no contiene ningún tipo de información relativa a la
especificación EJB, y por tanto es más portable. Por contra, además de tener que
mantener dos fuentes de información a la vez (el código Java y el archivo XML), el
propio archivo XML puede ser dificil de mantener y entender cuando alcanza cierta
longitud y complejidad. En este tutorial no se verán ejemplos de metadados en XML
por motivos de simplicidad, aunque si deseas ampliar información puedes visitar el
capítulo 19 de la especificación EJB 3.1, la cual puedes descargar desde la siguiente
dirección.
Por último, debes tener muy presente que los metadatos en formato XML
sobreescriben cualquier comportamiento ya expresado mediante anotaciones, siempre
que ambos hagan referencia a un mismo componente. Por ello, una combinación de
ambos tipos de metadatos sería perfectamente legal, aunque salvo honrosas
excepciones, poco recomendable (puede inducir a la confusión, y por tanto a cometer
errores).

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.

El comportamiento de multitud de aspectos del pool es configurable, aunque en este


tutorial no necesitamos hacerlo en ningún momento. Los detalles concretos del
funcionamiento del pool es algo que tampoco necesitamos saber de antemano para
seguir el tutorial; cuando se necesite comprender un aspecto concreto de dicho
funcionamiento, se explicará en la sección correspondiente.

2.3 LOCAL VS REMOTO


Como se explicó muy brevemente en el primer artículo del tutorial, un Session Bean
puede ser declarado de tipo local o remoto. Un Session Bean declarado como local
estará destinado a servir solicitudes de otros componentes dentro de la misma Java
Virtual Machine (JVM - Máquina Virtual Java) donde está desplegado (dicho con otras
palabras, dentro del contenedor donde se ejecuta la aplicación). El contenedor pasará
la referencia que apunta al objeto en cuestión al cliente, pues dentro de la misma JVM
está referencia es válida. Por contra, un Session Bean declarado como remoto está
destinado a servir peticiones de clientes externos al propio contenedor (como vimos
en el ejemplo al final del artículo anterior). En este caso, lo que se pasa es una copia
del objeto en cuestión, pues para una JVM externa no tendrá ningun sentido una
referencia a un objeto que no se encuentre en ella misma. Recuerda que esa copia no
es una copia del componente EJB (una instancia del POJO desplegado en el
contenedor), si no una vista/proxy que sabe como acceder a través de una red al
objeto real.

La especificación EJB no permite que la interface (en el sentido de contrato) de un


Session Bean sea declarada local y remota al mismo tiempo. Por tanto, lo siguiente no
es válido y producirá un error al ser desplegado:

@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
}

En el ejemplo anterior, declaramos una interface local y otra remota, y las


implementamos en un Session Bean de tipo Stateless. De esta manera, el Session Bean
podrá servir solicitudes de ambos tipos (cada una a través de la interface
correspondiente). Otra forma de expresar lo mismo sería de la siguiente manera:

public interface MiBeanLocalInterface {}

public interface MiBeanRemoteInterface {}

@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).

Por supuesto también podemos aprovechar las características de polimorfismo y


herencia en Java para crear nuestros componentes:

public interface MiInterfaceBase {


// contrato general para todas las interfaces que hereden de esta
}

public interface MiInterfaceLocal extends MiInterfaceBase {


// contrato concreto para acceso local
}

public interface InterfaceRemote extends MiInterfaceBase {


// contrato concreto para acceso remoto
}

public abstract class MiBeanBase implements MiInterfaceBase {


// implementación general para todas las clases que hereden de esta
}

@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).

SessionContext (Contexto de Sesión) es una interface que implementa EJBContext,


añadiendo métodos que permiten el uso de servicios adicionales. A través de él
podemos, por ejemplo, obtener una referencia al Session Bean actual para poder
pasarla a otro Session Bean como argumento de un método; esto es necesario, pues el
contenedor no permite pasar el Session Bean actual usando una referencia de
tipo this (los detalles de porqué esto es así no son necesarios para entender el
material, y por tanto los omito). Podemos acceder al contexto de sesión asociado a la
instancia del componente actual mediante inyección de dependencia (asunto que se
explicará en la sección 2.5):

@Local
@Stateless
public class MiBean {
@Resource
private SessionContext contexto;

// ...
}

Mediante la anotación @Resource (Recurso) indicamos al contenedor que debe


inyectar el contexto de sesión asociado a dicha instancia en la
variable contexto (cuando veamos el ciclo de vida de los componentes EJB se verá
cómo y cuándo se realiza esta operación). Otra manera de acceder al contexto de
sesión es usando la misma anotación, pero sobre un método setter que siga el
estandar JavaBean (no confundir con Enterprise JavaBean):

@Local
@Stateless
public class MiBean {
private SessionContext contexto;

@Resource
public void setContexto(SessionContext contexto) {
this.contexto = contexto;
}

// ...
}

Puesto que SessionContext extiende EJBContext, podemos acceder a ambas


interfaces desde la primera. Es muy importante que tengas presente que ciertos
métodos en EJBContext están marcados como deprecated, y además lanzarán una
excepción de tipo RuntimeException si son invocados. Te recomiendo que, por este
motivo, consultes la API de EJBContext en la siguiente dirección.

2.5 INYECCIÓN DE DEPENDENCIAS


La inyección de dependencias (Dependency Injection) es un proceso por el cual el
contenedor puede inyectar en un componente recursos que son necesarios. Un
ejemplo de inyección de dependencia lo vimos en la sección anterior, donde usamos la
anotación @Resource para inyectar una instancia de SessionContext en un SLSB. Otro
ejemplo de inyección de dependencia surje cuando uno de nuestros componentes
necesita de otro componente:

@Stateless
public class UnComponente {
private OtroComponente dependencia;

public String metodo() {


return dependencia.otroMetodo();
}
}

En el ejemplo anterior, el Session Bean UnComponente requiere otro Session Bean de


tipo OtroComponente. Puesto que, como ya se ha explicado, el contenedor EJB
gestiona el ciclo de vida de todos los componentes (toda la aplicación EJB funciona en
un entorno gestionado), una instanciación del tipo new OtroComponente() sería
incorrecta (esta instancia no tendría, por ejemplo, un contexto de sesión asociado,
pues ha sido inicializada manualmente). La solución consiste en informar al
contenedor de dicha dependencia mediante la anotación @EJB, dejando así en sus
manos esta tarea:

@Local
@Stateless(name="otroComponente")
public class OtroComponente {
public String otroMetodo() {
// ...
}
}
@Stateless
public class UnComponente {
@EJB(beanName="otroComponente")
private OtroComponente dependencia;

public String metodo() {


return dependencia.otroMetodo();
}
}

Gracias a la anotación @EJB el contenedor sabe que componente instanciar, inicializar,


y finalmente inyectar en la variable dependencia. En el ejemplo anterior, se registra
un Session Bean con nombre otroComponente (gracias
a@Stateless(name="otroComponente")), el cual puede ser accedido mediante
inyección de dependencia gracias a@EJB(beanName="otroComponente").

La inyección de dependencias es una poderosa carecterística de EJB que libera al


programador de muchas responsabilidades, ademas de resultar en código poco
acoplado y apto para unit testing (entre otras buenas cualidades). Puedes encontrar
más información sobre la anotación @EJB en la aquí y sobre la
anotación @Resource aquí(ambas páginas en inglés).

2.6 COMPONENTES DEL LADO DEL SERVIDOR


La primera pregunta que nos debemos hacer es: ¿Que es un componente del lado del
servidor?. No es ni más ni menos que un componente que es gestionado de manera
integra por el contenedor. Esto es, el cliente del componente no interacciona con el
componente en si, si no con una representación (referencia/proxy/vista) del
componente real. Los componentes del lado del servidor son dos:

- Todos los Session Bean (Stateless, Stateful, y Singleton)


- Message-Driven Beans

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).

2.7 STATELESS SESSION BEANS: CONCEPTOS BÁSICOS


Como vimos brevemente en el primer artículo del tutorial, los Stateless Session Bean
(SLSB - Session Bean Sin Estado) son aquellos que no mantienen estado entre
diferentes invocaciones de sus métodos. Podriamos considerar cada método dentro de
un SLSB como un servicio en si mismo, que realiza una tarea y devuelve un resultado
(puede no hacerlo) sin depender de invocaciones anteriores o posteriores sobre él
mismo o sobre otro método. Debido a este comportamiento, el contenedor puede
utilizar un número relativamente bajo de SLSB's para servir llamadas de muchos
clientes (ya que ninguno de estos clientes estará asociado a un SLSB en concreto), y
por ello son muy eficientes. El hecho de no mantener un estado entre invocaciones los
hace aún más eficientes.

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).

2.8 STATELESS SESSION BEANS: EL CICLO DE VIDA


El ciclo de vida de un componente describe los distintos estados (gestionados
íntegramente por el contenedor) por los que puede pasar cada instancia del
componente desde que es creada hasta que es destruida. Para los SLSB, este ciclo de
vida es muy simple y consta únicamente de dos estados (incluyo el término original en
inglés):

- No existe (Does not exists)


- Preparado en pool (Method-ready pool)

El primer estado, no existe, es autoexplicativo: la instancia del SLSB no ha sido creada


aún. El segundo estado,Preparado en pool, representa una instancia del SLSB que ha
sido instanciada y construida por el contenedor, y se encuentra en el pool lista para
recibir invocaciones por parte de un cliente (cuando esta invocación sucede, la
instancia es extraida el pool y asociada a una referencia que es pasada al cliente). A
este estado se llega durante el arranque del contenedor (el pool es poblado con cierto
número de instancias), y cuando no existan suficientes instancias en el pool para
servir llamadas de clientes (y por tanto se deban crear más). En resumen, siempre que
una nueva instancia del SLSB sea creada.

Durante la transición entre el primer estado y el segundo, el contenedor realizará tres


operaciones (en este orden):

- Instanciación del SLSB


- Inyección de cualquier recurso necesario y de dependencias
- Ejecución de un método dentro del SLSB marcado con la
anotación @PostConstruct, si existe

La instanciación del SLSB se lleva a cabo mediante reflexión, a traves


de Class.newInstance(). Por tanto, el SLSB debe tener un constructor por defecto
(sin argumentos), ya sea de forma implícita o explícita.
La inyección de recursos y de dependencias se vio en las secciones 2.4 y 2.5, donde se
utilizaron las anotaciones@Resource y @EJB para tales efectos. Estos y otros recursos
son inyectados en el SLSB de forma automática por el contenedor durante esta
operación. De manera adicional, cada vez que un SLSB sea invocado por un nuevo
cliente, todos los recursos son inyectados de nuevo.

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).

2.9 STATELESS SESSION BEANS: UN SENCILLO EJEMPLO


Supongamos que estamos desarrollando una aplicación de banca, y entre otras cosas
necesitamos escribir código que represente los conceptos de ingreso, retirada, y
transferencia de efectivo. Estos tres conceptos son operaciones que no requieren
mantener un estado dentro de la aplicación (esto es, una vez invocadas y terminadas
no dependen de otras operaciones) y por tanto son excelentes candidatas para ser
representadas mediante un Session Bean de tipo Stateless. Como ya hemos visto en
multitud de ejemplos, para declarar un Session Bean de tipo Stateless utilizamos la
anotación @Stateless (se omiten otras anotaciones como @Remote o @Local, así como
todo código no estrictamente necesario para explicar el tema tratado; desde este
momento se dará por hecho esto en la mayoría de los ejemplos que veamos):

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
}

public void retirarEfectivo(Cuenta cuenta, double cantidad) {


//retirar cantidad de cuenta
}

public void transferirEfectivo(Cuenta cuentaOrigen, Cuenta


cuentaDestino, double cantidad) {
// transferir cantidad de cuenta origen a cuenta destino
}
}

La clase Cuenta representa una cuenta bancaria, y teóricamente lo representaríamos


mediante un componente de tipo Entity Bean (Bean de Entidad), los cuales veremos en
el cuarto artículo de este tutorial. Lo realmente importante del ejemplo anterior es el
concepto de operaciones que no requieren estado, y por tanto pueden ser
representadas mediante Session Beans de tipo Stateless.

2.10 STATEFUL SESSION BEANS: CONCEPTOS BÁSICOS


Al contrario que los Session Bean de tipo Stateless, los Stateful Session Bean (SFSB -
Session Bean con Estado) mantienen estado entre distintas invocaciones de un mismo
cliente. Podríamos considerar un SFSB como una extensión del cliente en el
contenedor, ya que cada SFSB está dedicado de manera exclusiva a un único cliente
durante todo su ciclo de vida. Otra diferencia entre SLSB y SFSB es que estos últimos
no son almacenados en un pool, pues no son reusados, como veremos en la sección
2.11 cuando expliquemos su ciclo de vida.

¿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.

A pesar de mantener estado, un SFSB no es persistente, de manera que dicho estado


se pierde cuando la sesión del cliente asociado termina. Su misión es servir como
lógica de negocio (misión de todos los Session Bean) y nada más. Para persistir dicha
información deberemos usar otro tipo de componente EJB (Entity Bean) el cual
veremos en el artículo sobre persistencia.

2.11 STATEFUL SESSION BEANS: EL CICLO DE VIDA


El ciclo de vida de un SFSB es ligeramente más complejo que el de su hermano
pequeño SLSB, y consta de tres estados:

- No existe (Does not exists)


- Preparado (Method-ready)
- Pasivo (Passive)

El primer estado, no existe, es similar al de un SLSB: la instancia del SFSB no ha sido


creada aún. El segundo estado,preparado, representa una instancia del SFSB que ha
sido construida e inicializada, y está lista para servir llamadas de su cliente asociado
(recuerda que esta asociación perdura durante toda la vida del SFSB). El tercer
estado,pasivo, representa un SFSB que, después de un periodo de inactividad, es
persistido temporalmente para liberar recursos del servidor.

Durante la transición entre el primer estado y el segundo, el contenedor realizará las


siguientes tres operaciones:

- Instanciación del SFSB


- Inyección de cualquier recurso necesario y de dependencias, y asociación con el
cliente
- Ejecución de un método dentro del SFSB marcado con la
anotación @PostConstruct, si existe

La instanciación del SFSB se lleva a cabo mediante reflexión, a traves


de Class.newInstance(). Por tanto, el SFSB debe tener un constructor por defecto
(sin argumentos), ya sea de forma implícita o explícita.

La inyección de recursos y de dependencias se vio en las secciones 2.4 y 2.5, donde se


utilizaron las anotaciones@Resource y @EJB para tales efectos. Estos y otros recursos
son inyectados en el SFSB de forma automática por el contenedor durante esta
operación. Una vez finaliza la inyección de recursos y dependencias, el SFSB es
asociado al cliente que ha generado su creación.

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:

- El cliente ejecuta un método dentro del SFSB anotado con @Remove


- El SFSB sobrepasa un periodo de inactividad establecido (timeout)
En el primer caso, la ejecución de un método anotado con @Remove informa al
contenedor que el cliente ha terminado de usar su SFSB asociado y, por tanto, dicha
instancia ya no es necesaria. En este momento la instancia es desasociada de su
contexto de sesión y eliminada. Este método no es de
tipo callback (como @PostConstruct o@PreDestroy), pues no reacciona a un evento, si
no que es ejecutado explicitamente por el cliente. En el segundo caso (timeout), el
SFSB ha sobrepasado un periodo de tiempo establecido y el contenedor decide
eliminarlo, previa ejecución de un método (uno y solo uno) anotado con @PreDestroy,
y con iguales reglas que en su homónimo en SLSB: opcional, void, sin parámetros, sin
excepciones de tipo checked, no static, y no final.

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.

2.12 STATEFUL SESSION BEANS: PASIVACIÓN Y ACTIVACIÓN


Durante la vida de un SFSB, el contenedor puede decidir liberar recursos persistiendo
de manera temporal el estado de dicho SFSB, liberando de esta manera memoria del
sistema (u otros recursos). A esto lo llamamos pasivación. Si durante la pasivación el
cliente realiza una llamada al SFSB, el contenedor recreará en memoria la instancia
persistida y procedera con dicha llamada; a esto lo llamamos activación. Todo el ciclo
de activación-pasivación puede ocurrir múltiples veces en la vida de un SFSB, o
ninguna en absoluto. Sea como sea, es un proceso gestionado por el contenedor y
totalmente transparence al cliente, el cual no sabe en ningún momento si se ha
producido una pasivación o activación del SFSB asociado a su sesión.

No toda la información (estado) almacenada en un SFSB puede ser persistida. Los


siguientes tipos son pasivados por defecto:

- Todos los tipos primitivos


- Objetos que implementan la interface Serializable
- Referencias a factorias de recursos gestionados por el contenedor
(como javax.sql.DataSource)
- Referencias a otros componentes EJB
- javax.ejb.SessionContext
- javax.jta.UserTransaction
- javax.naming.Context
- javax.persistence.EntityManager
- javax.persistence.EntityManagerFactory
Para todos los demás tipos, la especificación EJB nos proporciona dos
métodos callback para controlar la pasivación-activación de un SFSB cuando existen
datos que no deben/pueden ser persistidos: @PrePassivate (Pre-pasivación)
y@PostActivate (Post-activación). Ambas anotaciones son aplicadas en métodos
dedicados a controlar cada uno de los procesos, según corresponda (en la sección 2.13
veremos un ejemplo de un SFSB con métodos de pasivación y activación).

@PrePassivate nos permite anotar un método (opcional) que deje el SFSB es un


estado adecuado para su pasivación. Puesto que la pasivación se lleva a cabo
mediante serialización, todas las referencias a objetos no serializables deben ser
puestas a null. Otra operacion llevada a cabo mediante @PrePassivate es la
liberación de recursos que no pertenezcan a la lista anterior. Una vez ejecutado el
método @PrePassivate, el SFSB es serializado y persistido temporalmente (tal vez en
disco, en una cache, etc; esto es decisión de cada implementación de EJB). Es
importante tener en cuenta que si un SFSB en estado pasivo sobrepasa un periodo de
inactividad establecido (timeout), el contenedor eliminará la instancia persistida sin
ejecutar su método @PreDestroy.

Si durante la pasivación el cliente asociado realiza una llamada al SFSB, el contenedor


realizará la activación del componente, deserializando la instancia desde el lugar
donde se encuentra persistida, restaurándola al estado anterior a producirse la
pasivación (inyección del contexto de sesión, inyección de referencias a otros
componentes EJB, etc), y ejecutando un método anotado con @PostActivate (también
opcional). En este método podemos inicializar todos los objetos no serializables a unos
valores adecuados (no debes confiar en los valores por defecto que el contenedor da a
objetos no serializables, pues entre implementaciones pueden ser diferentes), así
como restaurar cualquier recurso necesario para el correcto funcionamiento del SFSB.
Una vez que la activación ha concluido, el SFSB puede gestionar la llamada del cliente
que generó dicha activación.

Tanto @PrePassivate como @PostActivate deben seguir las siguientes reglas:

- El tipo de retorno debe ser void


- No puede aceptar ningún parámetro
- No puede lanzar ninguna excepción de tipo checked
- No puede ser static ni final

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.

2.13 STATEFUL SESSION BEANS: UN EJEMPLO SENCILLO


Supongamos que estamos diseñando una aplicación de tienda online, con un carrito de
la compra donde los clientes pueden añadir artículos, eliminarlos, vaciar el carrito, o
realizar un pedido. Puesto que cada una de estas operaciones se lleva a cabo de
manera independiente a las demas, pero los cambios de cada una de ellas pueden
afectar al resto, necesitamos mantener un estado entre distintas invocaciones, y por
tanto un componente Session Bean de tipo Stateful. Para declarar un SFSB usamos la
anotación @Stateful y hacemos que la clase en cuestión implemente la
interface Serializable (puesto que es este el mecanismo que utilizará el contenedor
para la pasivación-activación). El ejemplo, como siempre, es una estructura básica sin
apenas implementación: el objetivo del ejemplo es únicamente mostrar donde encaja
cada pieza:

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;

public void añadirArticulo(Articulo articulo, int cantidad) {


// añadir la cantidad de cierto artículo al carrito
}

public void eliminarArticulo(Articulo articulo, int cantidad) {


// eliminar la cantidad de cierto artículo del carrito
}

public void vaciarCarrito() {


// vaciar el carrito
}

@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.

Los dos últimos métodos (inicializar y detener) se encargar de obtener y liberar


recursos en momentos clave del ciclo de vida del SFSB. Fíjate
como @PostConstruct y @PostActivate anotan el mismo método, pues todas las
operaciones que hay en él son comunes a los procesos de construcción del SFSB y una
posible activación posterior. De igual
manera, @PrePassivate y @PreDestroy acompañan al mismo método, por el mismo
motivo. Ambos métodos, de tipo callback, son gestionados por el contenedor en base a
eventos, y por tanto el cliente del SFSB no necesita saber de su existencia (nosotros
los hemos declarado private, aunque cualquier nivel de visibilidad está permitido).

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!

Introducción a EJB 3.1 (III)


Publicado el 28 de Marzo de 2011
En el artículo anterior vimos algunos conceptos comunes a los componentes de una
aplicación EJB, como el contexto de sesión, o la diferencia entre remoto y local.
Además, vimos en cierta profundidad los dos tipos de Session Bean más comunes en
una aplicación EJB: Stateless y Stateful. En este artículo vamos a ver el último tipo de
Session Bean (Singleton Session Bean), dos conceptos aplicables a todos los Session
Bean y que son nuevos en la especificación EJB 3.1 (vista sin interface y llamadas
asíncronas), y finalmente un nuevo tipo de componente: Message-Driven Bean.
Comencemos.

3.1 SINGLETON SESSION BEANS: CONCEPTOS BÁSICOS


El Singleton Session Bean (no existe una traducción literal de la palabra Singleton,
pero viene a expresar el concepto de instancia única; desde ahora nos referiremos a
este componente como Singleton Session Bean, o Singleton a secas) es un nuevo tipo
de Session Bean introducido en la especificación EJB 3.1. Este componente se basa en
el patrón de diseño del mismo nombre definido por Erich Gamma, Richard Helm, Ralph
Johnson y John Vlissides en su libro Design Patterns: Elements of Reusable Object
Oriented Software (Patrones de Diseño: Elementos Reusables de Software Orientado a
Objetos; una lectura imprescindible). Este patrón de diseño garantiza que de una clase
dada solamente pueda crearse una instancia, con un punto de acceso global para
acceder a dicha instancia. La naturaleza única de un componente Singleton conlleva un
alto rendimiento dentro del contenedor para este tipo de Session Bean.

Es importante tener en cuenta la sutil diferencia entre un componente de tipo


Singleton y cualquiera de los otros dos tipos de Session Bean (SLSB y SFSB): cuando
un cliente hace una llamada a un método de un SLSB o SFSB, puesto que la instancia
del Session Bean está asociada a ese cliente en concreto (durante la invocación del
metodo para un SLSB, y durante toda la duración de la sesión para un SFSB), un único
thread (hilo de ejecución) es capaz de acceder a dicho método, y por tanto el
componente es seguro en terminos de multi-threading (ejecución de múltiples hilos).
Sin embargo, cuando trabajamos con un Singleton, multiples llamadas en paralelo
pueden estar produciendose en un momento dado a su única instancia, y por tanto el
componente debe garantizar que un hilo de ejecución no está interfiriendo con otro
hilo de ejecución, produciendo resultados incorrectos. Dicho de otra manera, un
componente Singleton debe ser concurrente;

3.2 SINGLETON SESSION BEANS: CONCURRENCIA BÁSICA


Debido a la naturaleza de los Session Bean de tipo Singleton, tenemos que tener muy
claros ciertos aspectos al diseñar este tipo de componente. Veamos un ejemplo de una
clase donde no tenemos en cuenta la cuestión de concurrencia:

class ClaseNoConcurrente {
private int numeroDeInvocacionesDeEstaInstancia = 0;

public int metodoUno() {


// lógica de negocio
return numeroDeEjecuciones++;
}

public int metodoDos() {


// lógica de negocio
return numeroDeEjecuciones++;
}
}

En la clase del ejemplo anterior, no se ha tenido en cuenta el aspecto de concurrencia


(lo cual, dependiendo de las especificaciones del problema de negocio que queremos
resolver, puede ser perfectamente legal). Una consecuencia de esto es que, cuando
intervienen múltiples hilos de ejecución en paralelo, una llamada al cualquiera de los
métodosmetodoXxx puede devolver un resultado incorrecto. Veamos una posibilidad
(entre muchas permitidas por la JVM) de la ejecución de esta clase por varios
clientes:

1. Un cliente C-1 llama a metodoUno()


2. El cliente C-1 obtiene el valor 1 para el número de ejecuciones
3. Un cliente C-2 llama a metodoUno()
4. Un cliente C-3 llama a metodoDos()
6. El cliente C-3 obtiene el valor 2 para el número de ejecuciones
7. El cliente C-2 obtiene el valor 2 para el número de ejecuciones

¿Que ha ocurrido en el ejemplo anterior? El cliente C-2 cree que los


métodos metodoXxx han sido invocados dos veces (punto 7), cuando en realidad se
han invocado tres veces (puntos 1, 3, y 4). Esto es debido a que la
operaciónnumeroDeEjecuciones++ no es atómica: cuando el código Java es convertido
en código de bytes (.class), dicha operación se compone de muchas otras, de manera
que la JVM puede para el hilo actual en mitad del incremento y dar tiempo de
ejecución a otro hilo. Incluso aunque la operación fuera atómica, la JVM podría seguir
deteniendo un hilo durante la lógica de negocio (antes de realizar el incremento),
dando tiempo de ejecución a otro hilo y produciendo de nuevo resultados incorrectos.
Puesto que un Singleton es compartido por muchos clientes (accedido por muchos
hilos de ejecución) este comportamiento no es aceptable: el Singleton debe ser
concurrente.

La especificación EJB 3.1 nos permite controlar la concurrencia de un Singleton de dos


maneras:

- Concurrencia Gestionada por el Contenedor (CMC - Contained-Managed


Concurrency)
- Concurrencia Gestionada por el Bean (BMC - Bean-Managed Concurrency)

Veamos cada una de ellas con cierto detalle.

3.3 SINGLETON SESSION BEANS: CONTAINED-MANAGED CONCURRENCY


Mediante CMC, el contenedor es responsable de gestionar toda la concurrencia en el
Singleton mediante metadatos, liberando así al programador de la tarea de escribir
código concurrente. Por defecto, todos los componentes Singleton son gestionados por
el contenedor, aunque podemos especificarlo de manera explicita mediante la
anotación @ConcurrencyManagement:

import javax.ejb.ConcurrencyManagement;
import javax.ejb.ConcurrencyManagementType;
import javax.ejb.Singleton;

@Singleton
@ConcurrencyManagement(ConcurrencyManagementType.CONTAINER)
class MiSingleton {
// ...
}

En el ejemplo anterior hemos definido un componente Singleton mediante la


anotación @Singleton, y hemos indicado al contenedor de forma explícita que
gestione por nosotros la concurrencia del componente mediante CMC (aunque, repito,
este comportamiento es el ofrecido por defecto para todos los Singleton). Cuando
usamos CMC, todos los métodos de la clase tienen por defecto un bloqueo de
tipo write (escritura). Este bloqueo es de utilidad cuando uno o varios métodos deben
ser accedidos de manera secuencial (hasta que uno no termine, otro no puede
comenzar) para evitar que se obtengan resultados incorrectos. Este acceso secuencial
es necesario en casos como el del ejemplo de la sección 3.2, en el que varios
métodos escribían sobre el valor de una variable que después era devuelta al cliente.
Como se puede deducir del comportamiento del bloqueo write, este afecta a todo el
objeto, y no a métodos concretos (todos los métodos write del mismo Singleton deben
compartir un único bloqueo).

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() {
// ...
}

3.4 SINGLETON SESSION BEANS: BEAN-MANAGED CONCURRENCY


En ocasiones, la concurrencia gestionada por el contenedor no es suficiente (tal vez no
nos permite definir con suficiente detalle la manera en la que necesitamos que
funcione la concurrencia de nuestra aplicación, por ejemplo). Bean-Managed
Concurrency (BMC - Concurrencia Gestionada por el Bean) deja en manos del
programador toda la gestión de la concurrencia. Esto puede realizarse utilizando
bloques synchronized, variables atómicas
comojava.util.concurrent.atomic.AtomicInteger, etc. Podemos indicar que la
concurrencia de un componente Singleton será gestionada íntegramente por el
programador mediante la anotación @ConcurrencyManagement (la cual usamos en la
sección anterior para declarar explícitamente el comportamiento contrario, CMC):

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.

3.5 SINGLETON SESSION BEANS: EL CICLO DE VIDA


El ciclo de vida de un Singleton muy parecido al de un SLSB, pues como en este último
se compone de los dos mismos estados:

- No Existe (Does not exists)


- Preparado (Method-ready)

La diferencia estriba en cuando se crea la única instancia del componente Singleton


(tras el despliegue de la aplicación o tras la primera invocación al componente), y en
su duración (a lo largo de toda la vida de la aplicación, esto es, hasta que la aplicación
sea replegada o el servidor sea parado). Evidentemente, solo el primer
comportamiento (el momento de creación) puede ser customizado, y para ello la
especificación EJB nos ofrece, como siempre, metadatos. Aunque el contenedor puede
decidir inicializar la única instancia de un Singleton en el momento que considere más
oportuno, podemos forzar que dicho proceso ocurra durante el despliegue mediante la
anotación@Startup:

import javax.ejb.Singleton;
import javax.ejb.Startup;

@Singleton
@Startup
public class MiSingleton {
// ...
}

El código anterior fuerza al contenedor a crear la única instancia de MiSingleton en el


momento del despliegue de la aplicación EJB de la que forma parte. Esto
comportamiento es llamado eager initialization (inicialización temprana). Ahora
supongamos que un Singleton debe ser inicializado antes que otro Singleton (tal vez
este último necesite poner la aplicación en cierto estado necesario para el correcto
funcionamiento del primero): podemos indicar esta dependencia en el orden de
inicialización mediante la anotación @DependsOn:

@Singleton
public class MiSingletonA {
// ...
}

@Singleton
@DependsOn("MiSingletonA")
public class MiSingletonB {
// ...
}

En el ejemplo anterior, indicamos al contenedor que en el momento de inicializar la


única instancia de MiSingletonB,MiSingletonA debe haber sido inicializado. De esta
manera, el contenedor forzará la inicialización del Singleton dependiente si aún no se
ha producido. Este comportamiento es totalmente ajeno al uso u omisión de @Startup:
si un Singleton depende de otro, este último se creará antes que el primero.

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
}

// lógica de negocio del Singleton


}

3.6 SINGLETON SESSION BEANS: UN EJEMPLO SENCILLO


La utilidad del componente Singleton está limitada a ciertos problemas de negocio que
podemos (o debemos) resolver mediante una única instancia de un objeto. Ejemplos
válidos de componentes Singleton serían gestores de ventanas, sistemas de ficheros,
colas de impresión, caches, etc. Para nuestro ejemplo de Singleton vamos a
desarrollar un sistema de logging, el cual debe ser accedido por toda la aplicación a
través de una única instancia:

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;

private enum Nivel {


DEBUG, INFO, ERROR
}

@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);
}

private void escribirMensajeEnArchivo(Nivel nivel, String mensaje) {


String cabecera = generarCabecera(nivel);
try {
writer.write(cabecera + mensaje + "\n");
} catch (IOException ioe) {
throw new RuntimeException(ioe);
}
}

private String generarCabecera(Nivel nivel) {


String fechaMasHoraActual = new SimpleDateFormat("dd/MM/yyyy
HH:mm:ss").format(new Date());
StringBuilder cabecera = new StringBuilder();
cabecera.append("[");
cabecera.append(nivel.name());
cabecera.append("] ");
cabecera.append(fechaMasHoraActual);
cabecera.append(" - ");

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.

Las primero que nos interesa del ejemplo son los


métodos callback @PostConstruct y @PreDestroy. En ellos se obtiene y libera
(respectivamente) un recurso dado, en nuestro caso el acceso a un fichero en disco.
Esto nos muestra la utilidad del componente Singleton: únicamente una instancia debe
crear, editar, y finalmente cerrar el mismo fichero en disco. Y esta única instancia es la
que obtendrán todos los clientes del Singleton. Ambos métodos se han marcado con
visibilidad protected para facilitar la tarea de testing (y por un motivo adicional que
se explicará en la próxima sección).

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).

Por último, los métodos de


utilidad escribirMensajeEnArchivo y generarCabecera son métodos de utilidad
usados por la lógica de negocio de nuestro componente.

3.7 SESSION BEANS EN GENERAL: NO-INTERFACE VIEW


Como se indicó en la sección anterior, el ejemplo del sistema de logging es
completamente funcional. Sin embargo, el componente no ha sido declarado local ni
remoto (ejemplos anteriores omitían este aspecto intencionadamente para mantener
el código simple). En este momento es preciso introducir el concepto de vista sin
interface.

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.

3.8 SESSION BEANS EN GENERAL: LLAMADAS ASÍNCRONAS


Otra de las novedades de la especificación EJB 3.1 es la posibilidad de llamar a los
métodos de nuestros Session Bean de forma asíncrona. Hasta ahora, todas las
llamadas que hemos realizado han sido sincronas, de manera que los clientes deben
esperar hasta que el método invocado termine de ejecutarse para seguir ejecutando el
resto de sus sentencias. Este es el comportamiento normal cuando invocamos un
método en Java. Sin embargo, cuando realizamos una llamada asíncrona, el flujo de
ejecución vuelve automáticamente a la aplicación que realiza la llamada, sin esperar el
resultado de la llamada. Más tarde, podemos comprobar dicho resultado si existe y lo
necesitamos.

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

En el ejemplo anterior, hemos declarado el método metodoLento() como un método


asíncrono mediante la anotación@Asynchronous. Dicha anotación puede ser aplicada
también a nivel de clase, en cuya caso todos los métodos de negocio (los declarados
en una interface local, en una interface remota, o los de visibilidad public en una vista
sin interface) serán considerados como asíncronos. Dentro del método hemos
simulado un proceso relativamente largo deteniendo durante quince segundos el hilo
de ejecución que está procesando la instancia del SLSB. Cualquier cliente que llame a
este método no tendrá que esperar esos quince segundos, ya que nada más realizar la
llamada le será devuelto en control de ejecución, sin esperar a que el método
asíncrono termine. Este comportamiento se conoce como fire-and-forget (disparar y
olvidar).

Otra posibilidad es que necesitemos el resultado de una invocación asíncrona.


Supongamos que necesitamos un método que realiza un cálculo intensivo y, cuando
finalmente obtiene el resultado, lo devuelve en una variable de tipoDouble. La manera
de declarar dicho método sería similar a esta:

import java.util.concurrent.Future;
import javax.ejb.AsyncResult;
import javax.ejb.Asynchronous;

// ...

@Asynchronous
public Future metodoLentoConResultado() {
try {
Thread.sleep(15000);

return new AsyncResult(Math.PI);


} catch(InterruptedException ie) {
throw new RuntimeException(ie);
}
}

Como puedes ver, el método metodoLentoConResultado() devuelve un objeto de


tipo Future (más concretamente de su implementación AsyncResult) parametizado
a Double. Es sobre este objeto Future sobre el que el cliente deberá comprobar si el
método asíncrono ha terminado su ejecución, para obtener a continuación su
resultado:

import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import javax.naming.NamingException;

// ...

Future resultado = bean.metodoLentoConResultado();


System.out.println("Método asíncrono invocado");
System.out.println("Realizando otras tareas mientras se ejecuta el método
asíncrono...");
// ...

while(true) {
if(!resultado.isDone()) {
Thread.sleep(2000);
} else {
System.out.println("Método asincrono devuelve el resultado " +
resultado.get());
break;
}
}

El ejemplo anterior es parte de un cliente del SLSB asíncrono. En el, en un momento


dado, se llama al método asíncrono, asignando el objeto Future devuelto por dicho
método en una variable. En este momento el resultado no está listo (recuerda que
tardará quince segundos en producirse), pero el control de la ejecución del cliente
continua sin esperar. Más adelante el cliente comprueba si el resultado está finalmente
disponible mediante el métodoisDone() de la clase Future. Cuando esto ocurra,
podemos obtener nuestro ansiado resultado mediante el métodoget() (también de la
clase Future), el cual devuelve un objeto del mismo tipo al que usamos para
parametizar la respuesta del método asíncrono (en nuestro caso, un objeto Double).
Es importante tener presente que el métodoget() bloquea el hilo de ejecución del
cliente hasta que el resultado esté listo, de manera que podemos omitir por completo
el bucle while:

Future resultado = bean.metodoLentoConResultado();


System.out.println("Método asíncrono invocado");
System.out.println("Realizando otras tareas mientras se ejecuta el método
asíncrono...");
// ...
System.out.println("Método asíncrono devuelve resultado " +
resultado.get());

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.

3.10 MESSAGE-DRIVEN BEANS: CONCEPTOS BÁSICOS


Los componentes de tipo Message-Driven Bean (MDB - Bean Dirigido por Mensajes)
son componentes asíncronos de tipo listener (oyente). Un MDB no es más que un
componente que espera a que se le envie un mensaje, y realiza cierta acción cuando
finalmente recibe dicho mensaje (el MDB escucha por si alguien le llama, de ahí su
nombre). Algunas propiedades de los componentes MDB son:

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

Los componentes MDB forman parte de un sistema de mensajería, el cual se compone


de los siguientes subsistemas:

Cliente (Productor) ---> Broker ---> Cliente (Consumidor) ---> Message-Driven


Bean

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.

3.11 JAVA MESSAGE SERVICE: CONCEPTOS BÁSICOS


Java Message Service (JMS - Servicio de Mensajería en Java) es una API neutral que
puede ser usada para acceder a sistemas de mensajería. Todos los contenedores EJB
3.x deben proporcionar un proveedor de JMS (el cual define su propia implementación
de la API), de manera que podamos trabajar con mensajes sin necesidad de añadir
librerías externas. Puesto que esta API es neutral, podemos cambiar en cualquier
momento el proveedor JMS por uno que se adecue más a nuestras necesidades (o
incluso usar un sistema de mensajería diferente a JMS).

Una aplicación JMS se compone, generalmente, de múltiples clientes JMS y un único


proveedor JMS. Un cliente JMS puede ser de dos tipos:
- Productor: su misión es enviar mensajes
- Consumidor: su misión es recibir mensajes

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.

3.12 JAVA MESSAGE SERVICE: MODELOS DE MENSAJERÍA


JMS proporciona dos modelos de mensajería, los cuales nos permiten definir el
comportamiento de nuestro sistema de mensajería:

- Publicar y Suscribir (pub/sub - Publish and Subscribe)


- Punto a Punto (p2p - Point to Point)

En el modelo pub/sub, un cliente JMS de tipo productor publicasus mensajes en un


canal virtual llamado topic (tema). A su vez, uno o varios clientes JMS de tipo
consumidor se suscriben a dicho topic si desean recibir los mensajes que en él se
publiquen. Si un suscriptor decide desconectarse del topic y más tarde reconectarse,
recibirá todos los mensajes publicados durante su ausencia (aunque este
comportamiento es configurable). Por todo esto, el modelo pub/sub es de tipo uno-a-
muchos (un productor, muchos consumidores).

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:

- No existe (Does not exists)


- Preparado en pool (Method-ready pool)

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.

Durante la transición entre el primer estado y el segundo, el contenedor realizará tres


operaciones (en este orden):

- Instanciación del MDB


- Inyección de cualquier recurso necesario y de dependencias
- Ejecución de un método dentro del MDB marcado con la anotación @PostConstruct,
si existe

La instanciación del MDB se lleva a cabo mediante reflexión, a traves de


Class.newInstance(). Por tanto, el MDB debe tener un constructor por defecto (sin
argumentos), ya sea de forma implícita o explícita.

Durante la inyección de dependencias, el contenedor inyectará automáticamente


cualquier recurso necesario para el MDB en base a los metadatos que hayamos
proporcionado (como una anotación @MessageDrivenContext, o una entrada en el
descriptor XML de EJB). De manera adicional, cada vez que un MDB procese un nuevo
mensaje, todas las dependencias se inyectarán de nuevo.

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).

3.14 MESSAGE-DRIVEN BEANS: DEFINICIÓN


Como ya hemos visto, los componentes de tipo MDB tienen como misión procesar
mensajes enviados de forma asíncrona. Aunque todo contenedor compatible EJB 3.x
debe incluir una implementación de JMS (de manera que tengamos un servicio de
mensajería on the box), MDB puede trabajar con otros servicios de mensajería
diferentes. En este tutorial solo se usará JMS como servicio de mensajería (afectando
este hecho, por ejemplo, a la interface que debe implementar el MDB, como veremos
en el próximo párrafo).

Todo MDB debe implementar la interface javax.jms.MessageListener, la cual define


el método onMessage(). Es dentro de este método donde se desarrolla toda la acción
cuando el MDB procesa un mensaje, como veremos en el ejemplo de la próxima
sección. De manera adicional, debemos indicar al contenedor que nuestra clase es un
componente MDB. Para ello, utilizamos la anotación @MessageDriven:

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
}
}

El ejemplo anterior aún no es funcional (producirá un error de despliegue), ya que el


MDB necesita saber el tipo de canal virtual (topic/queue) al que debe conectarse, así
como el nombre de dicho canal virtual. Esta información forma parte de la
configuración del MDB, configuración que proporcionamos al componente a través del
atributoactivationConfig (configuración de activación) de la anteriormente vista
anotación @MessageDriven:

@MessageDriven(activationConfig={
@ActivationConfigProperty(propertyName="destinationType",
propertyValue="javax.jms.Topic"),
@ActivationConfigProperty(propertyName="destination",
propertyValue="topic/MiTopic")})
public class PrimerMDB implements MessageListener {
// ...
}

En el ejemplo anterior, hemos proporcionado al MDB la configuración necesaria


mediante un array de anotaciones@ActivationConfigProperty. Cada una de estas
anotaciones contiene dos atributos: propertyName y propertyValue, en los cuales
indicamos parejas nombre-de-la-propiedad/valor-de-la-propiedad, respectivamente.
Volviendo a nuestro último ejemplo, hemos configurado el MDB con las siguientes
propiedades:

- El tipo de canal virtual al que el MDB se conectará (javax.jms.Topic)


- El nombre JNDI del canal virtual al que el MDB se conectará (topic/MiTopic).

Otra opción de configuración bastante interesante (aunque opcional) es la duración de


la subscripción:

@ActivationConfigProperty(propertyName="subscriptionDurability",
propertyValue="Durable")

Añadiendo la anotación anterior al array de propiedades de configuración del MDB,


nuestro componente recibirá todos los mensajes que se envien a su canal virtual
mientras el contenedor EJB (y por tanto el propio componente MDB) esté offline. Esta
situación puede ocurrir durante un shutdown del servidor, un problema de
conectividad por red, etc. En otras palabras, el mensaje estará disponible hasta que
todos sus suscriptores de tipo durable lo hayan recibido y procesado. La opción
contraria se aplica cambiando el valor del atributo propertyValue a NonDurable (no
durable). La durabilidad de los mensajes solo afecta a los componentes MDB que
trabajan con topics (modelo pub/sub), pues en un canal de tipo queue no tiene ningún
sentido almacenar un mensaje para componentes MDB offline (en cuanto un MDB este
online y lo consuma, el mensaje se eliminará).

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;

// ...
}

En el ejemplo anterior indicamos al contenedor que inyecte una instancia


de MessageDrivenContext mediante la
anotación @Resource. MessageDrivenContext extiende la interface EJBContext, sin
añadir ningún método. Solamente los métodos transaccionales son útiles al MDB, como
se verá cuando trabajemos con transacciones en artículos posteriores. El resto de
métodos de la interface lanzará una excepción si son invocados, ya que no tiene
sentido que un MDB pueda, por ejemplo, acceder al servicio de seguridad.

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).

3.15 MESSAGE-DRIVEN BEANS: UN SENCILLO EJEMPLO


Ya hemos visto como los servicios de mensajería asíncronos desacoplan de forma
completa al productor de un mensaje de su/s receptor/es: ninguno de ellos es
consciente de la parte contraria. Esto debido a que, entre ellos, existe un broker donde
se realiza todo el proceso de almacenaje (ya sea en canales virtuales de tipo topic o
queue) y reparto de los mensajes que se reciben.

El ejemplo que vamos a ver consta de tres partes:

- Creación de un canal virtual


- Creación de un componente MDB para consumir mensajes
- Creación de un cliente para enviar mensajes mediante JMS

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");
}
}
}
}

En el ejemplo anterior declaramos un componente MDB (con la


anotación @MessageDriven), lo configuramos para actuar como consumidor de los
mensajes del topic registrado con dirección JNDI topic/PrimerTopic (con las
anotaciones @ActivationConfigProperty), y finalmente añadimos dentro del
método onMessage() la lógica que procesará los mensajes. Recuerda que este método
es el único declarado en la interface MessageListener, la cual deben implementar
todos los MDB basados en JMS.

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

Ahora ya estamos listos para escribir el cliente productor JMS:


package es.davidmarco.ejb.cliente;

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;

public class ClienteJMS {


public static void main(String[] args) throws NamingException,
JMSException {
Properties propiedades = new Properties();
propiedades.put("java.naming.factory.initial",
"org.jnp.interfaces.NamingContextFactory");
propiedades.put("java.naming.factory.url.pkgs",
"org.jboss.naming:org.jnp.interfaces");
propiedades.put("java.naming.provider.url",
"jnp://localhost:1099");

Context contexto = new InitialContext(propiedades);


ConnectionFactory factoria =
(ConnectionFactory)contexto.lookup("ConnectionFactory");

Topic topic = (Topic)contexto.lookup("topic/PrimerTopic");


Connection conexion = factoria.createConnection();
Session sesion = conexion.createSession(false,
Session.AUTO_ACKNOWLEDGE);
MessageProducer productor = sesion.createProducer(topic);
conexion.start();

TextMessage mensajeDeTexto = sesion.createTextMessage("Mensaje


enviado desde Java");
productor.send(mensajeDeTexto);

conexion.close();
}
}

En nuestro cliente JMS externo al contenedor lo primero que hacemos es crear un


objeto de propiedades con los valores necesarios para conectar con el contenedor EJB
(como hicimos en el cliente del primer artículo). A continuación creamos un contexto
de ejecución desde el que poder acceder al contenedor, y mediante JNDI obtenemos
un objeto factoría ConnectionFactory, a través del cual podremos realizar conexiones
para el envío de mensajes.

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.

Al ejecutar el cliente, el mensaje se enviará al topic, y puesto que existen clientes


consumidores asociados a ese topic, el contenedor EJB consumirá el mensaje, extraerá
del pool MDB una instancia del componente MDB (o creará la instancia en el aire; a
nosotros nos es indiferente), y le pasará el mensaje al método onMessage() de dicha
instancia para que procese el mensaje. Si, tras ejecutar el cliente JMS, miras la
pestaña Console de Eclipse, verás el mensaje imprimido en el log de JBoss (tal como
se definió al escribir el método onMessage).

¿Recuerdas el sencillo esquema de la sección 3.10?:

Cliente (Productor) ---> Broker ---> Cliente (Consumidor) ---> Message-Driven


Bean

- El cliente Java es el cliente productor


- JMS (más concretamente su implementación HornetQ) es el broker
- El contenedor EJB es el cliente consumidor
- Una instancia MDB es quien procesa el mensaje

Lo interesante de un servicio de mensajería es que el cliente productor no sabe quien


consumirá su mensaje, ni como lo hará. Podría ser un MDB, o podría ser otro
componente en nada relacionado con la especificación EJB (tal vez ejecutándose en
una máquina remota con un sistema operativo distinto).

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

enviarMensaje("Mensaje enviado desde MDB");


} catch (JMSException jmse) {
throw new RuntimeException("Error al procesar un
mensaje");
}
}
}

private void enviarMensaje(String mensaje) throws JMSException {


ConnectionFactory factoria =
(ConnectionFactory)contexto.lookup("ConnectionFactory");
Topic topic = (Topic)contexto.lookup("topic/SegundoTopic");
Connection conexion = factoria.createConnection();
Session sesion = conexion.createSession(false,
Session.AUTO_ACKNOWLEDGE);
MessageProducer productor = sesion.createProducer(topic);
conexion.start();

TextMessage mensajeDeTexto = sesion.createTextMessage(mensaje);


productor.send(mensajeDeTexto);

conexion.close();
}
}

En el ejemplo anterior, dentro del método onMessage() se llama a un método de


utilidad que envia un nuevo mensaje a un segundo topic (que debemos haber
declarado con anterioridad, por supuesto). La única diferencia entre el código del
método de utilidad y el método main() del cliente Java es que, puesto que el primero
se está ejecutando dentro del contenedor EJB, podemos obtener el contexto de
ejecución mediante inyección de dependencia, evitando así la necesidad de crear el
objeto de propiedades y conectar por red al contenedor (lo cual sería bastante
absurdo).

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");
}
}
}
}

Cuando PrimerMDB reciba un mensaje, la consola de JBoss (pestaña Console de


Eclipse) mostrará como ambos MDB han procesado los mensajes de sus topics
correspondientes:

19:00:47,453 INFO [STDOUT] PrimerMDB ha procesado el mensaje: Mensaje


enviado desde Java
19:00:47,475 INFO [STDOUT] SegundoMDB ha procesado el mensaje: Mensaje
enviado desde MDB

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).

Introducción a EJB 3.1 (IV)


Publicado el 07 de Abril de 2011

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).

4.1 ENTIDADES: CONCEPTOS BÁSICOS


Las entidades, a diferencia del resto de componentes EJB, son objetos Java reales que
son manejados entre componentes (o entre un cliente y un componente) en su forma
original, nunca a través de proxys/vistas. Podemos crearlos con sentencias new,
pasarlos como parámetros a un Session Bean, etc. Pero el verdadero valor de las
entidades reside en que su estado puede ser almacenado en una base de datos, y más
tarde recuperado en un nuevo objeto del tipo correspondiente. De manera adicional,
los cambios que realicemos en el estado de una entidad serán sincronizados con la
información que tenemos almacenada en la base de datos.

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.

En este artículo no vamos a tratar en profundidad la especificación JPA (ni la


declaración ni el uso de entidades), si no como integrarla con una aplicación EJB 3.1.
Si no conoces JPA, es prácticamente obligatorio que visites el tutorial de JPA publicado
en este mismo blog, de manera que puedas comprender todo el material que sigue a
continuación.

4.2 ENTIDADES: EL CICLO DE VIDA


Como ya se ha mencionado, las entidades no son objetos del lado del servidor. Sin
embargo, cuando son usadas dentro del contexto de un contenedor EJB, se convierten
en objetos gestionados (gracias al servicio de persistencia, el cual es controlado
mediante la interface EntityManager). Esto nos lleva a los dos únicos estados de una
entidad cuando se encuentra en el contexto de un contenedor EJB:

- Gestionada (Attached)
- No gestionada (Detached)

En el primer estado, la entidad se encuentra gestionada por el servicio de persistencia:


cualquier cambio que realicemos en su estado se verá reflejado en la base de datos
subyacente. En el segundo estado, la entidad es un objeto Java regular, y cualquier
cambio que realicemos en su estado no será sincronizado con la base de datos
subyacente. En este último estado, la entidad puede ser, por ejemplo, enviada a traves
de una red (mediante serialización).

4.3 ENTIDADES: UNIDAD DE PERSISTENCIA


Una unidad de persistencia (persistence unit) representa un conjunto de entidades
que pueden ser mapeadas a una base de datos, así como la información necesaria para
que la aplicación JPA pueda acceder a dicha base de datos. Se define mediante un
archivo llamado persistence.xml, el cual debe acompañar a la aplicación donde se
realizan las tareas de persistencia (recuerda que puede ser una aplicación EJB o una
aplicación Java normal):

<?xml version="1.0" encoding="UTF-8"?>


<persistence xmlns="http://java.sun.com/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence
http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd"
version="2.0">
<persistence-unit name="introduccionEJB">

<!-- configuración de acceso a la base de datos -->


<!-- lista de entidades que pueden ser mapeadas -->

</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.

4.4 ENTIDADES: CONTEXTO DE PERSISTENCIA


Otro concepto que tenemos que tener claro es el de contexto de persistencia. Un
contexto de persistencia representa un conjunto de instancias de entidades que se
encuentran gestionadas en un momento dado. Existen dos tipos de contextos de
persistencia:

- Limitados a una transacción (Transaction-scoped)


- Extendidos (Extended)
Cuando trabajamos dentro de un contexto de persistencia limitado a una transancción,
todas las entidades gestionadas pasarán a estar no gestionadas cuando dicha
transacción finalice. Dicho con otras palabras, los cambios realizados tras finalizar la
transacción no serán sincronizados con la base de datos.

Cuando trabajamos dentro de un contexto de persistencia extendido, las cosas


funcionan de manera diferente: el contexto de persistencia sobrevivirá a la transacción
donde se ejecuta, de manera que los cambios que realicemos en el estado de las
entidades gestionadas por el contexto de persistencia se sincronizarán con la base de
datos en el momento en que entremos en una nueva transacción. Este comportamiento
es útil cuando trabajamos con SFSB, pues permite mantener un estado conversacional
y mantener nuestras entidades sincronizadas.

4.5 ENTIDADES: ENTITY MANAGER


Mediante la interface EntityManager (Gestor de entidades) tenemos acceso al servicio
de persistencia de nuestro contenedor. Podemos obtener una instancia
de EntityManager en nuestros componentes EJB mediante inyección de
dependencias:

import javax.ejb.Stateless;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;

@Stateless
public class MiSlsb {

@PersistenceContext(unitName="introduccionEJB")
private EntityManager em;

// operaciones del SLSB


}

El ejemplo anterior inyecta una instancia de EntityManager en el SLSB mediante la


anotación @PersistenceContext. A esta anotación hay que proporcionarle como
atributo el nombre de la unidad de persistencia que EntityManager usará para realizar
la persistencia (y que hemos definido en el archivo persistence.xml). Con los
metadatos proporcionados obtendremos por defecto un contexto de persistencia
limitado a una transacción (ver sección anterior); si deseamos obtener un contexto de
persistencia extendido (solo válido en SFSB por su naturaleza conversacional),
debemos añadir el atributo type con el valor correspondiente para este
comportamiento:

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.

4.6 ENTIDADES: UN SENCILLO EJEMPLO


Veamos un sencillo ejemplo de un componente EJB realizando persistencia sobre una
base de datos. Al igual que algunos ejemplos anteriores, este consta de varias partes:

- Una fuente de datos (data source) donde realizar la persistencia


- Una aplicación EJB donde se realiza las acciones de persistencia
- Un cliente EJB desde el que intercambiar entidades con la aplicación EJB

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:

<?xml version="1.0" encoding="UTF-8"?>


<datasources>
<local-tx-datasource>
<jndi-name>DerbyDS</jndi-name>
<connection-
url>jdbc:derby://localhost:1527/introduccionEJB;create=true</connection-
url>
<driver-class>org.apache.derby.jdbc.ClientDataSource40</driver-
class>
<user-name></user-name>
<password></password>
</local-tx-datasource>
</datasources>

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.

El siguiente paso es crear un proyecto EJB 3.1 en Eclipse, y continuación levantar la


base de datos Derby desde el propio IDE. Para ello, haz click con el botón derecho
sobre el nombre del proyecto EJB en la pestaña Proyect Explorery selecciona:

Apache Derby > Add Apache Derby nature

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:

Apache Derby > Start Derby Network Server


Nos aparecerá una ventana donde se nos informa que la base de datos está siendo
levantada, haz click en el botónOK para finalizar este proceso. En un entorno en
producción, todas estas operaciones serían innecesarias, pues teóricamente
tendríamos una base de datos externa funcionando de manera continua.

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;

public interface OperacionesConCuentas {


public void crearCuenta(Cuenta cuenta);
public Cuenta obtenerCuenta(Long id);
public void borrarCuenta(Cuenta cuenta);
}

Ahora ya podemos implementar el componente remoto:

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;

public Long getId() {


return id;
}

public void setId(Long id) {


this.id = id;
}

public String getNumeroDeCuenta() {


return numeroDeCuenta;
}

public void setNumeroDeCuenta(String numeroDeCuenta) {


this.numeroDeCuenta = numeroDeCuenta;
}

public String getNombreDelTitular() {


return nombreDelTitular;
}

public void setNombreDelTitular(String nombreDelTitular) {


this.nombreDelTitular = nombreDelTitular;
}

public Double getSaldo() {


return saldo;
}

public void setSaldo(Double saldo) {


this.saldo = saldo;
}

public void aumentarSaldo(Double cantidad) {


saldo += cantidad;
}

public void reducirSaldo(Double cantidad) {


saldo -= cantidad;
}
}

Algunos detalles de la entidad definida en el ejemplo anterior merecen una pequeña


explicación: para empezar, nuestra entidad implementa la interface Serializable,
necesaría cuando nuestra entidad va a viajar a través de una red (en nuestro caso
entre el cliente Java y el contenedor EJB). Dentro de la entidad definimos 4
propiedades: una para la identidad de la entidad, y tres para representar su estado
(numeroDeCuenta, nombreDelTitular, y saldo), más sus correspondientes métodos
getter/setter. De manera adicional, hemos añadido algunas operaciones de lógica de
negocio (aumentarSaldo() y reducirSaldo) dentro de la entidad; es una buena
práctica que las operaciones relacionadas con cuentas estén dentro de la clase que
representa dichas cuentas.

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:

<?xml version="1.0" encoding="UTF-8"?>


<persistence xmlns="http://java.sun.com/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence
http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd"
version="2.0">
<persistence-unit name="introduccionEJB" transaction-type="JTA">
<provider>org.hibernate.ejb.HibernatePersistence</provider>

<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>

En el archivo XML anterior hemos declarado una unidad de persistencia con


nombre introduccionEJB (que fue el que usamos como parámetro de la
anotación @PersistenceContext en el componente SLSB que definimos previamente),
así como transacciones de tipo JTA (gestionadas por el contenedor; los tipos de
transacción se explicaron en las secciones 3.2 y 3.3 del tercer artículo del tutorial de
JPA).
Ya dentro de la declaración de la unidad de persistencia, hemos declarado el
proveedor de persistencia que usaremos (HibernatePersistence), la dirección JNDI
de la fuente de datos que declaramos en el archivo derby-ds.xml, y las entidades que
gestionaremos en esta unidad de persistencia (en nuestro caso solamente Cuenta).
Por último, hemos configurado algunos detalles relativos a la fuente de datos dentro
del elemento <properties>: el dialecto que usará el contenedor para construir
sentencias SQL adecuadas a nuestra base de datos, y la creación automática de los
esquemas necesarios en base a los metadatos de nuestras entidades (de esta manera
nos evitamos crear manualmente tanto las tablas como sus columnas, incluyendo la
definición del tipo de dato de cada columna, restricciones de cada columna, etc).
Ahora ya podemos desplegar la aplicación EJB en el contenedor.

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:

Build Path > Configure Build Path

En la ventana que nos aparece, vamos a la pestaña Proyects, hacemos click en el


botón Add, seleccionamos el proyecto EJB donde esta nuestro componente SLSB y
nuestra entidad, hacemos click en el botón OK, y de nuevo hacemos click en el
botón OK. Ahora ya podemos escribir el cliente:

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;

public class Cliente {


private static final String JNDI_BEAN =
"OperacionesConCuentasRemote/remote";

public static void main(String[] args) throws NamingException {


Properties properties = new Properties();
properties.put("java.naming.factory.initial",
"org.jnp.interfaces.NamingContextFactory");
properties.put("java.naming.factory.url.pkgs",
"org.jboss.naming:org.jnp.interfaces");
properties.put("java.naming.provider.url",
"jnp://localhost:1099");
Context context = new InitialContext(properties);

Cuenta cuenta = new Cuenta();


cuenta.setNumeroDeCuenta("0000-0001");
cuenta.setNombreDelTitular("Nuevo cliente");
cuenta.setSaldo(2500D); */

OperacionesConCuentas occ =
(OperacionesConCuentas)context.lookup(JNDI_BEAN);
occ.crearCuenta(cuenta);
}
}

En el ejemplo anterior, creamos e inicializamos una cuenta, obtenemos un proxy/vista


al componente EJB, e invocamos su método crearCuenta() pasándole como
parámetro la cuenta. Esta entidad será serializada, viajará a traves de la red hasta el
contenedor, será deserializada, y dentro del componente EJB será persistida en la base
de datos. Podemos comprobarlo ejecutando una sentencia SQL contra la base de
datos: para ello, haz click con el botón derecho sobre el nombre del proyecto EJB en la
pestaña Proyect Explorer y selecciona:

Apache Derby > ij (Interactive SQL)

La consola de ij se abrirá en la pestaña Console de Eclipse, y desde el prompt de ij nos


conectamos a la base de datos mediante el comando:

connect 'jdbc:derby://localhost:1527/introduccionEJB;'

Cuando la conexión se realice, volveremos a ver el prompt de ij. Ahora ya podemos


realizar la consulta SQL mediante el comando:

select * from cuenta;

La consola de ij nos mostrará un registro en la tabla Cuenta:

1 |Nuevo cliente |0000-0001 |2500.0

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):

// ...

public class Cliente {


private static final String JNDI_BEAN =
"OperacionesConCuentasRemote/remote";

public static void main(String[] args) throws NamingException {


Properties properties = new Properties();
properties.put("java.naming.factory.initial",
"org.jnp.interfaces.NamingContextFactory");
properties.put("java.naming.factory.url.pkgs",
"org.jboss.naming:org.jnp.interfaces");
properties.put("java.naming.provider.url",
"jnp://localhost:1099");
Context context = new InitialContext(properties);

// Cuenta cuenta = new Cuenta();


// cuenta.setNumeroDeCuenta("0000-0001");
// cuenta.setNombreDelTitular("Nuevo cliente");
// cuenta.setSaldo(2500D); */

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:

select * from cuenta;

Si has realizado correctamente el ejercicio, la tabla Cuenta no deberá mostrar ninguna


entidad (salvo que hayas insertado entidades adicionales).

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).

También podría gustarte