3. Enterprise beans y JPA

Veremos en este apartado cómo se gestionan las transacciones y el contexto de persistencia (entity manager) con beans de sesión.

3.1. Proyecto básico JPA

Vamos a retomar el ejemplo básico con el que terminamos la sesión de JPA, en el que definimos una aplicación web con las entidades Autor y Mensaje y una relación uno-a-muchos entre ellas. Nos vamos a olvidar por el momento de los DAOs y vamos a trabajar directamente con las entidades.

Por simplificar, vamos a colocar todas las clases de entidad en el paquete org.expertojava.ejb:

Autor.java
package org.expertojava.ejb;

import javax.persistence.*;
import java.util.HashSet;
import java.util.Set;

@Entity
public class Autor {
    @Id
    @GeneratedValue
    @Column(name = "autor_id")
    Long id;
    @Column(name="email", nullable = false)
    private String correo;
    private String nombre;
    @OneToMany(mappedBy = "autor", cascade = CascadeType.ALL)
    private Set<Mensaje> mensajes = new HashSet<Mensaje>();

    public Long getId() { return id; }

    public String getCorreo() { return correo; }
    public void setCorreo(String correo) { this.correo = correo; }

    public String getNombre() { return nombre; }
    public void setNombre(String nombre) { this.nombre = nombre; }

    public Set<Mensaje> getMensajes() { return mensajes; }
    public void setMensajes(Set<Mensaje> mensajes) { this.mensajes = mensajes; }

    public Autor() {
    }

    public Autor(String nombre, String correo) {
        this.nombre = nombre;
        this.correo = correo;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        Autor autor = (Autor) o;

        if (!correo.equals(autor.correo)) return false;
        if (!nombre.equals(autor.nombre)) return false;

        return true;
    }

    @Override
    public int hashCode() {
        int result = correo.hashCode();
        result = 31 * result + nombre.hashCode();
        return result;
    }

    @Override
    public String toString() {
        return "Autor{" +
                "id=" + id +
                ", correo='" + correo + '\'' +
                ", nombre='" + nombre + '\'' +
                '}';
    }
}
Mensaje.java
package org.expertojava.ejb;

import javax.persistence.*;
import java.util.Date;

@Entity
public class Mensaje {

    @Id
    @GeneratedValue
    @Column(name = "mensaje_id")
    private Long id;
    @Column(nullable = false)
    private String texto;
    private Date fecha;
    @ManyToOne
    @JoinColumn(name = "autor", nullable = false)
    private Autor autor;

    public Long getId() { return id; }

    public String getTexto() { return texto; }
    public void setTexto(String texto) { this.texto = texto; }

    public Date getFecha() { return fecha; }
    public void setFecha(Date fecha) { this.fecha = fecha; }

    public Autor getAutor() { return autor; }
    public void setAutor(Autor autor) { this.autor = autor; }

    public Mensaje() {}

    public Mensaje(String texto, Autor autor) {
        this.texto = texto;
        this.autor = autor;
     }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        Mensaje mensaje = (Mensaje) o;

        if (!autor.equals(mensaje.autor)) return false;
        if (!texto.equals(mensaje.texto)) return false;

        return true;
    }

    @Override
    public int hashCode() {
        int result = texto.hashCode();
        result = 31 * result + autor.hashCode();
        return result;
    }

    @Override
    public String toString() {
        return "Mensaje{" +
                "id=" + id +
                ", texto='" + texto + '\'' +
                ", fecha=" + fecha +
                '}';
    }
}

El fichero persistence.xml es el siguiente. suponemos que en el servidor de aplicaciones ya está creada la fuente de datos java:/datasources/MensajesDS.

META-INF/persistence.xml
<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.0"
             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">
    <persistence-unit name="mensajes">
        <jta-data-source>java:/datasources/MensajesDS</jta-data-source>
        <properties>
            <property name="hibernate.hbm2ddl.auto" value="create" />
            <property name="hibernate.show_sql" value="true" />
        </properties>
    </persistence-unit>
</persistence>

3.2. Gestión de transacciones con beans de sesión

