Proyecto de Integración
 

Creación de entidades de dominio con JPA

Introducción

El objetivo de esta sesión es añadir al proyecto jbib-modelo las clases y anotaciones necesarias para convertir las clases de dominio en entidades persistentes basadas en JPA. Adelantando algunos detalles, en esta sesión deberemos desarrollar los siguientes elementos:

  • Añadir la configuración de JPA al proyecto jbib-modelo:
    • Dependencias de Hibernate en el fichero de configuración de Maven pom.xml
    • Fichero de configuración de la unidad de persistencia JPA META-INF/persistence.xml
    • Esquema de base de datos biblioteca_test
  • Añadir anotaciones en las clases de dominio para mapearlas en tablas
  • Desarrollar las clases EAO que proporcionan una capa de abstracción para la recuperación, modificación y borrado de las entidades
  • Comprobar y ampliar tests que comprueben el funcionamiento de las entidades y de los EAO

Vamos a ello paso a paso.

Configuración de JPA

En primer lugar debes añadir en el fichero de Maven pom.xml las dependencias necesarias para trabajar con JPA/Hibernate. Las puedes encontrar a continuación:

   <repositories>
      <repository>
         <id>JBoss</id>
         <name>JBoss Repsitory</name>
         <layout>default</layout>
         <url>http://repository.jboss.org/nexus/content/groups/public/</url>
      </repository>
   </repositories>

   <dependencies>

      <!-- MySql Connector -->
      <dependency>
         <groupId>mysql</groupId>
         <artifactId>mysql-connector-java</artifactId>
         <version>5.1.17</version>
         <scope>runtime</scope>
      </dependency>

      <!-- slf4j -->
      <dependency>
         <groupId>org.slf4j</groupId>
         <artifactId>slf4j-log4j12</artifactId>
         <version>1.5.8</version>
         <scope>runtime</scope>
      </dependency>

      <!-- Hibernate -->
      <dependency>
         <groupId>org.hibernate</groupId>
         <artifactId>hibernate-entitymanager</artifactId>
         <version>3.5.6-Final</version>
      </dependency>
   </dependencies>

Añade los siguientes líneas al fichero de propiedades de log4j.properties. Se pueden descomentar para tener más detalle en el log de Hibernate:

# 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

Continua creando el fichero de configuración de JPA META-INF/persistence.xml con el siguiente contenido:

<?xml version="1.0" encoding="UTF-8"?>
<persistence version="1.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/persistence_1_0.xsd">
 <persistence-unit name="proyint_test" transaction-type="RESOURCE_LOCAL">
   <provider>org.hibernate.ejb.HibernatePersistence</provider>
   <class>org.especialistajee.jbib.model.UsuarioDomain</class>
   <class>org.especialistajee.jbib.model.AlumnoDomain</class>
   <class>org.especialistajee.jbib.model.BibliotecarioDomain</class>
   <class>org.especialistajee.jbib.model.ProfesorDomain</class>
   <class>org.especialistajee.jbib.model.LibroDomain</class>
   <class>org.especialistajee.jbib.model.MultaDomain</class>
   <class>org.especialistajee.jbib.model.OperacionDomain</class>
   <class>org.especialistajee.jbib.model.ActivaDomain</class>
   <class>org.especialistajee.jbib.model.HistoricaDomain</class>
   <class>org.especialistajee.jbib.model.PrereservaDomain</class>
   <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_test" />
        <property name="hibernate.hbm2ddl.auto" value="create" />
        <property name="hibernate.show_sql" value="true" />
   </properties>
 </persistence-unit>
</persistence>

Verás que se declaran todas las entidades que vamos a hacer persistentes y la configuración de la conexión a la base de datos biblioteca_test. Es importante hacer notar que el valor de hibernate.hbm2ddl.auto está puesto a create. Esto significa que el esquema de base de datos se creará de nuevo en cada ejecución con las características definidas en las anotaciones.

Nota
En esta sesión vamos a crear el esquema inicial de base de datos y lo poblaremos con datos creados a mano en los tests. Este esquema nos va a servir para validar el modelo definido por las clases de dominio.

Por último, crea un esquema vacío de base de datos con el nombre biblioteca_test usando el programa MySQLAdministrator.

