No hay deuda que no se pague… En un curso que dicté hace poco quedé de hacer una pequeña aplicación para mostrar las cosas básicas que se pueden hacer con JPA (Java Persistence API). Dado que el curso era el FJ-310-EE5 (Developing Applications for the Java EE Platform), oficial de Sun, y que los alumnos ya se encuentran familiarizados con NetBeans y GlassFish usaré en este ejemplo Netbeans 6.1 y GlassFish v2. El objetivo de este artículo no es un “JPA paso a paso” sino que destacar un par de puntos clave en el desarrollo con JPA.

Como GlassFish v2 viene con JavaDB, tambien conocida como Apache Derby, será la base de datos que usaremos para este ejemplo. Primero es necesario crear una nueva base de datos.

Luego creamos una conexión desde Netbeans.

En nuestro ejemplo crearemos un proyecto de tipo Java EE, que generará un EAR EjemploJPA compuesto de dos módulos.
- EjemploJPA-war
- EjemploJPA-ejb
Adicionalmente crearemos un proyecto tipo Java bautizado EjemploJPA-Lib, en el cual definiremos nuestras entidades, interfaces y excepciones. Con lo anterior vemos que tenemos lo siguiente en NetBeans:

En este ejemplo, tendremos dos entidades principales: Cliente y OrdenDeCompra, donde un Cliente puede tener 0 o más ordenes de compra.
package com.joseselman.ejemplojpa.entidades;
import java.io.Serializable;
import java.util.Collection;
import java.util.LinkedList;
import javax.persistence.*;
@Entity
@Table(name="cliente")
public class Cliente implements Serializable {
private int id;
private String nombre;
private Collection<OrdenDeCompra> ordenes;
@Id
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getNombre() {
return nombre;
}
public void setNombre(String nombre) {
this.nombre = nombre;
}
@OneToMany(cascade=CascadeType.ALL, mappedBy="cliente", fetch=FetchType.LAZY)
public Collection<OrdenDeCompra> getOrdenes() {
return ordenes;
}
public void setOrdenes(Collection<OrdenDeCompra> ordenes) {
this.ordenes = ordenes;
}
}
package com.joseselman.ejemplojpa.entidades;
import javax.persistence.*;
@Entity
@Table(name="orden")
public class OrdenDeCompra implements java.io.Serializable {
private int id;
private String direccion;
private Cliente cliente;
private int monto;
@ManyToOne()
@JoinColumn(name="ID_CLIENTE")
public Cliente getCliente() {
return cliente;
}
public void setCliente(Cliente cliente) {
this.cliente = cliente;
}
@Column(name="DIRECCION_ENVIO")
public String getDireccion() {
return direccion;
}
public void setDireccion(String direccion) {
this.direccion = direccion;
}
@Id
@Column(name="ID_ORDEN")
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public int getMonto() {
return monto;
}
public void setMonto(int monto) {
this.monto = monto;
}
}
En el módulo EjemploJPA-ejb creamos una Unidad de Persistencia (Persistence Unit):

Lo que genera el siguiente archivo de configuración:
<?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 http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd">
<persistence-unit name="EjemploJPA-ejbPU" transaction-type="JTA">
<provider>oracle.toplink.essentials.PersistenceProvider</provider>
<jta-data-source>ds/ejemploJPA</jta-data-source>
<jar-file>EjemploJPA-Lib.jar</jar-file>
<properties>
<property name="toplink.ddl-generation" value="drop-and-create-tables"/>
</properties>
</persistence-unit>
</persistence>
Al hacerlo con el IDE, se encarga de crear el Connection Pool y Data Source en el servidor de aplicaciones. En caso contrario tendríamos que crear el Connection Pool y DataSource directamente en la consola del servidor de aplicaciones.

Como seleccionamos la estrategia DROP AND CREATE, en la unidad de persistencia, las tablas son creadas en forma automática al correr la aplicación.