Los beans de sesión permiten dos tipos de gestión de transacciones, denominados CMP (Container Managed Persistence) y BMP (Bean Managed Persistence).

Cuando se utiliza CMP, los beans gestionan de forma automática las transacciones. Toda llamada a un método del bean es interceptada por el contenedor, que crea un contexto transaccional que dura hasta que el método ha terminado (con éxito o con fracaso, al lanzar una excepción). Si el método termina con éxito el contenedor hace un commit de la transacción. Si el método lanza una excepción el contenedor hace un rollback.

Cuando se utiliza BMP es el programador el que debe abrir y cerrar las transacciones utilizando JTA de forma explícita el código de los métodos del bean.

Ambos enfoques se basan en JTA, que es utilizado por el servidor de aplicaciones para gestionar las transacciones distribuidas utilizando el algoritmo two phase commit. Los recursos que participan en estas transacciones deben ser fuentes de datos XA.

Un uso muy común de los beans de sesión es la implementación de la capa de negocio de la aplicación, definiendo métodos de servicio transaccionales. En cada método de servicio se realizan llamadas a la capa de persistencia definida por los DAO que gestionan las entidades del modelo. Los DAO utilizan entity managers inyectados por el contenedor. El contenedor se encarga automáticamente de inyectar el mismo entity manager en todos los DAO que participan en la misma transacción, de forma que todos los DAO van a compartir el mismo contexto de persistencia. Se libera a los DAO de la gestión de transacciones y su implementación se hace más sencilla y flexible.

Al utilizar JTA es posible llamar desde un método de servicio a otros servicios de la capa de negocio y englobar todo ello en una única transacción.

3.2.1. Gestión de transacciones BMT

En la gestión de transacciones denominada BMT (Bean Managed Transactions) el programador se hace cargo de la gestión de transacciones dentro de los métodos de los beans utilizando JTA. Ya hemos visto anteriormente un ejemplo cuando describimos JTA:

@Stateless
@TransactionManagement(TransactionManagementType.BEAN) (1)
public class PedidoService {
   @Resource
   private UserTransaction userTransaction; (2)

   public void hacerPedido(Item item, Cliente cliente) {
      try {
         userTransaction.begin(); (3)
         if (itemService.disponible(item)) {
            pedidoService.add(cliente,item);
            clienteService.anotaCargo(cliente,item);
            itemService.empaqueta(cliente,item);
         }
         userTransaction.commit();
      } catch (Exception e) { (4)
         userTransaction.rollback();
         e.printStackTrace();
      }
   }
}
1 Se declara el tipo de gestión de transacción como BMT, para lo que se define el valor TransactionManagementType.BEAN como tipo de la anotación @TransactionManagement
2 Se declara el recurso UserTransaction que vamos a utilizar para controlar la transacción mediante JTA. Se define utilizando inyección de dependencias.
3 Se inicia la transacción. Las siguientes líneas de código definen las llamadas a los métodos transaccionales de otros beans de sesión. En estos métodos se utilizan conexiones XA que participan en la transacción definida en el bean.
4 Si alguna de estas llamadas genera una excepción, ésta se recoge y se llama a userTransaction.rollback(), con lo que automáticamente se deshace la transacción y todos los recursos implicados en la transacción vuelven a su estado original.

En el caso en que todas las llamadas terminen correctamente, se realiza la llamada userTransaction.commit(); que la transacción y final se propaga a todos los recursos que intervienen en la transacción.

Vemos que el funcionamiento es idéntico al que hemos definido cuando hemos hablado de JTA. Todas las llamadas a los beans de sesión se incorporan a la misma transacción, sin modificar el código de los métodos. De esta forma no perdemos flexibilidad en los métodos de negocio, siguen siendo atómicos, se pueden utilizar de forma independiente y, además, pueden incorporarse distintas llamadas a la misma transacción.