Anotaciones de las clases de dominio

Debes añadir las anotaciones necesarias en las clases de dominio para mapear las entidades con las tablas de la base de datos. Los tipos de los campos deben corresponder con los definidos en las clases.

El siguiente esquema resume el nombres requerido para las tablas y columnas, sus claves primarias y ajenas y valores de los campos enumerados:

  • libro
    • idLibro (PK)
    • autor
    • fechaAlta
    • isbn
    • numPaginas
    • titulo

  • operacion
    • idOperacion (PK)
    • estadoOperacion (enum: ACTIVA, HISTORICA)
    • fechaInicio
    • fechaFin
    • fechaFinReal
    • tipoOperacion (enum: PRESTAMO, RESERVA)
    • usuario_idUsuario (FK)
    • libro (FK)

  • usuario
    • idUsuario (PK)
    • tipoUsuario (enum: ALUMNO, BIBLIOTECARIO, PROFESOR)
    • login
    • password
    • nombre
    • apellido1
    • apellido2
    • email
    • estadoUsuario (enum: ACTIVO, MOROSO)
    • ciudad
    • codigoPostal
    • calle
    • numero
    • piso
    • tutor (de la entidad AlumnoDomain)
    • departamento (de la entidad ProfesorDomain)

  • multa
    • idMulta (PK)
    • estadoMulta (enum: ACTIVA, HISTORICA)
    • fechaFin
    • fechaInicio
    • idUsuario (FK)

  • prereserva
    • idOperacion (PK)
    • fechaInicio
    • libro (FK)
    • usuario_idUsuario (FK)

Para ello hay que añadir las anotaciones de JPA a las siguientes clases en el paquete org.especialistajee.jbib.model:

  • LibroDomain
  • MultaDomain
  • PrereservaDomain
  • UsuarioDomain (abstracta) y su relación de herencia con BibliotecarioDomain, AlumnoDomain y ProfesorDomain
  • OperacionDomain (abstracta) y su relación de herencia con ActivaDomain y HistoricaDomain

Listamos a continuación como ejemplo las clases LibroDomain, OperacionDomain y sus clases hijas ActivaDomain y HistoricaDomain:

Clase LibroDomain:

package org.especialistajee.jbib.model;

// imports

@Entity
@Table(name="libro")
@NamedQueries( {
    @NamedQuery(name = "LibroDomain.findAll",
          query = "SELECT l FROM LibroDomain l")})  
public class LibroDomain extends DomainObject {
   private static final long serialVersionUID = 1L;
   
    @Id @Column(name="idLibro")
    @GeneratedValue(strategy=GenerationType.AUTO)
    Long id;
   private String isbn;
   private String titulo;
   private String autor;
   private Integer numPaginas;
   private Date fechaAlta;
   @OneToMany(mappedBy = "libro")
   private Set<HistoricaDomain> historicas = 
      new HashSet<HistoricaDomain>();
   @OneToOne(mappedBy = "libro")
   private ActivaDomain activa;
   @OneToOne(mappedBy = "libro")
   private PrereservaDomain prereserva;

   // hashCode y equals

    public LibroDomain() {}

    public LibroDomain(String isbn) {
            this.isbn = isbn;
    }

    // getters y setters
   
   public void addOperacionHistorica(HistoricaDomain historica) {
      if (historica == null)
         throw new IllegalArgumentException("Null operacion");
      historicas.add(historica);
      // se actualiza la parte inversa de la relación
      historica.setLibro(this);
   }
   
   public EstadoLibro getEstado() {
      if (prereserva != null)
         return EstadoLibro.PRE_RESERVADO;
      if (activa == null && prereserva == null)
         return EstadoLibro.DISPONIBLE;
      if (activa != null && prereserva == null && 
            activa.getTipo().equals(TipoOperacion.RESERVA))
         return EstadoLibro.RESERVADO_EN_SALA;
      if (activa != null && prereserva == null &&
            (activa.getTipo().equals(TipoOperacion.PRESTAMO) ||
             activa.getTipo().equals(TipoOperacion.DEPOSITO)))
         return EstadoLibro.PRESTADO;
      throw new BibliotecaException("Estado libro inconsistente");
   }
}

