Proyecto de Integración
 

Persistencia con JPA

Introducción

El objetivo de esta sesión es crear un nuevo proyecto que implementará una capa de persistencia alternativa basada en JPA. De esta forma estamos construyendo dos stacks del proyecto de integración, uno tradicional basado en JDBC y en DAOs y otro basado en JPA. Ambos stacks comparten el proyecto común en el que se definen las clases de dominio y las reglas de negocio.

El trabajo va a consistir básicamente en dos partes. En la primera crearemos el proyecto Maven jbib-persist-jpa, las entidades JPA y realizaremos el mapeado de estas entidades con las tablas de la base de datos ya existente. Los proyectos JPA y JDBC deben compartir el mismo esquema de base de datos. Comprobaremos que el mapeado funciona correctamente con algunos tests de JUnit.

Una vez mapeadas las entidades, deberemos implementar los EAO que definen la interfaz de modificación y acceso a los datos y las consultas. Crearemos en estas clases las mismas consultas que las definidas en el proyecto JDBC. Ampliaremos los tests de Junit para que prueben estos EAO y por último crearemos una clase de negocio (business object) que contenga los métodos de negocio que en la sesión anterior:

  • Listar libros disponibles
  • Listar libros reservados
  • Realizar reserva

Construcción del proyecto Maven

Creamos un proyecto nuevo jbib-persist-jpa con el comando de Maven:

$ cd /home/especialista/proy-int
$ mvn archetype:generate 
-DarchetypeArtifactId=maven-archetype-quickstart 
-DgroupId=org.especialistajee.proyint -DartifactId=jbib-persist-jpa
-Dversion=1.0-SNAPSHOT -Dpackage=org.especialistajee.jbib
	

Podemos borrar las clases autogeneradas por Maven (App y AppTest) y sustituir el fichero POM por este otro que incluye las librerías de Hibernate y el plugin de acceso a base de datos de la sesión anterior. También se incluye en el POM la referencia al proyecto común, en el que se definen las clases de dominio y funciones auxiliares que se van a necesitar en este proyecto:

<!-- jbib-comun -->
<dependency>
  <groupId>org.especialistajee.proyint</groupId>
  <artifactId>jbib-comun</artifactId>
  <version>1.0-SNAPSHOT</version>
  <type>jar</type>
  <scope>compile</scope>
</dependency>

Una vez sustituido el POM, importamos el proyecto recién creado en Eclipse.

Ahora creamos los directorios y ficheros adicionales de configuración de SQL y de JPA.

En primer lugar copiamos los scripts de la sesión anterior de creación de la base de datos de prueba. Igual que en el proyecto anterior, el script biblioteca.sql que crea la base de datos y las tablas lo colocamos en src/main/sql/ y el datos.sql que rellena las tablas con los datos iniciales en src/test/.

Después creamos el directorio src/main/resources en el que colocamos el fichero log4j.properties y la configuración de JPA en el fichero META-INF/persistence.xml:

Fichero log4j.properties

# Direct log messages to stdout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target=System.out
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L - %m%n

# Root logger option
log4j.rootLogger=WARN, stdout

# Hibernate logging options (INFO only shows startup messages)
#log4j.logger.org.hibernate=INFO

# Log JDBC bind parameter runtime arguments
#log4j.logger.org.hibernate.type=ALL

Fichero persistence.xml:

<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/persistence_1_0.xsd"
   version="1.0">
   <persistence-unit name="proyint" transaction-type="RESOURCE_LOCAL">
      <properties>
         <property name="hibernate.dialect" 
	        value="org.hibernate.dialect.MySQLInnoDBDialect" />
         <property name="hibernate.connection.driver_class" 
                   value="com.mysql.jdbc.Driver" />
         <property name="hibernate.connection.username" 
                   value="root" />
         <property name="hibernate.connection.password" 
                   value="especialista" />
         <property name="hibernate.connection.url"
            value="jdbc:mysql://localhost:3306/biblioteca" />
         <property name="hibernate.hbm2ddl.auto" value="validate" />
         <property name="hibernate.show_sql" value="true" />
      </properties>
   </persistence-unit>
</persistence>

Vemos que se define una conexión a base de datos del proyecto de integración creada en la sesión anterior biblioteca y que la propiedad hibernate.hbm2ddl.auto tiene el valor validate para que no se modifique la base de datos.

Mapeando las entidades