Un problema de BMT es que no se puede unir una transacción definida en un método a una transacción ya abierta por el llamador. De esta forma, si el método anterior es llamado desde un cliente que tiene una transacción JTA abierta, esta transacción del llamador se suspende. JTA no permite transacciones anidadas. De la misma forma, los métodos dentro de la transacción definida en el bean (pedidoBean.add() y demás) no pueden a su vez abrir una nueva transacción con BMT, ya que estaríamos en el mismo caso que el que hemos mencionado, abriendo una transacción anidad. En el siguiente apartado veremos que la gestión de transacciones por el contenedor (CMT, Containter Managed Transaction) proporciona más flexibilidad, ya que, por ejemplo, permite que los métodos de los beans se incorporen a transacciones ya abiertas.

3.2.2. Gestión de transacciones CMT

Cuando verdaderamente aprovechamos todas las funcionalidades del contenedor EJB es cuando utilizamos el otro enfoque de la gestión de las transacciones en los enterprise beans, el llamado CMT (Container Managed Transactions). Aquí, el programador no tiene que escribir explícitamente ninguna instrucción para definir el alcance de la transacción, sino que es el contenedor EJB el que la maneja automáticamente. En este enfoque, los métodos de negocio de los beans se anotan y podemos configurar con anotaciones cómo se van a comportar. Veamos cómo definiríamos con CMT el mismo ejemplo anterior.

@Stateless
@TransactionManagement(TransactionManagementType.CONTAINER)
public class PedidoService {

   @TransactionAttribute(TransactionAttributeType.REQUIRED)
   public void hacerPedido(Item item, Cliente cliente) {
      if (itemService.disponible(item)) {
         pedidoService.add(cliente,item);
         clienteService.anotaCargo(cliente,item);
         itemService.empaqueta(cliente,item);
      }
   }
}

Definimos en la clase el tipo de gestión de transacciones como TransactionManagementType.CONTAINER. El contenedor se encarga de la gestión de las transacciones del bean. Nosotros sólo debemos indicar en cada método qué tipo de gestión de transacción se debe hacer. En el método hacerPedido lo hemos definido como REQUIRED usando la anotación @TransactionAttribute.

La anotación @TransactionAttribute se puede realizar a nivel de método o a nivel de clase. En este último caso todos los métodos de la clase se comportan de la forma especificada.

El tipo de atributo de transacción REQUIRED obliga a que el método se ejecute dentro de una transacción. En el caso en que el llamador del método haya abierto ya una transacción, el contenedor ejecuta este método en el ámbito de esa transacción. En el caso en que el llamador no haya definido ninguna transacción, el contenedor inicia automáticamente una transacción nueva.

El tipo de atributo de transacción REQUIRED es el que tienen por defecto todos los métodos de los componentes EJB. Por eso se dice que son componentes transaccionales.

Todas las llamadas a otros métodos desde el bean se ejecutan en una transacción. Estas llamadas pueden ser a la capa de persistencia o a otros métodos de negocio.

Si el método no termina normalmente, sino que lanza una excepción de tipo RuntimeException (porque la lanza él mismo o alguna de las llamadas realizadas) se realiza automáticamente un rollback de la transacción. El contenedor captura la excepción provocada por el método y llama a JTA para realizar un rollback.

Si se lanza una excepción chequeada que no es de tipo RuntimeException el rollback no se realiza automáticamente. En este caso hay que llamar al método setRollbackOnly del EJBContext que se puede obtener por inyección de dependencias:

@Stateless
public class PeliculaService {
   @Resource
   private EJBContext context;

   public void metodoTransaccional() throws AlgunaExcepcionChequeada {

      // Llamadas a otros métodos

      if (seHaProducidoError) {
         context.setRollbackOnly();
         throw new AlgunaExcepcionChequeada("Se ha producido un error");
      }
   }
}

Si el método del bean termina normalmente, el contenedor hace commit de la transacción.

El código resultante es muy sencillo ya toda la gestión de la transacción se realiza de forma implícita. En el código sólo aparece la lógica de negocio.

La utilización de las transacciones CMT tiene la ventaja añadida de que los métodos pueden participar en transacciones abiertas en otros métodos.

Propagación de transacciones