Añadimos la anotación @Entity, la anotación @Table para especificar el nombre de la tabla y anotaciones @Column para el nombre de ciertas columnas. Añadimos también la anotación @Id para definir la clave primaria y @OneToMany para las relaciones uno-a-muchos con otras entidades. Por último, añadimos una consulta JPA que devuelve todos los libros.

Clase OperacionDomain:

package org.especialistajee.jbib.model;

// imports

@Entity
@Table(name="operacion")
@Inheritance(strategy=InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name="estadoOperacion",
                discriminatorType=DiscriminatorType.STRING,
                columnDefinition="enum('ACTIVA','HISTORICA')")
@org.hibernate.annotations.ForceDiscriminator
@NamedQueries( {
    @NamedQuery(name = "OperacionDomain.findAll",
          query = "SELECT o FROM OperacionDomain o")})  
public abstract class OperacionDomain extends DomainObject {
   private static final long serialVersionUID = 1L;
    @Id @Column(name="idOperacion")
    @GeneratedValue(strategy=GenerationType.AUTO)
   private Long id;
    @ManyToOne
    @JoinColumn(nullable=false)
    protected UsuarioDomain usuario;
    @Enumerated(EnumType.STRING)
    @Column(name="tipoOperacion",
                    columnDefinition="enum('RESERVA','PRESTAMO','DEPOSITO')",
                    nullable=false)
   protected TipoOperacion tipo;
    @Column(nullable=false)
   protected Date fechaInicio;
    protected Date fechaFin;
     
   // hashCode, equals, getters y setters
   
   public abstract EstadoOperacion getEstado();
   public abstract void setLibro(LibroDomain libro);
   public abstract LibroDomain getLibro();
}

Además de las anotaciones similares a las de Libro debemos añadir las anotaciones que mapean la relación de herencia con la anotación @Inheritance. Usamos la estrategia de tabla única. La columna discriminante la definimos con la anotación @DiscriminatorColumn. Definimos que su nombre es estadoOperacion y que los valores en la columna van ser STRING. También especificamos con @ColumnDefinition que la definición de la columna en el esquema de la base de datos va a ser un valor enumerado que puede tomar únicamente los valores ACTIVA e HISTORICA. La utilidad de esto es que la base de datos sólo permitirá esos valores en la columna, lanzando un error si se intenta actualizar con otro valor.

La definición de @ColumnDefinition para valores enumerados la debemos usar para todos los mapeos de tipos enumerados en todas las clases.

Por último, utilizamos el modificador nullable = false en todos los campos que queremos mapear a columnas NOT NULL. Es recomendable definir en la entidad un constructor que contenga todos los campos definidos como no nulos. De esta forma se obliga a proporcionar esos valores en su creación.

Por último, la anotación @org.hibernate.annotations.ForceDiscriminator es necesaria para que Hibernate pueda construir correctamente objetos de las clases hijas a partir de consultas realizadas sobre la clase padre. En este caso, la consulta OperacionDomain.findAll va devolver todos los objetos ActivaDomain y HistoricaDomain existentes en la base de datos.

Clase ActivaDomain:

package org.especialistajee.jbib.model;

// imports

@Entity 
@DiscriminatorValue(value="ACTIVA")
public class ActivaDomain extends OperacionDomain {
   private static final long serialVersionUID = 1L;
   
    @OneToOne
    @JoinColumn(name="libro")
   private LibroDomain libro;
   
   public ActivaDomain() {}
   
   public ActivaDomain(LibroDomain libro, UsuarioDomain usuario, 
         TipoOperacion tipo, Date fecha) {
      this.libro = libro;
      this.usuario = usuario;
      this.tipo = tipo;
      this.fechaInicio = fecha;
      usuario.addOperacion(this);
      libro.setActiva(this);   
   }

   public EstadoOperacion getEstado() {
      return EstadoOperacion.ACTIVA;
   }
   
   public LibroDomain getLibro() {
      return libro;
   }

   public void setLibro(LibroDomain libro) {
      this.libro = libro;
   }
}

Clase HistoricaDomain:

package org.especialistajee.jbib.model;