Comenzamos mapeando las entidades. Utilizamos como guía las clases de dominio del módulo jbib-comun y las tablas de la base de datos creadas en la sesión anterior. Debemos crear tantas entidades como clases de dominio. De hecho, las clases de dominio (OperacionDomain, etc.) y las entidades (OperacionEntity, etc.) van a ser dos caras del mismo concepto. Las clases de entidad representan objetos de dominio hechos persistentes en la base de datos. Ls clases de dominio van a funcionar como transfer objects desconectados de la base de datos y serán los objetos devueltos por la capa de negocio.

Una diferencia fundamental con los DAO es que en este proyecto la capa de negocio va a trabajar con objetos persistentes gestionados con los EAO y con las transacciones, mientras que en el proyecto jbib-persist-dao son los DAO los que realizan ese trabajo y la capa de negocio trabaja con objetos de dominio desconectados de la base de datos.

Empezamos creando el test org.especialistajee.jbib.entity.PersistenceUnitTest.java que valida la conexión JPA con la base de datos en el directorio src/test/java:

package org.especialistajee.jbib.entity;

import static org.junit.Assert.*;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;

import org.junit.Test;

public class PersistenceUnitTest {

   @Test
   public void createEntityManagerTest() {
      EntityManagerFactory emf = Persistence
            .createEntityManagerFactory("proyint");
      EntityManager em = emf.createEntityManager();
      assertNotNull(em);
      em.close();
   }
}

Probamos que funciona correctamente el test y pasamos a la creación de las entidades. Por cada entidad es conveniente crear una línea class en el fichero de configuración de JPA persistence.xml. Al final quedará así:

<persistence-unit name="proyint" transaction-type="RESOURCE_LOCAL">
   <class>org.especialistajee.jbib.entity.AlumnoEntity</class>
   <class>org.especialistajee.jbib.entity.BibliotecarioEntity</class>
   <class>org.especialistajee.jbib.entity.LibroEntity</class>
   <class>org.especialistajee.jbib.entity.MultaEntity</class>
   <class>org.especialistajee.jbib.entity.OperacionEntity</class>
   <class>org.especialistajee.jbib.entity.ProfesorEntity</class>
   <class>org.especialistajee.jbib.entity.UsuarioEntity</class>
   <properties>
	...

Tenemos que crear las siguientes clases en el paquete org.especialistajee.jbib.entity:

  • LibroEntity
  • OperacionEntity
  • UsuarioEntity (abstracta) y su relación de herencia con BibliotecarioEntity, AlumnoEntity y ProfesorEntity
  • MultaEntity

Listamos a continuación como ejemplo la clase MultaEntity:

package org.especialistajee.jbib.entity;
// imports

@Entity
@Table(name="multa")
public class MultaEntity {
	@Id @Column(name="idMulta")
	@GeneratedValue(strategy=GenerationType.AUTO)
	private int id;
	private Date fechaInicio;
	private Date fechaFin;
	@Enumerated(EnumType.STRING)
	@Column(name="estadoMulta",
	        columnDefinition="enum('ACTIVA','HISTORICO')")
	private EstadoMulta estado;
	@ManyToOne
	@JoinColumn(name="login")
	private UsuarioEntity usuario;
	
	public MultaEntity() {}

   public MultaEntity(MultaDomain datosMulta) {
      this.id = datosMulta.getId();
      this.estado = datosMulta.getEstado();
      this.fechaInicio = datosMulta.getFechaInicio();
      this.fechaFin = datosMulta.getFechaFin();
   }
	
	public MultaDomain obtenerDomain() {
	   MultaDomain datosMulta = new MultaDomain();
      datosMulta.setId(this.id);
	   datosMulta.setEstado(this.estado);
	   datosMulta.setFechaInicio(this.fechaInicio);
	   datosMulta.setFechaFin(this.fechaFin);
	   return datosMulta;
	}
	
   public int getId() {
      return id;
   }

	public Date getFechaInicio() {
		return fechaInicio;
	}

	public void setFechaInicio(Date fechaInicio) {
		this.fechaInicio = fechaInicio;
	}

	public Date getFechaFin() {
		return fechaFin;
	}

	public void setFechaFin(Date fechaFin) {
		this.fechaFin = fechaFin;
	}

	public EstadoMulta getEstado() {
		return estado;
	}

	public void setEstado(EstadoMulta estado) {
		this.estado = estado;
	}

	public UsuarioEntity getUsuario() {
		return usuario;
	}

	public void setUsuario(UsuarioEntity usuario) {
		this.usuario = usuario;
	}
}