Existen seis posibles formas de propagar una transacción, definidas por los posibles tipos de atributos de transacción:

  • TransactionAttributeType.REQUIRED

  • TransactionAttributeType.REQUIRES_NEW

  • TransactionAttributeType.SUPPPORTS

  • TransactionAttributeType.MANDATORY

  • TransactionAttributeType.NOT_SUPPORTED

  • TransactionAttributeType.NEVER

El funcionamiento de cada tipo de gestión depende de si el llamador del método hace la llamada con una transacción ya abierta o no. Vamos a ver un ejemplo. Supongamos que los métodos a los que se llama en hacerPedido() no son métodos de un DAO (un POJO), sino que se tratan de métodos de negocio de otros EJBs:

@Stateless
@TransactionManagement(TransactionManagementType.CONTAINER)
public class PedidoEJB implements PedidoLocal {
   @Resource
   private SessionContext context;

   @TransactionAttribute(TransactionAttributeType.REQUIRED)
   public void hacerPedido(Item item, Cliente cliente) {
      try {
         if (itemEJB.disponible(item)) {
            pedidoEJB.add(cliente,item);
            clienteEJB.anotaCargo(cliente,item);
            itemEJB.empaqueta(cliente,item);
         } catch (Exception e) {
            context.setRollbackOnly();
         }
      }
   }
}

En este caso tenemos una situación como la que se muestra en la siguiente figura, en la que un método de negocio de un EJB llama a otro método de negocio. ¿Cómo van a gestionar la transacción los métodos llamados? Va a depender de qué tipo de @TransactionAttribute se hayan definido en los métodos.

REQUIRED

se trata del tipo de gestión de transacciones por defecto. Si no declaramos el tipo en el método (ni en el bean) éste es el que se aplica. El código del método siempre debe estar en una transacción. Si el llamador realiza la llamada al método dentro de una transacción, el método participa en ella y se une a la transacción. En el caso de que el llamador no haya definido ninguna transacción, se crea una nueva que finaliza al terminar el método.

Si la transacción falla en el método y éste participa en una transacción definida en el cliente, el contenedor deshará la transacción en el método y devolverá al cliente la excepción javax.transaction.RollbackException, para que el cliente obre en consecuencia.

Si el método termina sin que se genere ninguna excepción y la transacción es nueva (el llamador no había definido ninguna), el contenedor hace un commit de la transacción. En el caso en que el método participe en una transacción abierta en el cliente, será éste el que deberá hacer el commit.

REQUIRES_NEW

indica que el contenedor debe crear siempre una transacción nueva al comienzo del método, independientemente de si el cliente ha llamado al método con una transacción creada o no. La transacción en el llamador (si existe) queda suspendida hasta que la llamada termina. Además, el método llamado no participa en la transacción del llamador. El método llamado tiene su propia transacción y realiza un commit cuando termina. Si la transacción en el cliente falla después, el rollback ya no afecta al método terminado.

Un ejemplo de uso de este atributo es una llamada a un método para escribir logs. Si hay un error en la escritura del log, no queremos que se propague al llamador. Por otro lado, si el llamador falla queremos que quede constancia de ello.

SUPPORTS

el método hereda el estado transaccional del llamador. Si el llamador define una transacción, el método participa en ella. Si no, el método no se ejecuta en ninguna transacción. Suele utilizarse cuando el bean realiza operaciones de lectura que no modifican el estado de sus recursos transaccionales.

MANDATORY

el cliente debe obligatoriamente crear una transacción antes de llamar a este método. Si se llama al método desde un entorno en el que no se ha abierto una transacción, se genera una excepción. Se usa muy raramente.

NOT_SUPPORTED

el método se ejecuta sin crear una transacción. En el caso en que el llamador haya creado una, ésta se suspende, se ejecuta el método y después continua. Para el cliente el comportamiento es igual que REQUIRES_NEW. La diferencia es que en el bean no se ha creado ninguna transacción.

Se suele usar sólo por MDB soportando un proveedor JMS no transaccional

NEVER

se genera una excepción si el cliente invoca el método habiendo creado una transacción. No se usa casi nunca.

3.2.3. Gestión de la transacción con la interfaz UserTransaction