// imports

@Entity @DiscriminatorValue(value="HISTORICA")
public class HistoricaDomain extends OperacionDomain {

   private static final long serialVersionUID = 1L;
   private Date fechaFinReal;

    @ManyToOne
    @JoinColumn(name="libro")
   protected LibroDomain libro;
   
   public HistoricaDomain() {}
   
   public HistoricaDomain(ActivaDomain activa, Date fechaFinReal) {
      this.usuario = activa.getUsuario();
      this.libro = activa.getLibro();
      this.tipo = activa.getTipo();
      this.fechaInicio = activa.getFechaInicio();
      this.fechaFin = activa.getFechaFin();
      this.fechaFinReal = fechaFinReal;
      usuario.addOperacion(this);
      libro.addOperacionHistorica(this);
   }

   public EstadoOperacion getEstado() {
      return EstadoOperacion.HISTORICA;
   }
   
   public void setFechaFinReal(Date fechafinreal) {
      this.fechaFinReal = fechafinreal;
   }

   public Date getFechaFinReal() {
      return fechaFinReal;
   }
   
   public LibroDomain getLibro() {
      return libro;
   }

   public void setLibro(LibroDomain libro) {
      this.libro = libro;
   }
}

Lo único que hay que definir en las clases hijas es la anotación @DiscriminatorValue que define el valor de la columna discriminante que define objetos de cada entidad. También hay que hacer notar que el mapeo de la relación inversa con LibroDomain especifica el mismo nombre libro en ambas clases. De esta forma conseguimos que en la tabla se utilice una única columna libro para guardar la clave ajena al libro de la operación.

Tests de las entidades

Para comprobar que el funcionamiento de las entidades es correcto, utiliza el siguiente test. Lee el código y comprueba que está creando una serie de entidades al comienzo y después realizando una serie de consultas y modificaciones sobre esas entidades creadas:

package org.especialistajee.jbib.model;

import static org.junit.Assert.assertTrue;

import java.util.Date;
import java.util.Set;

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

import org.junit.BeforeClass;
import org.junit.Test;

public class EntityTest {

   static EntityManagerFactory emf = null;
   static Long alumnoId, libroId = null;

   @BeforeClass
   // Activamos la unidad de persistencia de test para todos los tests
   // y poblamos los datos
   public static void initialize() {
      emf = Persistence.createEntityManagerFactory("proyint_test");
      EntityManager em = emf.createEntityManager();
      em.getTransaction().begin();

      LibroDomain libro = new LibroDomain("0321482751");
      libro.setAutor("Alistair Cockburn");
      libro.setTitulo("Agile Software Development");
      libro.setNumPaginas(467);
      em.persist(libro);

      AlumnoDomain alumno = new AlumnoDomain("alu", "alu");
      alumno.setDireccion(new Direccion("Rambla", 10, "4 E",
            "Alicante", "03002"));
      em.persist(alumno);

      ActivaDomain activa = new ActivaDomain(libro, alumno,
            TipoOperacion.PRESTAMO, new Date());
      em.persist(activa);

      PrereservaDomain prereserva = new PrereservaDomain(libro,
            alumno, new Date());
      em.persist(prereserva);

      HistoricaDomain hist1 = new HistoricaDomain(activa, new Date());
      System.out.println(hist1.getLibro().getIsbn());
      em.persist(hist1);

      HistoricaDomain hist2 = new HistoricaDomain(activa, new Date());
      em.persist(hist2);

      // Tests para comprobar que las relaciones se han actualizado en memoria

      assertTrue(alumno.getOperaciones().size() == 3);
      assertTrue(libro.getHistoricas().contains(hist2));
      assertTrue(libro.getActiva().getUsuario().equals(alumno));

      em.getTransaction().commit();
      em.close();

      alumnoId = alumno.getId();
      libroId = libro.getId();

   }

   @Test
   public void testFindLibro() {
      EntityManager em = emf.createEntityManager();
      em.getTransaction().begin();
      Query query = em.createQuery("SELECT l FROM LibroDomain l "
            + "WHERE l.isbn = :isbn");
      query.setParameter("isbn", "0321482751");
      LibroDomain libro = (LibroDomain) query.getSingleResult();
      assertTrue(libro.getAutor().equals("Alistair Cockburn")
            && libro.getTitulo().equals("Agile Software Development")
            && libro.getNumPaginas() == 467);
      em.getTransaction().commit();
      em.close();
   }

