Ejemplo sencillo de JPA con Java SE

De ChuWiki

Veamos un pequeño ejemplo de JPA (Java Persistence API) con Java SE (java de escritorio, sin web ni ejbs). Usaremos Apache Derby como base de datos puesto que no es necesaria ninguna instalación. Usaremos tanto EclipseLink como Hibernate como proveedores de persistencia. Puedes ver el código completo del ejemplo en ejemplo-jpa. Vamos a ello.


persistence-api[editar]

Java define una API de persistencia en base de datos, que está en la librería persistence-api.jar. Podemos descargarla, por ejemplo, del repositorio de maven http://mvnrepository.com/artifact/javax.persistence/persistence-api . Sin embargo, la api de oracle/sun en el repositorio de maven sólo llega a la versión 1.0.2. Si queremos usar JPA versión 2 o superior, debemos bajar alguna de las implementaciones de Hibernate, EclipseLink, etc. En nuestro ejemplo pondremos la de eclipselink, así que podemos bajarnos esta http://mvnrepository.com/artifact/org.eclipse.persistence/javax.persistence

Si nuestro proyecto es maven, podemos poner esta dependencia en el fichero pom.xml

		<dependency>
			<groupId>org.eclipse.persistence</groupId>
			<artifactId>javax.persistence</artifactId>
			<version>2.1.0</version>
		</dependency>

Bien, teniendo esta dependencia tenemos varias cosas interesantes para nuestro código.

Anotaciones[editar]

Por un lado, tenemos las anotaciones con las que haremos que nuestras clases sean persistentes. Por ejemplo, si queremos que una clase sea persistente en base de datos, le pondremos la anotación @Entity. A su atributo que haga de clave primaria le pondremos @Id y @GeneratedValue. Esta última anotación indica que la clave primaria debe ser generada automáticamente sin que nosotros nos preocupemos de ello.

Todos los atributos de la clase anotada con @Entity se guardarán a su vez en base de datos, por lo que no es necesario anotarlos uno a uno, salvo que queramos algo especial de ellos, como en el caso del campo que hace de identificador. La siguiente clase es un ejemplo de clase anotada para ser persistente

package com.chuidiang.ejemplos.domain;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;

@Entity
public class Employee {
	@Id
	@GeneratedValue
	private Long id;

	
	private String name;

	public Employee() {}

	public Employee(String name) {
		this.name = name;
	}

	public Long getId() {
		return id;
	}

	public void setId(Long id) {
		this.id = id;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	@Override
	public String toString() {
		return "Employee [id=" + id + ", name=" + name + "]";
	}

}

El fichero persistence.xml[editar]

En JPA debe colocarse un fichero persistence.xml, dentro del directorio META-INF dentro del jar. En este fichero se indican los parámetros de conexión a la base de datos y las clases java que son persistentes, es decir, las clases java a las que hemos puesto anotaciones propias de JPA.

En este fichero se define una persistence-unit con un nombre que debe ser único. A esta persistence-unit se le puede poner un atributo transaction-type, cuyos valores pueden ser RESOURCE_LOCAL o JTA. En el primer caso, nosotros en nuestro código nos debemos encargar de crear el EntityManager (la clase java que se encarga de guardar/recuperar clases persistentes de la base de datos). Si ponemos JTA, nosotros no debemos encargarnos de crear ese EntityManager, alguien nos lo tiene que pasar. Esta última opción sólo tiene sentido si nuestra aplicación corre en un contenedor de aplicaciones, estilo TomEE, Glassfish o JBoss (Apache Tomcat no vale). Ese servidor de aplicaciones será el que cree la clase EntityManager y nos la pase. En nuestro ejemplo, como hablamos de Java SE y no vamos a usar contenedor de aplicaciones, pondremos RESOURCE_LOCAL

<?xml version="1.0" encoding="UTF-8" ?>
<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" xmlns="http://java.sun.com/xml/ns/persistence">

	<persistence-unit name="un.nombre.unico" ransaction-type="RESOURCE_LOCAL">
        ...
        </persistence-unit>
</persistence>

Otra cosa que debemos poner es el proveedor de persistencia. JPA sólo define la API o interfaces que se deben cumplir si se quiere usa JPA, pero no implementa esas clases. Necesitamos una librería externa que implemente esta API. Una de las más conocidas es Hibernate. Para este ejemplo, usaremos dos, Hibernate y EclipseLink.