Para gestionar una transacción JTA de forma programativa deberemos en primer lugar obtener del servidor de aplicaciones el objeto javax.transacion.UserTransaction. La forma más sencilla de hacerlo es mediante inyección de dependencias, declarándolo con la anotación:

@Resource
UserTransaction utx;

Una vez obtenido el UserTransaction podemos llamar a los métodos de la interfaz para demarcar la transacción:

  • public void begin()

  • public void commit()

  • public void rollback()

  • public int getStatus()

  • public void setRollbackOnly()

  • public void setTransactionTimeout(int segundos)

Para comenzar una transacción la aplicación se debe llamar a begin(). Para finalizarla, la transacción debe llamar o bien a commit() o bien a rollback().

El método setRollbackOnly() modifica la transacción actual de forma que el único resultado posible de la misma sea de roll back (falllo).

El método setTransactionTimeout(int segundos) permite modificar el timeout asociado con la transacción actual. El parámetro determina la duración del timeout en segundos. Si se le pasa como valor cero, el timeout de la transacción es el definido por defecto.

El siguiente ejemplo simplificado muestra un fragmento de código que utiliza transacciones JTA. En el ejemplo mostramos un fragmento de código en el que un usuario de una biblioteca devuelve tarde un préstamo y se le anota una multa

UserTransaction tx;

//...
// Obtenemos el UserTransaction
// y lo guardamos en tx
//...

tx.begin();
try {
   operacionService.devolverEjemplar(ejemplar);
   usuarioService.multar(login, fechaDevolucionPrevista,
                         fechaDevolucionReal);
   tx.commit();
   }
} catch (Exception e) {
   tx.rollback();
   throw new BusinessException("Error al hacer la devolución", e);
}

Los objetos operacionService y usuarioService son beans de sesión sin estado y los métodos devolverEjemplar y multar son métodos atómicos. Cada uno de ellos crea su propia transacción. JTA nos permite incorporar ambas llamadas en una única transacción, de forma que si uno de los métodos devuelve algún error se deshace toda la operación.

El código anterior puede ejecutarse en cualquier componente gestionado por el contenedor, como por ejemplo un servlet. También puede incluirse en un bean que define un servicio componiendo llamadas a otros servicios.

La otra forma de gestionar transacciones es gestionándolas de forma declarativa con anotaciones en los EJBs. Lo veremos más adelante.

3.2.4. Transacciones a través de múltiples bases de datos

Una de las características interesantes de JTA es que permite gestionar transacciones distribuidas que afectan a distintas bases de datos. Podemos utilizar distintas unidades de persistencia en los beans y agruparlas en una misma transacción. Las siguientes figuran muestran dos posibles escenarios:

En la figura anterior, el cliente invoca un método de negocio en el Bean-A. El método de negocio inicia una transacción, actualiza la base de datos X, actualiza la base de datos Y e invoca un método de negocio en el Bean-B. El segundo método de negocio actualiza la base de datos Z y devuelve el control al método de negocio en el Bean-A, que finaliza con éxito la transacción. Las tres actualizaciones de la base de datos ocurren en la misma transacción.

En la figura anterior, el cliente llama a un método de negocio en el Bean-A, que comienza una transacción y actualiza la base de datos X. Luego el Bean-A invoca un segundo método de negocio en el Bean-B, que reside en un servidor de aplicaciones remoto. El método en el Bean-B actualiza la base de datos Y. La gestión de transacciones de ambos servidores de aplicaciones se asegura de que ambas actualizaciones se realizan en la misma transacción. Es un ejemplo de una transacción distribuida a nivel de servidores de aplicaciones.

Para este segundo ejemplo necesitamos un servidor de aplicaciones compatible con la especificación completa de Java EE 6. No basta con un servidor compatible con el perfil Web.

3.3. Entity manager y contexto de persistencia en los beans

Todas las acciones efectivas sobre las base de datos se realizan a través del entity manager. Es el objeto que se encarga de realizar las consultas y actualizaciones en las tablas y de mantener sincronizados con la base de datos los objetos entidad que residen en su contexto de persistencia.