   @Test
   public void testFindAlumno() {
      EntityManager em = emf.createEntityManager();
      em.getTransaction().begin();
      Query query = em.createQuery("SELECT a FROM AlumnoDomain a "
            + "WHERE a.login = :login");
      query.setParameter("login", "alu");
      AlumnoDomain alumno = (AlumnoDomain) query.getSingleResult();
      assertTrue(alumno.getTipo().equals(TipoUsuario.ALUMNO)
            && alumno.getLogin().equals("alu")
            && alumno.getPassword().equals("alu")
            && alumno.getDireccion().getCiudad().equals("Alicante"));
      em.getTransaction().commit();
      em.close();
   }

   @Test
   public void testFindOperacionesUsuario() {
      EntityManager em = emf.createEntityManager();
      em.getTransaction().begin();
      AlumnoDomain alumno = em.find(AlumnoDomain.class, alumnoId);
      Set<OperacionDomain> operaciones = alumno.getOperaciones();
      assertTrue(operaciones.size() == 3);
      int numHistoricas = 0;
      int numActivas = 0;
      for (OperacionDomain operacionDomain : operaciones) {
         if (operacionDomain.getEstado() == EstadoOperacion.HISTORICA)
            numHistoricas++;
         else if (operacionDomain.getEstado() == EstadoOperacion.ACTIVA)
            numActivas++;
      }
      Set<PrereservaDomain> preReservas = alumno.getPreservas();
      int numPrereservas = preReservas.size();
      assertTrue(numHistoricas == 2 && numActivas == 1
            && numPrereservas == 1);
      em.getTransaction().commit();
      em.close();
   }

   @Test
   public void testFindOperacionesLibro() {
      EntityManager em = emf.createEntityManager();
      em.getTransaction().begin();
      LibroDomain libro = em.find(LibroDomain.class, libroId);
      assertTrue(libro.getHistoricas().size() == 2
            & libro.getActiva() != null);
      em.getTransaction().commit();
      em.close();
   }

   @Test
   public void testFindMultasUsuario() {
      // completar
   }
}

Completa el test incluyendo algunas multas en la creación de entidades y comprobando su funcionamiento completando el código del testFindMultasUsuario.

También puedes utilizar el programa MySQL Query Browser para comprobar las tablas y los datos resultantes de las actualizacione.

Clases 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
  • PrereservaEao
  • MultaEao

Por ahora vamos a definir únicamente las funciones básicas de creación, actualización y borrado, y también la consulta que devuelve todas las entidades. En la siguiente sesión incluiremos en los EAO más consultas.

Como ejemplo listamos la clase OperacionEao:

package org.especialistajee.jbib.eao;

import java.util.List;

import javax.persistence.EntityManager;
import javax.persistence.Query;

import org.especialistajee.jbib.model.OperacionDomain;

public class OperacionEao {
   private EntityManager em;

   public OperacionEao(EntityManager em) {
      this.em = em;
   }

   public EntityManager getEntityManger() {
      return em;
   }

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

   public OperacionDomain find(Long opId) {
      return em.find(OperacionDomain.class, opId);
   }

   public OperacionDomain create(OperacionDomain op) {
      em.persist(op);
      em.flush();
      em.refresh();
      return op;
   }

   public OperacionDomain update(OperacionDomain op) {
      return em.merge(op);
   }

   public void delete(Long opId) {
      OperacionDomain op = em.getReference(OperacionDomain.class, opId);
      em.remove(op);
   }

   @SuppressWarnings("unchecked")
   public List<OperacionDomain> findAllOperacions() {
      Query query = em.createNamedQuery("OperacionDomain.findAll");
      return query.getResultList();
   }
}

Tests de los EAO

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.EaoTest:

package org.especialistajee.jbib.eao;

// imports

public class EaoTest {

   static EntityManagerFactory emf = null;
   static Long operacionId, libroId, usuarioId = null;