   <persistence-unit ...>
      <provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>
      ...
   </persistence-unit>
   <persistence-unit ...>
      <provider>org.hibernate.ejb.HibernatePersistence</provider>
      ...
   </persistence-unit>

Por supuesto, para tener estos proveedores, necesitamos añadir el jar correspondiente a nuestro proyecto. Si usamos maven, las dependencia sería una de estas dos, según queramos eclipseLinc o hibernate.

<dependency>
	<groupId>org.hibernate</groupId>
	<artifactId>hibernate-entitymanager</artifactId>
	<version>5.0.3.Final</version>
</dependency> 
<dependency>
	<groupId>org.eclipse.persistence</groupId>
	<artifactId>eclipselink</artifactId>
	<version>2.2.1</version>
</dependency>

Ahora podemos poner las clases que van a ser persistentes. Hay dos opciones, una es indicar las clases que son persistentes una a una, la otra opción es indicar al proveedor de persistencia que busque automáticamente las clases anotadas como persistentes en el código. Esto se hace de una de estas maneras

   <persistence-unit ...>
      <provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>

      <exclude-unlisted-classes>true</exclude-unlisted-classes>
      <class>com.chuidiang.ejemplos.domain.Employee</class>
      <class>com.chuidiang.ejemplos.domain.Department</class>
      ...
   </persistence-unit>

Hemos puesto que excluya las clases no listadas abajo y a continuación, hemos puesto las clases una a una. La otra opción es poner la opción exclude-unlisted-classes a false y ya no haría falta poner las clases.

   <persistence-unit ...>
      <provider>org.eclipse.persistence.jpa.PersistenceProvider</provider>