Recordemos que el contexto de persistencia de un entity manager está formado por todos los objetos entidad gestionadas por el entity manager y que representan una caché de primer nivel de los datos.

El entity manager se encarga de propagar a la base de datos los cambios realizados en los objetos entidad haciendo un flush de forma automática cuando la transacción termina o antes de ejecutar una query.

En JPA gestionado por el contenedor la forma normal de obtener el entity manager es usando la inyección de dependencias:

@PersistenceContext(unitName = "mensajes-ejbPU")
EntityManager em;

El atributo unitName indica la unidad de persistencia con la que se conecta el entity manager.

Esta inyección de dependencias hay que hacerla en un objeto gestionado al que el contenedor tenga acceso. Lo habitual es hacerla en el bean de sesión que define la interfaz de negocio.

Vamos a empezar definiendo un método de negocio muy sencillo. El método nuevoAutor que añade un autor a la base de datos.

AutorService.java
package org.expertojava.ejb;

import javax.ejb.Stateless;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import java.util.Date;
import java.util.List;
import java.util.Set;

@Stateless
public class AutorServicio {

    @PersistenceContext(unitName = "mensajes")
    EntityManager em;

    public Autor nuevoAutor(String correo, String nombre) {
        Autor autor = new Autor(nombre, correo);
        em.persist(autor);
        return autor;
    }
}

El contenedor es el encargado de inyectar el entity manager. El contenedor decidirá si debe crear un entity manager nuevo o si debe utilizar uno ya existente dependiendo del atributo type de la anotación @PersistenceContext.

El valor por defecto del atributo type es PersistenceContextType.TRANSACTION:

@PersistenceContext(type=PersistenceContextType.TRANSACTION)
EntityManager em;

En los entity managers de tipo TRANSACTION se inyecta el mismo entity manager en todos los DAO que comparten la transacción. Esto significa que los distintos DAOs estarán compartiendo también el mismo contexto de persistencia y aprovechando la caché de entidades gestionadas.

El otro tipo de gestión de la vida del entity manager se utiliza con los beans de sesión con estado. El contexto de persistencia se mantiene abierto mientras que existe el bean con estado y las entidades permanecen gestionadas a lo largo de múltiples transacciones. Es lo que se denomina contexto de persistencia extendido:

@PersistenceContext(type=PersistenceContextType.EXTENDED)
EntityManager em;

3.4. Ejemplo completo

Los beans de sesión sin estado son, por sus características de transaccionalidad y seguridad, los componentes idóneos para implementar la capa de negocio de una aplicación.

Podemos comprobar su funcionamiento en el siguiente ejemplo, en el que completamos la aplicación web mensajes con la que hemos comenzado esta sesión.

El bean sin estado AutorServicio define los métodos de negocio. Obtiene un entity manager por inyección de dependencias.

AutorServicio.java
package org.expertojava.ejb;

import javax.ejb.Stateless;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import java.util.Date;
import java.util.List;
import java.util.Set;

@Stateless
public class AutorServicio {

    @PersistenceContext(unitName = "mensajes")
    EntityManager em;

    private static final String all_autores = "SELECT a FROM Autor a";

    public Autor nuevoAutor(String correo, String nombre) {
        Autor autor = new Autor(nombre, correo);
        em.persist(autor);
        return autor;
    }

    public Mensaje nuevoMensaje(String texto, Long idAutor) {
        Autor autor = em.find(Autor.class, idAutor);
        if (autor == null) {
           throw new RuntimeException("No existe autor");
        } else {
            Mensaje mens = new Mensaje(texto, autor);
            mens.setFecha(new Date());
            mens.setAutor(autor);
            em.persist(mens);
            return mens;
        }
    }

    public Set<Mensaje> listaMensajes(Long idAutor) {
        Autor autor = em.find(Autor.class, idAutor);
        if (autor == null) {
            throw new RuntimeException("No existe autor");
        } else {
            Set<Mensaje> listaMensajes = autor.getMensajes();
            listaMensajes.size();
            return listaMensajes;
        }
    }