   @BeforeClass
   public static void initialize() {
      emf = Persistence.createEntityManagerFactory("proyint_test");
      EntityManager em = emf.createEntityManager();
      LibroEao lEao = new LibroEao(em);
      UsuarioEao uEao = new UsuarioEao(em);
      OperacionEao oEao = new OperacionEao(em);
      PrereservaEao preEao = new PrereservaEao(em);

      em.getTransaction().begin();

      LibroDomain libro = new LibroDomain("0321127420");
      libro.setAutor("Martin Fowler");
      libro.setTitulo("Patterns Of Enterprise Application Architecture");
      libro.setNumPaginas(533);

      libro = lEao.create(libro);
      libroId = libro.getId();

      LibroDomain libro2 = new LibroDomain("0321278658");
      libro2.setAutor("Kent Beck");
      libro2.setTitulo("Extreme Programming Explained - Embrace Change");
      libro2.setNumPaginas(189);

      libro2 = lEao.create(libro2);

      LibroDomain libro3 = new LibroDomain("0131401572");
      libro3.setAutor("Clifton Nock");
      libro3.setTitulo("Data Access Patterns");
      libro3.setNumPaginas(512);

      libro3 = lEao.create(libro3);

      UsuarioDomain usuario = new AlumnoDomain("alu1", "alu1");
      usuario.setNombre("Pilar");
      usuario.setApellido1("Torralba");
      usuario.setApellido2("Pérez");

      usuario = uEao.create(usuario);
      usuarioId = usuario.getId();

      UsuarioDomain usuario2 = new AlumnoDomain("alu2", "alu2");
      usuario2.setNombre("Antonio");
      usuario2.setApellido1("Martínez");
      usuario2.setApellido2("Salvador");

      usuario2 = uEao.create(usuario2);

      ActivaDomain op = new ActivaDomain(libro, usuario,
            TipoOperacion.PRESTAMO, new Date());
      op = (ActivaDomain) oEao.create(op);
      operacionId = op.getId();

      ActivaDomain op2 = new ActivaDomain(libro2, usuario,
            TipoOperacion.PRESTAMO, new Date());
      oEao.create(op2);

      ActivaDomain op3 = new ActivaDomain(libro3, usuario,
            TipoOperacion.RESERVA, new Date());
      oEao.create(op3);

      PrereservaDomain pre = new PrereservaDomain(libro, usuario2,
            new Date());
      preEao.create(pre);

      em.getTransaction().commit();
      em.close();
   }

   @Test
   public void testUpdate() {
      EntityManager em = emf.createEntityManager();
      em.getTransaction().begin();
      LibroEao eao = new LibroEao(em);
      LibroDomain libro = eao.find(libroId);
      libro.setNumPaginas(590);
      eao.update(libro);
      // El eao gestiona entidades conectadas y no las desconecta tras un
      // update
      assertTrue(em.contains(libro));
      em.getTransaction().commit();
      em.close();

      em = emf.createEntityManager();
      em.getTransaction().begin();
      eao.setEntityManager(em);
      libro = eao.find(libroId);
      assertTrue(libro.getNumPaginas() == 590);
      em.getTransaction().commit();
   }

   @Test
   public void testFindAllLibros() {
      EntityManager em = emf.createEntityManager();
      em.getTransaction().begin();
      LibroEao eao = new LibroEao(em);
      List<LibroDomain> lista = eao.findAllLibros();
      LibroDomain libro = eao.find(libroId);
      assertTrue(lista.size() == 3 && lista.contains(libro));
      em.getTransaction().commit();
   }
}

Añade algún test que compruebe:

  • El cambio de una operación activa a histórica
  • Recuperación de operaciones activas e históricas de un libro
  • Recuperación de las operaciones de un usuario

Entrega

Para la entrega se deberá etiquetar el estado del proyecto con el tag entrega-proyint-jpa-entidades. Recordamos que la dirección del proyecto padre proyint-jbib en el servidor SVN debe ser:

svn+ssh://server.jtech.ua.es/home/svn/<login>/proyint/trunk/proyint-jbib

Como ayuda, proporcionamos la siguiente figura con un ejemplo de estructura de paquetes y clases resultantes de esta sesión.