Hay que hacer notar algunas cosas:

  • Para realizar el mapeo del atributo estado (de tipo enumerado EstadoMulta) en la columna estadoMulta se utilizan los atributos name para mapear el nombre de la columna y columnDefinition para mapear al tipo de dato enumerado de la columna.
  • Se define el constructor que toma un objeto de dominio y crea la entidad a partir de él. Sólo copia los campos valor, no las relaciones.
  • Al revés: se define un método obtenerDomain que crea un objeto dominio a partir de los datos valor de la entidad.

Creamos la siguiente clase de test en el directorio src/test/java. No funcionará porque faltan entidades por definir:

package org.especialistajee.jbib.entity;

import static org.junit.Assert.*;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;

import org.especialistajee.jbib.model.EstadoOperacion;
import org.especialistajee.jbib.model.EstadoUsuario;
import org.especialistajee.jbib.model.TipoOperacion;
import org.junit.Test;

public class OperacionEntityTest {

	@Test
	public void testFindOperacion(){
	     EntityManagerFactory emf = Persistence.createEntityManagerFactory("proyint");
	     EntityManager em = emf.createEntityManager();
	     OperacionEntity operacion = em.find(OperacionEntity.class, 1);
	     assertTrue(operacion.getEstado() == EstadoOperacion.HISTORICO &&
	    		 operacion.getTipo() == TipoOperacion.PRESTAMO &&
	    		 operacion.getUsuario().getLogin().equals("patricia") &&
	    		 operacion.getLibro().getIsbn().equals("0131401572"));
	     em.close();
	}
	
	@Test
	public void testFindBibliotecario() {
      EntityManagerFactory emf = Persistence.createEntityManagerFactory("proyint");
      EntityManager em = emf.createEntityManager();
	   BibliotecarioEntity bibliotecario = em.find(BibliotecarioEntity.class, "b");
	   assertTrue(bibliotecario.getNombre().equals("b") &&
	              bibliotecario.getEstado() == EstadoUsuario.ACTIVO &&
	              bibliotecario.getEmail().equals("b") &&
	              bibliotecario.getDireccion().getCalle().equals("Bervedere"));
      em.close();
	}
   
   @Test
   public void testFindLibro() {
      EntityManagerFactory emf = Persistence.createEntityManagerFactory("proyint");
      EntityManager em = emf.createEntityManager();
      LibroEntity libro = em.find(LibroEntity.class, "0321482751");
      assertTrue(libro.getAutor().equals("Alistair Cockburn") &&
                 libro.getTitulo().equals("Agile Software Development") &&
                 libro.getNumPaginas() == 467);
      em.close();
   }
}

Definimos el resto de entidades y hacemos que funcionen correctamente los tests. Hay que tener un cuidado especial en la relación de herencia entre UsuarioEntity (abstracta) y sus clases hijas BibliotecarioEntity, AlumnoEntity y ProfesorEntity. Vamos a usar la estrategia de una única tabla y hay que definir correctamente la columna y los valores discriminantes:

Clase UsuarioEntity:

@Entity 
@Table(name="usuario")
@Inheritance(strategy=InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name="tipoUsuario",
                     discriminatorType=DiscriminatorType.STRING, 
                     columnDefinition="enum('ALUMNO','PROFESOR','BIBLIOTECARIO')")
public abstract class UsuarioEntity {
...

Clase AlumnoEntity:

@Entity @DiscriminatorValue(value="ALUMNO")
public class AlumnoEntity extends UsuarioEntity {
...

Construyendo los EAO

Una vez construidas y probadas las entidades debemos crear las clases EAO que abstraerán las operaciones comunes CRUD y las consultas. Las debemos crear en el paquete org.especialistajee.jbib.eao. Creamos las siguientes:

  • LibroEao
  • OperacionEao
  • UsuarioEao

Como ejemplo listamos la clase OperacionEao:

package org.especialistajee.jbib.eao;

// imports

public class OperacionEao {
   private EntityManager em;

   public EntityManager getEntityManger() {
      return em;
   }

   public void setEntityManager(EntityManager em) {
      this.em = em;
   }

   public OperacionEntity find(int idOperacion) {
      return em.find(OperacionEntity.class, idOperacion);
   }

   public OperacionEntity create(OperacionEntity operacion) {
      em.persist(operacion);
      em.flush();
      em.refresh(operacion);
      return operacion;
   }

   public OperacionEntity update(OperacionEntity Operacion) {
      return em.merge(Operacion);
   }