    public List<Autor> listaAutores() {
        return em.createQuery(all_autores).getResultList();
    }
}

Los siguientes servlets muestran cómo definir la capa de controlador de la aplicación que recibe peticiones del usuario y las convierte en llamadas a los métodos de negocio. Más adelante veremos que es posible sustituir esta capa por los controladores de un API RESTful.

Los servlets son:

  • NuevoMensajeServlet: añade un nuevo mensaje a un autor

  • NuevoAutorMensajeServlet: añade un autor y un mensaje en una única transacción. Es un ejemplo que muestra cómo es posible utilizar la propagación de transacciones en los métodos de negocio.

  • ListaMensajesServlet: devuelve los mensajes de un autor

  • ListaAutoresServlet: devuelve todos los autores

NuevoMensajeServlet.java
@WebServlet(name = "nuevomensaje", urlPatterns = "/nuevomensaje")
public class NuevoMensajesServlet extends HttpServlet {

    @EJB
    AutorServicio autorServicio;

    protected void doPost(HttpServletRequest request,
                          HttpServletResponse response)
            throws ServletException, IOException {

    }

    protected void doGet(HttpServletRequest request,
                         HttpServletResponse response)
            throws ServletException, IOException {
        response.setContentType("text/html");

        String texto = request.getParameter("texto");
        Integer id = Integer.parseInt(request.getParameter("idAutor"));

        PrintWriter out = response.getWriter();
        Mensaje mens = autorServicio.nuevoMensaje(texto, id.longValue());
        out.println("<!DOCTYPE HTML PUBLIC \"" +
                "-//W3C//DTD HTML 4.0 " +
                "Transitional//EN\">");
        out.println("<HTML>");
        out.println("<BODY>");
        out.println("<h1>Mensaje</h1>");
        out.println("<p>" + mens.toString() + "</p>");
        out.println("</BODY>");
        out.println("</HTML");
    }
}
NuevoAutorMensaje.java
@WebServlet(name = "nuevoautor", urlPatterns = "/nuevoautor")
public class NuevoAutorMensajeServlet extends HttpServlet {

    @EJB
    AutorServicio autorServicio;
    @Resource
    UserTransaction tx;

    protected void doPost(HttpServletRequest request,
                          HttpServletResponse response)
            throws ServletException, IOException {

    }

    protected void doGet(HttpServletRequest request,
                         HttpServletResponse response)
            throws ServletException, IOException {

        response.setContentType("text/html");

        String nombre = request.getParameter("nombre");
        String correo = request.getParameter("correo");
        String texto = request.getParameter("texto");

        PrintWriter out = response.getWriter();
        try {
            tx.begin();
            Autor autor = autorServicio.nuevoAutor(correo, nombre);
            autorServicio.nuevoMensaje(texto, autor.getId());
            out.println("<!DOCTYPE HTML PUBLIC \"" +
                    "-//W3C//DTD HTML 4.0 " +
                    "Transitional//EN\">");
            out.println("<HTML>");
            out.println("<BODY>");
            out.println("<h1>Añadido autor y mensaje</h1>");
            out.println("<p>" + autor.toString() + "<p>");
            out.println("</BODY>");
            out.println("</HTML");
            tx.commit();
        } catch (Exception e) {
            try {
                tx.rollback();
            } catch (SystemException e1) {
                e1.printStackTrace();
                throw new RuntimeException(e1);
            }
            throw new RuntimeException(e);
        }
    }
}
ListaMensajesServlet.java
@WebServlet(name = "listamensajes", urlPatterns = "/listamensajes")
public class ListaMensajesServlet extends HttpServlet {

    @EJB
    AutorServicio autorServicio;

    protected void doPost(HttpServletRequest request,
                          HttpServletResponse response)
            throws ServletException, IOException {

    }