Adicionalmente creamos un Stateless Session EJB para ejecutar un par de consultas a la base de datos.
El método cargarDatos() para poblar datos en la base de datos. Notar que es necesario indicar las referencias en ambas puntas de la relación. Además, recordemos que el contexto de persistencia vive a lo largo de toda la transacción.
public void cargarDatos() throws ClientesOrdenesDAOException {
Cliente clienteVictor = new Cliente();
clienteVictor.setId(1);
clienteVictor.setNombre("Victor");
em.persist(clienteVictor);
OrdenDeCompra ordenDeCompraVictor1 = new OrdenDeCompra();
ordenDeCompraVictor1.setId(1);
ordenDeCompraVictor1.setDireccion("XXXXXXXX 1200");
ordenDeCompraVictor1.setMonto(100000);
OrdenDeCompra ordenDeCompraVictor2 = new OrdenDeCompra();
ordenDeCompraVictor2.setId(2);
ordenDeCompraVictor2.setDireccion("ZZZZZZZZ 5688 depto. 101");
ordenDeCompraVictor2.setMonto(150000);
// Las asociaciones
ordenDeCompraVictor1.setCliente(clienteVictor);
clienteVictor.getOrdenes().add(ordenDeCompraVictor1);
ordenDeCompraVictor2.setCliente(clienteVictor);
clienteVictor.getOrdenes().add(ordenDeCompraVictor2);
Cliente clienteJaime = new Cliente();
clienteJaime.setId(2);
clienteJaime.setNombre("Jaime");
em.persist(clienteJaime);
OrdenDeCompra ordenDeCompraJaime1 = new OrdenDeCompra();
ordenDeCompraJaime1.setId(3);
ordenDeCompraJaime1.setDireccion("YYYYYY 6006 of. 26");
ordenDeCompraJaime1.setMonto(200000);
// Las asociaciones
ordenDeCompraJaime1.setCliente(clienteJaime);
clienteJaime.getOrdenes().add(ordenDeCompraJaime1);
Cliente clienteJose = new Cliente();
clienteJose.setId(3);
clienteJose.setNombre("Jose");
em.persist(clienteJose);
OrdenDeCompra ordenDeCompraJose1 = new OrdenDeCompra();
ordenDeCompraJose1.setId(4);
ordenDeCompraJose1.setDireccion("TTTTTTT 103 depto 1604");
ordenDeCompraJose1.setMonto(500000);
// Las asociaciones
ordenDeCompraJose1.setCliente(clienteJose);
clienteJose.getOrdenes().add(ordenDeCompraJose1);
}
Al llamar a este método, vemos que los datos son cargados en la base de datos.

El método listarClientes() para consultar todos los clientes y sus ordenes de compra ingresadas. Acá hacemos "el truco" de recorrer las "relaciones" de la entidad para asegurarnos que serán cargadas; recordemos que marcamos esta relación como LAZY.
public Cliente[] listarClientes() throws ClientesOrdenesDAOException {
Query q = em.createQuery("SELECT c FROM Cliente c");
Cliente[] clientes = (Cliente[])(q.getResultList()).toArray(new Cliente[0]);
if (clientes != null) {
for(int i=0; i<clientes.length; i++) {
// Para asegurarnos que se carguen las relaciones "LAZY"
clientes[i].getOrdenes().size();
}
}
return clientes;
}

El método consultarClientesMontos() genera un reporte con los nombres de los clientes y la suma de todas las ordenes de compra asociadas a ellos. Para este caso fue necesario crear un DTO (Data Transfer Object) para que sirva como contenedor para el resultado.
public ClienteMontoDTO[] consultarClientesMontos() throws ClientesOrdenesDAOException {
Query q = em.createQuery("SELECT NEW com.joseselman.ejemplojpa.dto.ClienteMontoDTO(c.nombre, SUM(o.monto)) " +
"FROM Cliente c LEFT JOIN c.ordenes o GROUP BY c.nombre");
ClienteMontoDTO[] resultado = (ClienteMontoDTO[])(q.getResultList().toArray(new ClienteMontoDTO[0]));
return resultado;
}

Descarga los proyectos NetBeans desde acá
Referencias: Glassfish Project - Java Persistence Example.