   public void delete(int idOperacion) {
      OperacionEntity Operacion = em.getReference(
            OperacionEntity.class, idOperacion);
      em.remove(Operacion);
   }

   public long countOperacionesUsuarioEstado(String login,
         EstadoOperacion estado) {
      Query query = em
            .createNamedQuery("OperacionEntity.countOperacionesUsuarioEstado");
      query.setParameter("login", login);
      query.setParameter("estado", estado);
      return (Long) query.getSingleResult();
   }

   @SuppressWarnings("unchecked")
   public List<OperacionEntity> findAllOperaciones() {...}

   @SuppressWarnings("unchecked")
   public List<OperacionEntity> findAllOperaciones(
         TipoOperacion tipo) {...}

   @SuppressWarnings("unchecked")
   public List<OperacionEntity> findAllOperaciones(
         EstadoOperacion estado) {...}

   @SuppressWarnings("unchecked")
   public List<OperacionEntity> findAllOperaciones(
         TipoOperacion tipo, EstadoOperacion estado) {...}

   @SuppressWarnings("unchecked")
   public List<OperacionEntity> findOperacionesUsuario(
         String login, EstadoOperacion estado) {...}

   @SuppressWarnings("unchecked")
   public List<OperacionEntity> findOperacionesLibro(
         String isbn, EstadoOperacion estado) {...}
}
Nota
En los apuntes hay un error en el código del EAO. El método create no debe crear la entidad, sino que la recibe como parámetro. La entidad se crea fuera del EAO y así podemos realizar las asociaciones con otras entidades antes de llamar a create.

Listamos también los tests sobre las consultas de clase OperacionEao. Están definidos en el directorio src/test/java y en la clase org.especialistajee.jbib.eao.OperacionEaoTest:

package org.especialistajee.jbib.eao;
// imports 

public class OperacionEaoTest {

   @Test
   public void testFindAllOperaciones() {
      EntityManagerFactory emf = Persistence
            .createEntityManagerFactory("proyint");
      EntityManager em = emf.createEntityManager();
      OperacionEao operacionEao = new OperacionEao();
      operacionEao.setEntityManager(em);
      List<OperacionEntity> lista = operacionEao
            .findAllOperaciones();
      assertEquals(18, lista.size());
      em.close();
   }

   @Test
   public void testFindAllOperacionesTipo() {
      EntityManagerFactory emf = Persistence
            .createEntityManagerFactory("proyint");
      EntityManager em = emf.createEntityManager();
      OperacionEao operacionEao = new OperacionEao();
      operacionEao.setEntityManager(em);
      List<OperacionEntity> lista = operacionEao
            .findAllOperaciones(TipoOperacion.PRESTAMO);
      assertEquals(15, lista.size());
      em.close();
   }

   @Test
   public void testFindAllOperacionesUsuarioEstado() {
      EntityManagerFactory emf = Persistence
            .createEntityManagerFactory("proyint");
      EntityManager em = emf.createEntityManager();
      OperacionEao operacionEao = new OperacionEao();
      operacionEao.setEntityManager(em);
      List<OperacionEntity> lista = operacionEao
            .findOperacionesUsuario("aitor",
                  EstadoOperacion.HISTORICO);
      assertEquals(4, lista.size());
      em.close();
   }
}

Debemos definir las siguientes funciones (y las consultas asociadas precompiladas en las clases entidad):

OperacionEao:

  • long countOperacionesUsuarioEstado(String login, EstadoOperacion estado): cuenta las operaciones de un usuario y un estado determinado
  • List<OperacionEntity> findAllOperaciones(): devuelve todas las operaciones (entity) de la base de datos
  • List<OperacionEntity> findAllOperaciones(TipoOperacion tipo): devuelve todas las operaciones de un tipo determinado
  • List<OperacionEntity> findAllOperaciones(EstadoOperacion estado): devuelve todas las operaciones en un estado determinado
  • findAllOperaciones(TipoOperacion tipo, EstadoOperacion estado): devuelve todas las operaciones de un tipo en un estado (PRESTAMO y ACTIVAs, por ejemplo)
  • findOperacionesUsuario(String login, EstadoOperacion estado): devuelve las operaciones de un usuario y un estado
  • findOperacionesLibro(String isbn, EstadoOperacion estado): devuelve las operaciones de un libro en un estado

LibroEao:

  • List<LibroEntity> findAllLibros(): Devuelve todos los libros de la base de datos
  • List<LibroEntity> findLibrosDisponibles(): Devuelve todos los libros disponibles

Construyendo los BO

Terminamos construyendo la clase de negocio org.especialistajee.jbib.bo.OperacionBo en la que definimos los siguientes métodos:

  • OperacionDomain realizaReserva(String login, String isbn): realiza una reserva del libro isbn para el usuario login. Se supone que se cumplen todos los requisitos: el login es un profesor o alumno activo que no tiene cubierto el cupo de reservas y el libro está disponible. Se lanza una excepción de tipo BibliotecaException si no es así. Devuelve el objeto OperacionDomain con los datos la operación (incluyendo los datos del libro y del )
  • List<LibroDomain> listaLibrosDisponibles(): Devuelve una lista de objetos LibroDomain con los datos de los libros disponibles.
  • List<OperacionDomain> listaReservasActivas(): Devuelve una lista de objetos OperacionDomain con los datos de las reservas activas (incluyendo los datos de los libros y usuarios de cada reserva).

Puedes utilizar el código de la aplicación principal de la sesión pasada para codificar el método realizaReserva. No es necesario capturar las excepciones intermedias; definimos un único try/catch/finally para todo el método. Damos una pista con algo de su código:

/**
 * Prerrequisitos: el login es un profesor o alumno activo que no tiene
 * cubierto el cupo de reservas y el libro está disponible. Se lanza una
 * excepción si no es así.
 * 
 * @param login
 *           - login del usuario
 * @param isbn
 *           - isbn del libro a reservar
 * @return un objeto OperacionDomain con los datos de la operación realizada
 */
public OperacionDomain realizaReserva(String login, String isbn) 
     throws BibliotecaException {

   if (login == null || isbn == null) {
      throw new IllegalArgumentException(
            "Se esperaba un login y un isbn");
   }

   EntityManagerFactory emf = Persistence
         .createEntityManagerFactory("proyint");
   EntityManager em = emf.createEntityManager();
   em.getTransaction().begin();
   try {
      UsuarioEao usuarioEao = new UsuarioEao();
      OperacionEao operacionEao = new OperacionEao();
      LibroEao libroEao = new LibroEao();

      usuarioEao.setEntityManager(em);
      operacionEao.setEntityManager(em);
      libroEao.setEntityManager(em);

      UsuarioEntity usuario = usuarioEao.find(login);

      // Comprobamos que el usuario no sea moroso, y que sea alumno o
      // profesor
      if (usuario == null) {
         em.getTransaction().rollback();
         throw new BibliotecaException(
               "El usuario especificado no existe");
      }
      ...
      // Comprobamos que el usuario no tiene lleno el cupo de reservas
      ...
      // Comprobamos que el libro esta disponible
      ...
      // Comprobamos que el libro no está reservado
      ...
      // Calculamos el numero de dias que tiene validez la reserva
      ...
      // Damos de alta la reserva
      ...
      // Actualizamos las relaciones inversas (no es estrictamente
      // necesario, porque no ya no usamos más esas colecciones)
      ...
      em.getTransaction().commit();
      em.close();

      // Construimos la OperacionDomain que se devuelve
      ...
      return datosOperacion;
   } catch (Exception e) {
      em.getTransaction().rollback();
      throw new BibliotecaException(
            "Error en la reserva", e);
   } finally {
     em.close();
   }
}

A continuación listamos el test sobre el BO y la aplicación de prueba. Ambos en el directorio src/test/java.

Clase org.especialistajee.jbib.bo.OperacionBoTest

package org.especialistajee.jbib.bo;

import static org.junit.Assert.*;

import java.util.List;

import org.especialistajee.jbib.model.LibroDomain;
import org.especialistajee.jbib.model.OperacionDomain;
import org.junit.Test;

public class OperacionBoTest {
	@Test
	public void testListaLibrosDisponibles() {
		OperacionBo operacionBo = new OperacionBo();
		List<LibroDomain> libros = operacionBo.listaLibrosDisponibles();
		assertEquals(8,libros.size());
	}

	@Test
	public void testListaLibrosReservados() {
		OperacionBo operacionBo = new OperacionBo();
		List<LibroDomain> libros = operacionBo.listaLibrosDisponibles();
		assertEquals(8,libros.size());
	}
	