    protected void doGet(HttpServletRequest request,
                         HttpServletResponse response)
            throws ServletException, IOException {
        response.setContentType("text/html");

        Integer id = Integer.parseInt(request.getParameter("idAutor"));

        PrintWriter out = response.getWriter();
        Set<Mensaje> mensajes =
                autorServicio.listaMensajes(id.longValue());
        out.println("<!DOCTYPE HTML PUBLIC \"" +
                "-//W3C//DTD HTML 4.0 " +
                "Transitional//EN\">");
        out.println("<HTML>");
        out.println("<BODY>");
        out.println("<h1>Mensajes</h1>");
        out.println("<ul>");
        for (Mensaje m : mensajes) {
            out.println("<li>" + m.toString() + "</li>");
        }
        out.println("</ul>");
        out.println("</BODY>");
        out.println("</HTML");
    }
}
ListaAutoresServlet.java
@WebServlet(name="listaautores", urlPatterns="/listaautores")
public class ListaAutoresServlet extends HttpServlet {

    @EJB
    AutorServicio autorServicio;

    protected void doPost(HttpServletRequest request,
                          HttpServletResponse response)
            throws ServletException, IOException {

    }

    protected void doGet(HttpServletRequest request,
                         HttpServletResponse response)
            throws ServletException, IOException {
        response.setContentType("text/html");

        PrintWriter out = response.getWriter();
        List<Autor> autores = autorServicio.listaAutores();
        out.println("<!DOCTYPE HTML PUBLIC \"" +
                "-//W3C//DTD HTML 4.0 " +
                "Transitional//EN\">");
        out.println("<HTML>");
        out.println("<BODY>");
        out.println("<h1>Mensajes</h1>");
        out.println("<ul>");
        for (Autor a : autores) {
            out.println("<li>" + a.toString() + "</li>");
        }
        out.println("</ul>");
        out.println("</BODY>");
        out.println("</HTML");
    }
}

Por último, el siguiente JSP es el que recoge los datos mediante formularios y realiza las invocaciones a los servlets correspondientes.

index.jsp
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<html>
<head>
    <title>Start Page</title>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
</head>
<body>

<h1>¡Mensajes!</h1>

<h3>Nuevo autor y mensaje</h3>

<form action="<%=request.getContextPath()%>/nuevoautor">
    <p>Correo: <input type="text" name="correo"></p>
    <p>Nombre: <input type="text" name="nombre"></p>
    <p>Texto: <input type="text" name="texto"></p>
    <input type="submit" value="Enviar">
</form>

<h3>Nuevo mensaje</h3>

<form action="<%=request.getContextPath()%>/nuevomensaje">
    <p>Texto: <input type="text" name="texto"></p>
    <p>Autor id: <input type="text" name="idAutor"></p>
    <input type="submit" value="Enviar">
</form>

<h3>Lista mensajes</h3>

<form action="<%=request.getContextPath()%>/listamensajes">
    <p>Autor id: <input type="text" name="idAutor"></p>
    <input type="submit" value="Enviar">
</form>

<h3>Lista autores</h3>


<form action="<%=request.getContextPath()%>/listaautores">
    <input type="submit" value="Enviar">
</form>


</body>
</html>

Un ejemplo de test Arquillian de la operación de negocio que crea un nuevo autor:

SaludoServicioTest.java
@RunWith(Arquillian.class)
public class SaludoServicioTest {

    @EJB
    private AutorServicio autorServicio;

    @Deployment
    public static Archive<?> deployment() {
        return ShrinkWrap.create(WebArchive.class)
                .addPackage(Autor.class.getPackage())
                .addAsResource("META-INF/persistence.xml");
    }

    @Test
    public void deberiaDevolverNuevoAutor() {
        String correo = "pedro.picapiedra@gmail.com";
        String nombre = "Pedro Picapiedra";
        Autor autor = autorServicio.nuevoAutor(correo, nombre);
        assertTrue(autor.getCorreo().equals(correo) &&
                   autor.getNombre().equals(nombre));
    }
}

3.5. Ejercicios

3.5.1. (3,5 puntos) Aplicación web filmoteca

Crea la aplicación web filmoteca con todas las entidades y DAOs que hayas definido en los ejercicios de JPA. Convierte las clases PeliculaServicio y ActorServicio en beans de sesión y crea un mínimo de 4 servlets que prueben su funcionamiento. Define también un test Arquilian como mínimo.