      <exclude-unlisted-classes>false</exclude-unlisted-classes>
      ...
   </persistence-unit>

En cualquier caso, para el caso de Java SE, la opción exclude-unlisted-classes puede no estar soportada por el proveedor de persistencia, así que lo mejor es simplemente poner el listado de clases, sin poner esta opción. En el listado completo del ejemplo, en persistence.xml puedes ver que la opción está comentada.

Finalmente, nos queda poner los parámetros de conexión a la base de datos. Para ello, podemos usar bien propiedades generales definidas por JPA, bien propiedades específicas del proveedor de persistencia que usemos, es decir, propiedades específicas de hibernate, de eclipseLync, etc. Veamos las propiedades generales de JPA

<property name="javax.persistence.jdbc.driver" value="org.apache.derby.jdbc.EmbeddedDriver" />
<property name="javax.persistence.jdbc.url" value="jdbc:derby:memory:eclipseLincSimpleDb;create=true" />
			
<!-- 
<property name="javax.persistence.jdbc.user" value="ejemplo-jpa" />
<property name="javax.persistence.jdbc.password" value="ejemplo-jpa" /> 
-->

Hemos indicado cual es la clase que usaremos como Driver de conexión a base de datos. En este caso, usamos como base de datos Derby, que es una base de datos hecha en java, que no requiere instalación y por tanto es fácilmente utilizable en programas de pruebas, como este. Basta añadir el fichero jar de apache derby en nuestro proyecto y listo, ya podemos usar la base de datos. En el caso de usar maven, la dependencia puede ser

<dependency>
	<groupId>org.apache.derby</groupId>
	<artifactId>derby</artifactId>
	<version>10.8.2.2</version>
</dependency>

La segunda propiedad es la cadena de conexión a la base de datos, nuevamente, una cadena de conexión propia de la base de datos Apache Derby. En la cadena hemos puesto memory, de forma que la base de datos estará en memoria. En cuanto termine la ejecución de nuestro programa perderemos la base de datos y los datos, pero nuevamente es una buena opción para pequeños programas de prueba.

Las dos siguientes propiedades, que están comentadas, son el usuario y password de conexión a base de datos. Como estamos usando apache derby, su configuración por defecto es sin usuario ni password, por eso dejamos comentadas las opciones.

A partir de aquí podemos poner adicionalmente propiedades específicas de nuestro proveedor de persistencia. Por ejemplo, como estamos usando una base de datos en memoria, no tendremos tablas creadas ni nada cuando arranque el programa. Tanto ecliseLinc como hibernate son capaces de crear las tablas automáticamente en función de las clases que hemos anotado en el punto anterior. Para eclipseLinc podemos poner

<!-- EclipseLink should create the database schema automatically -->
<property name="eclipselink.ddl-generation" value="create-tables" />
<property name="eclipselink.ddl-generation.output-mode"	value="database" />

o en el caso de hibernate

!-- Hibernate should create the database schema automatically -->
<property name="hibernate.hbm2ddl.auto" value="update"/>

En el fichero completo persistence.xml hemos puesto dos persistence-unit, con distinto nombre, una para eclipseLinc, la otra para hibernate. Esto, añadir dos persistence-unit en el mismo fichero, está permitido por JPA, pero el entorno de desarrollo eclipse marcará un warning, porque a eclipse no le gusta.

Persistence y EntityManager[editar]

EntityManager será la clase que nos permitirá hacer transacciones con la base de datos, es decir, guardar entidades en base de datos, modificarlas, consultarlas, etc. Tiene métodos, entre otros muchos, persist() para guardar una de nuestras entidades en base de datos, createQuery() para crear una consulta y obtener datos de la base de datos y getTransaction(), para obtener una "transaction". ¿qué es eso de una "transaction"?. A veces queremos hacer varias operaciones en base de datos y queremos que o bien todas ellas vayan bien, o bien que ninguna se haga. La "transaction" es una forma de conseguir esto. Se obtiene una "transaction" y se llama a begin()

EntityManager manager = ...;
EntityTransaction tx = manager.getTransaction();
tx.begin();

// varias operaciones en base de datos.

// si todas van bien
tx.commit();

// si alguna va mal, en cuanto detectemos el fallo
tx.rollback();

Una vez llamado begin(), comenzamos a hacer todas las operaciones que queramos, que no se guardarán en base de datos, sino que quedarán anotadas de alguna forma. Si todas ellas van bien, llamamos al método commit(), con lo que todo lo que hemos hecho se guardará definitivamente en base de datos. Si alguna operación nos falla, en vez de deshacer todo lo que hayamos hecho hasta el momento, bastará con llamar a rollback(), y todo se deshará automáticamente.

¿Cómo obtenemos el EntityManager?. Si estamos usando un contenedor de aplicaciones completo, como JBoss o Glassfish, hay forma de obenerlo más o menos automáticamente (el contenedor nos lo pasará). Pero si usamos un contenedor no tan completo (como Apache Tomcat) o estamos en java de escritorio (no hay contenedor ninguno), debemos crear nosotros nuestro propio EntityManager. ¿Cómo lo hacemos?. Usando la clase Persistence

EntityManagerFactory factory = Persistence.createEntityManagerFactory("eclipseLinkPersistenceUnit");
EntityManager manager = factory.createEntityManager();

Al método createEntityManagerFactory() le pasamos como parámetro el nombre de la persistence-unit que queramos usar, que esté incluida en el fichero persistence.xml.

Algunas operaciones con JPA[editar]

Inserción[editar]

Bueno, pues ya tenemos todo preparado. Vamos a empezar a hacer cosas la base de datos y nuestra clase Employee. Lo primero, crear algunas instancias y guardarlas.

private void createEmployees() {
   System.out.println("CREATE EMPLOYEES :");
   // manager es el EntityManager obtenido anteriorment
   EntityTransaction tx = manager.getTransaction();
   tx.begin();

   try {
      manager.persist(new Employee("Jakab Gipsz"));
      manager.persist(new Employee("Captain Nemo"));
      tx.commit();
   } catch (Exception e) {
      e.printStackTrace();
      tx.rollback();
   }
}

Simplemente obtenemos a partir del EntityManager una EntityTransaction usando el método getTransaction(). Comenzamos la transacción llamando a begin() y empezamos con la creación de elementos. Nos basta con instanciar y rellenar los campos de Employee (usamos el constructor que admite un nombre como parámetro) y llamando a manager.persist() pasando como parámetro el Employee recién creado, ya lo tenemos guardado en base de datos. Repetimos la operación para tener dos empleados.

Una vez terminada la transacción, llamamos a tx.commit() para que las inserciones se hagan efectivas en base de datos. Si hubiera cualquier error y saltara una excepción, aparte de imprimir dicha excepción, podemos hacer un tx.rollback() para que la escritura en base de datos no sea efectiva y descartar la creación de estos dos Employee.

Consulta[editar]

Para ver los Employee recién creados, hacemos una consulta a base de datos

private void listEmployees() {
   System.out.println("Employees list :");
   
   // Nuevamente, manager es el EntityManager obtenido anteriormente.
   List<Employee> resultList = manager.createQuery(
         "Select a From Employee a", Employee.class).getResultList();
   System.out.println("num of employess:" + resultList.size());
   for (Employee next : resultList) {
      System.out.println("next employee: " + next);
   }
}

Para las consultas, en principio no es necesario iniciar ni terminar ninguna transacción. Una consulta no es una operación que queramos deshacer si falla. Así que simplemente llamamos al método createQuery() de EntityManager pasando dos parámetros.