	// Mucho cuidado, porque modifica el estado de la base
	// de datos: se añade una nueva operación activa con el libro
	// y el usuario. El test afecta a otros. Recomendable cambiarlo y utilizar DBUnit.
	@Test
	public void testRealizarReserva() {
		OperacionBo operacionBo = new OperacionBo();
		List<LibroDomain> libros = operacionBo.listaLibrosDisponibles();
		int disponibles = libros.size();
		OperacionDomain operacion = operacionBo.realizaReserva("aitor", "1933988347");
		libros = operacionBo.listaLibrosDisponibles();
		assertEquals(disponibles-1,libros.size());
		assertEquals(operacion.getLibro().getTitulo(), "EJB 3 In Action");
	}
}

Clase main org.especialistajee.jbib.bo.PruebaBo en el directorio src/test/java

package org.especialistajee.jbib.bo;

// imports

public class PruebaBo {

   public static void main(String[] args) {
      // Cargamos por primera vez el entity manager factory al comienzo de la
      // aplicación. Se construyen todas las consultas y se validan las
      // entidades
      @SuppressWarnings("unused")
      EntityManagerFactory emf = Persistence
            .createEntityManagerFactory("proyint");
      BufferedReader br = new BufferedReader(
            new InputStreamReader(System.in));
      PruebaBo prueba = new PruebaBo();
      int opc = -1;

      while (opc != 0) {
         System.out
               .println("\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n");
         System.out
               .print("\t1.- Obtener listado de libros disponibles.\n"
                     + "\t2.- Obtener listado de libros reservados.\n"
                     + "\t3.- Realizar reserva.\n"
                     + "\t0.- Salir.\n"
                     + "\t\tElija una opcion:");
         try {
            opc = Integer.parseInt(br.readLine());
            switch (opc) {
            case (1):
               prueba.listarLibrosDisponibles();
               br.readLine();
               break;
            case (2):
               prueba.listarLibrosReservados();
               br.readLine();
               break;
            case (3):
               prueba.realizaReserva(br);
               br.readLine();
               break;
            }
         } catch (Exception e) {
            e.printStackTrace();
            try {
               br.readLine();
            } catch (IOException e1) {
            }
         }
      }
   }

   public void listarLibrosDisponibles() {
      OperacionBo op = new OperacionBo();
      try {
         List<LibroDomain> lista = op
               .listaLibrosDisponibles();
         for (LibroDomain libro : lista) {
            System.out.print("[" + libro.getIsbn() + "] ");
            System.out.print(libro.getTitulo());
            System.out.println();
         }

      } catch (BibliotecaException e) {
         System.out.println("Error en la operación: ");
         e.printStackTrace();
      }
   }

   public void listarLibrosReservados() {
      OperacionBo op = new OperacionBo();
      try {
         List<OperacionDomain> lista = op
               .listaReservasActivas();
         for (OperacionDomain operacion : lista) {
            LibroDomain libro = operacion.getLibro();
            UsuarioDomain usuario = operacion.getUsuario();

            System.out.print("[" + libro.getIsbn() + "] ");
            System.out.print(libro.getTitulo());
            System.out.print(" (Reservado por "
                  + usuario.getLogin() + ")");
            System.out.println();
         }
      } catch (BibliotecaException e) {
         System.out.println("Error en la operación: ");
         e.printStackTrace();
      }
   }

   public void realizaReserva(BufferedReader br) {
      OperacionBo op = new OperacionBo();
      try {
         System.out.println("ISBN: ");
         String isbn = br.readLine();
         System.out.println("Login usuario: ");
         String login = br.readLine();

         OperacionDomain result = op.realizaReserva(login,
               isbn);
         System.out
               .println("Reserva realizada con identificador "
                     + result.getId());
      } catch (Exception e) {
         System.out.println("Error en la operación: ");
         e.printStackTrace();
      }
   }
}

Entrega

Resumen. Se deberá crear el proyecto jbib-persist-jpa, en el que hay construir los siguientes elementos:

  • Configurar de Maven (POM.xml), configuración de log4j y de JPA persistence.xml.
  • Mapear con las tablas de la base de datos las entidades LibroEntity, OperacionEntity, OperacionActivaEntity, OperacionHistoricoEntity, UsuarioEntity y MultaEntity definiendo las relaciones entre ellas.
  • Definir y probar los tests de JUnit para las entidades.
  • Implementar los EAO LibroEao, OperacionEao y UsuarioEao utilizando JPA y las entidades definidas anteriormente.
  • Definir y probar el test para la clase OperacionEao.
  • Implementar el BO OperacionBo con el método para reservar libro y los listados operaciones reservas y libros disponibles.
  • Definir y probar el test de la clase OperacionBo y la aplicación ejemplo.