JPA: Maestro / detalle con clave primaria compartida

Hace casi 10 años (wow!) había publicado un artículo sobre las claves compuestas en entidades de tipo Maestro / Detalle. Bueno, aquí está una super actualización. Esa vez fue hecha con JPA 1.0, ahora lo mostraré más actualizado y mejorado con con el JPA 2.0.

El ejemplo será usado del clásico: Factura / Detalle factura; donde Factura será el "maestro" y "Detalle Factura" su detalle. La tabla de Detalle deberá tener el código de la factura y un correlativo. Ambos campos serán parte de la clave primaria del detalle.


Clase "maestro"

Primero comencemos por definir la clase "Maestro":

@Entity
public class Factura implements Serializable {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long facturaId;

    @Temporal(javax.persistence.TemporalType.DATE)
    private Date fechaFactura;

    @OneToMany(mappedBy = "factura")
    private List<FacturaDetalle> detalle;
//...

Clase completa en Factura.

Como se ve, luce totalmente normal como si fuera una Entidad común y silvestre. Además, tiene una lista (línea 44) que contendrá todas las filas del detalle de la factura.

Clase "detalle"

Ahora veremos cómo luce esa clase detalle.

@Entity
@Table(name = "FACTURA_DETALLE")
public class FacturaDetalle implements Serializable {

    @EmbeddedId
    private FacturaDetallePK facturaDetallePK;

    @MapsId("facturaId")
    @ManyToOne
    private Factura factura;

    private String descripcion;

    public FacturaDetalle() {
    }

    public FacturaDetalle(long facturaId, int ordenId) {
        facturaDetallePK = new FacturaDetallePK(facturaId, ordenId);
    }
//...

Clase completa en :FacturaDetalle.

La línea 35 nos parece algo raro, ya que es una clase incrustada @EmbeddedId. En esta estarán los campos de la clave primaria.

El mapeo entre el detalle y el maestro se dá en la línea 39. Es una relación muchos-a-uno @ManyToOne, y el campo relacionado entre Factura y el Detalle se dan por el atributo facturaId. Esa relación se da en la línea 37 con la anotación @MapsId.

Ahora veamos qué contiene la clase FacturaDetallePK

Clase Clave primaria de detalle

@Embeddable
public class FacturaDetallePK implements Serializable {
    private long facturaId;
    private int orderId;

//...

Clase completa en :FacturaDetallePK.

La clase luce como cualquiera con anotación @Embeddable. En fin, eso es todo. No requiere mucha anotación adicional.

¿Cómo se usa?

La inserción de objetos es mucho más natural. Comencemos con crear un objeto de la clase "Maestro". Ya que después de insertarlo obtendremos el ID generado.

    private void start() {
        LOG.info("Insertando objeto factura");
        Factura factura = new Factura();
        factura.setFechaFactura(new Date());
        persist(factura);
        LOG.log(Level.INFO, "Factura insertada:{0}", factura);
//...

Ahora bien, necesitamos crear el detalle, y como ya tenemos el ID del Maestro, entonces simplemente le pasamos al objeto. El campo ordenId lo he puesto en duro solo para fines de prueba:
//...
        LOG.info("Insertando detalle 1");
        FacturaDetalle det1 = new FacturaDetalle(factura.getFacturaId(), 1);
        det1.setDescripcion("PCs");
        det1.setFactura(factura);
        persist(det1);
        LOG.log(Level.INFO, "detalle 1 insertada{0}", det1);

        LOG.info("Insertando detalle 2");
        FacturaDetalle det2 = new FacturaDetalle(factura.getFacturaId(), 2);
        det2.setDescripcion("Monitores");
        det2.setFactura(factura);
        persist(det2);
        LOG.log(Level.INFO, "detalle 2 insertada{0}", det2);
//...

Listo, ya tenemos dos objetos como detalle de la factura. Si lo consultamos, veamos lo que aparece:
//...
        LOG.info("Obteniendo objetos");
        List<Factura> facturas = em.createQuery("SELECT f FROM Factura f", Factura.class).getResultList();
        facturas.stream().forEach((f) -> {
            LOG.log(Level.INFO, "Factura: {0}", f);
        });


Pero en las tablas, el resultado será este.

El "maestro":

Y del "detalle" así:

Código fuente

El código fuente completo del proyecto lo pueden obtener de aquí:

Social

Twitter

Facebook

Comentarios

Entradas más populares de este blog

Groovy: Un lenguaje dinámico y ágil para la Plataforma Java

Cambiar ícono a un JFrame

UML en NetBeans