  • El primero es la consulta que queremos realizar, en un lenguaje similar pero no igual a SQL, que se llama JPQL (Java Persistent Query Language).
  • El segundo es la clase que esperamos que nos devuelva la consulta, en nuestro caso una lista de Employee.class.

A la query obtenida de esta forma, llamamos a su método getResultList(), que nos devolverá directamente una lista de List<Employee>. Ya sólo nos queda hacer un bucle ir sacando por pantalla los resultados o lo que queramos hacer con ellos.

Si queremos consultar un Employee concreto, por ejemplo, por su nombre, sólo tenemos que añadir la cláusula where en la sentencia JPQL, como se muestra en el siguiente ejemplo

private Employee queryForEmployee(String name) {
   System.out.println("QUERY FOR "+name);
   Query query = manager
         .createQuery("Select a from Employee a where a.name = :name");
   query.setParameter("name", name);
   Employee anEmployee = (Employee) query.getSingleResult();
   System.out.println("Result : " + anEmployee.toString());
   return anEmployee;
}

En la sentencia JPQL hemos puesto select a from Employee a. Esto guarda en la variable a cada empleado, de forma que con a.name podemos acceder al nombre de cada empleado. Podríamos usar cualquier otro atributo de la clase Employee, pero en este ejemplo buscaremos por nombre y por ello la cláusula where pone a.name = :name.

La primera parte, a.name, como es dicho es el atributo name de cada uno de los Employee en base de datos, y queremos que sea igual a un nombre concreto. Como el nombre nos lo pasan como parámetro del método queryForEmployee, en la consulta JPQL ponemos una variable que nos inventemos, precedida de un : para que JPQL sepa que eso es una variable. La hemos llamado :name, aunque podríamos usar cualquier otro nombre.

Una vez obtenida la query, debemos dar valor a las variables que hayamos definido, :name en nuestro caso. Para ello llamamos al método setParameter() de la query, pasando como primer parámetro en nombre de la variable, sin el dos puntos, y como segundo parámetro, el valor que queramos para ella.

Y listo, sólo nos queda obtener el resultado. Como somos optimistas y presumimos que no va a haber dos Employee con el mismo nombre, en vez de obtener una lista de Employee, llamamos al método getSingleResult(), que nos devolverá el primer Employee que encuentre en base de datos y cumpla la condición. Como getSingleResult() devuelve un Object, debemos hacer el cast a Employee.

Ojo con getSingleResult(). Saltará una excepción si no hay resultados en base de datos que cumplan la condición, o si hay más de uno. getSingleResult() es más adecuado si consultamos por id o si consultamos por alguna columna con la restricción de que no tenga valores repetidos.

Ahora ya sacar el resultado por pantalla, devolverlo, o hacer lo que queramos con él.

Modificación[editar]

La modificación de un dato también es sencilla. Aparte de poder usar una sentencia JPQL de update, podemos consultar la base de datos, obtener la clase Employee, modificar sus datos directamente con los métodos set y luego salvar. Esto es lo que hacemos en el siguiente trozo de código

EntityTransaction tx = manager.getTransaction();
tx.begin();

try {
   Employee anEmployee = manager.find(Employee.class, id);
   anEmployee.setName(newName);
   manager.persist(anEmployee);
   tx.commit();
} catch (Exception e) {
   e.printStackTrace();
   tx.rollback();
}

Buscamos el Employee en base de datos por medio del método find() de EntityManager. A este método le pasamos el tipo de clase que esperamos como respuesta Employee.class y el id en base de datos. Si lo encuntra, nos devolverá el Employee en cuestión.

Cambiamos el nombre del Employee con el método setName() y finalmente, guardamos los cambios llamando a persist(). Un commit() y listo.

Borrado[editar]

Para borrar, nuevamente tendríamos la opción de hacer un delete usando el lenguaje JPQL, o bien si tenemos la clase Employee, bastaría con llamar al método remove() de EntityManager, que es justo lo que vamos a hacer

EntityTransaction tx = manager.getTransaction();
tx.begin();
      
try {
   Employee anEmployee = manager.find(Employee.class, id);
   manager.remove(anEmployee);
   tx.commit();
} catch (Exception e){
   e.printStackTrace();
   tx.rollback();
}

Poco hay que comentar, se busca el Employee en base de datos igual que antes, y se borra.

Cierre de conexiones[editar]

Cuando terminamos de usar un EntityManager o una EntityManagerFactory, es responsabilidad nuestra cerrarlas. Para ello, debemos llamar a los métodos close() de dichas clases.