Relaciones @OneToMany

JPA relaciones @OneToMany

Las relaciones uno a muchos (@OneToMany) se caracterizan por Entidad donde tenemos un objeto principal y colecci贸n de objetos de otra Entidad relacionados directamente. Estas relaciones se definen mediante colecciones, pues tendremos una serie de objetos pertenecientes al objeto principal.

Este articulo es parte de un guia completa de JPA, para ver el contenido completo puedes ir a Java Persistence API (JPA)


Un ejemplo cl谩sico para entender este tipo de relaciones son las facturas, pues tendremos una Entidad cabecera donde tengamos los datos principales de la factura, como podr铆a ser serie, cliente, total, fecha de expedici贸n, etc. Por otra parte, la factura tendr谩 una serie de l铆neas que representa cada uno de los productos vendidos. 

Veamos como quedar铆a la entidad Invoice (factura):

package com.obb.jpa.jpaturorial.entity;

import java.util.Calendar;
import java.util.List;
import javax.persistence.*;

@Entity
@Table(name="INVOICES")
public class Invoice {
    
    @Id
    @Column(name="ID")
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private long id;
    
    @Column(name = "STATUS", length = 20, nullable = false)
    @Enumerated(EnumType.STRING)
    private Status status;
    
    @Temporal(TemporalType.TIMESTAMP)
    @Column(name = "REGIST_DATE", nullable = false)
    private Calendar registDate;
    
    @JoinColumn(name = "FK_CUSTOMER", nullable = false)
    @ManyToOne(optional = false, cascade = CascadeType.ALL, fetch = FetchType.EAGER)
    private Customer customer;
    
    @OneToOne(mappedBy = "invoice", cascade = CascadeType.ALL)
    private Payment payment;
    
    @OneToMany(cascade = CascadeType.ALL, mappedBy = "invoice")
    private List<InvoiceLine> lines;

    /** GET and SET */
}


Podemos apreciar como hemos definido la propiedad lines como una lista (List), lo cual nos permite relacionar la factura con un n煤mero indeterminado de l铆neas, por otro lado, hemos definido la propiedad mappedBy para indicar que es una relaci贸n bidireccional, es decir, la Entidad InvoiceLine tendr谩 tambi茅n una relaci贸n hacia la Entidad Invoice.

La Entidad InvoiceLine se ver谩 de la siguiente manera:

package com.obb.jpa.jpaturorial.entity;

import javax.persistence.*;

@Entity
@Table(name = "invoice_lines")
public class InvoiceLine {
    @Id
    @Column(name = "ID")
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @Column(name = "PRODUCT", nullable = false)
    private String product;
    
    @Column(name = "PRICE", nullable = false)
    private double price;
    
    @Column(name = "TOTAL", nullable = false)
    private double quantity;
    
    @ManyToOne
    @JoinColumn(name = "FK_INVOICE", nullable = false, updatable = false)
    private Invoice invoice;
    
    /** GET and SET */

}


Como dijimos hace un momento, la Entidad InvoiceLine tiene una propiedad de tipo Invoice para poderla hacer bidireccional, observemos que el nombre de la propiedad invoice corresponde con el valor del mappedBy definido en la Entidad Invoice, ya que de lo contrario JPA nos arrojar谩 un error.

Tamb铆en los quiero invitar a ver mi curso de JPA, donde explico todos estos temas aplicados con API REST, https://codmind.com/courses/jpa

 Los invito a mi Curso de Mastering JPA, donde habla de todos estos temas y crearemos un API REST para probar todos los conceptos de persistencia.


En este imagen podemos apreciar que la tabla invoice_lines tiene la columna FK_INVOICE que ser谩 utilizada para realizar el JOIN con la tabla invoices, de tal forma que cada registro de invoice_lines que sea encontrado ser谩 convertido en una instancia de la Entidad InvoiceLine.

public static void main(String[] args) {
    Invoice invoice = new Invoice();

    List<InvoiceLine> lines = new ArrayList<>();
    for(int c = 0; c<10; c++){
        InvoiceLine line = new InvoiceLine();
        line.setInvoice(invoice);
        line.setPrice(10);
        line.setProduct("Product " + (c+1));
        line.setQuantity(c+1);
        lines.add(line);
    }

    Customer customer = new Customer();
    customer.setName("Oscar Blancarte");

    invoice.setCustomer(customer);
    invoice.setLines(lines);
    invoice.setRegistDate(Calendar.getInstance());
    invoice.setStatus(Status.ACTIVE);

    EntityManager em = EntityManagerUtil.getEntityManager();
    em.getTransaction().begin();
    em.persist(invoice);
    em.getTransaction().commit();
}


Dando como resultado los siguientes registros en la base de datos:

JPA relaciones @OneToMany tabla invoices
Tabla invoices
JPA relaciones @OneToMany tabla invoice_lines
Tabla invoice_lines

Ahora si podemos ver todo materializado y nos queda m谩s la forma en que la informaci贸n es almacenada. 


Conclusiones

Hemos comprobado lo simple que es crear relaciones @OneToMany y hemos podido comprobar como  la informaci贸n es almacenada, solo faltar铆a resaltar que JPA no garantiza el orden en que los elementos de la colecci贸n son insertados, es por ello, que siempre se aconseja tener una columna que indique el orden natural de los elementos para sobre esa columna hacer el ordenamiento, esto claro si nos interesa mantener el ordenamiento, en caso podemos ignorarlo.

Anterior脥ndiceSiguiente

14 thoughts to “Relaciones @OneToMany”

  1. tengo tres tablas que estan relacionadas uno a muchos susesivamente, cuando realizo la verificacion en Postman me salta el error _
    2019-07-27 20:38:18.136 ERROR 10912 — [nio-9092-exec-1] s.e.ErrorMvcAutoConfiguration$StaticView : Cannot render error page for request [/mscapacidad/listaDivisiones] and exception [Could not write JSON: Infinite recursion (StackOverflowError); nested exception is com.fasterxml.jackson.databind.JsonMappingException: Infinite recursion (StackOverflowError)

    1. Hola Christian, seguramente est谩s retornando una Entity directamente en el servicio REST, cuando hace esto, lo que hace Java es hacer un parce del Objeto a JSON, sin embargo, seguramente tus objetos han de tener un relaci贸n ciclica, lo que hace que la conversi贸n a JSON se cicle, lo que te recomiendo es que en lugar de retornar una Entity directamente, crees un DTO con una estructura que no sea ciclica, al mismo tiempo que podr铆as retornar solo los campos que necesites, te dejo mi art铆culo del patr贸n DTO para que entiendas como funciona https://www.oscarblancarteblog.com/2018/11/30/data-transfer-object-dto-patron-diseno/
      saludos.

  2. Hola Oscar, gracias por este excelente aporte y por la forma tan clara que tienes para exponer cada tema.
    Te quer铆a preguntar, ya tengo una base de datos Oracle y solo necesito hacer consulta a una tabla buscando un campo especifico, 驴Cual ser铆a la instrucci贸n para realizar dicha consulta y luego llevar el resultado de esa consulta a una variable?
    Muchas gracias

    1. Hola Karlox, cuando realices la consulta, en lugar de seleccionar toda la entidad, debes acotar los campos consultados, por ejemplo:
      select e.nombre from e where e.id = 1, solo toma en cuenta que este query en lugar de retornarte una entidad, te va a regresar un arreglo de objectos de dos dimensiones Object[][], donde la primera dimensi贸n es cada tupla y la segunda dimensi贸n son los campos consultados en el mismo orden de la consulta.

      Saludos.

  3. Hola Oscar, una pregunta, c贸mo puedo manejar una relaci贸n de uno a cero o muchos? o sea no se c贸mo hacer con la llave for谩nea en una relaci贸n uno a cero.

    1. Para empezar, no existen las relaciones uno a cero, en tal caso, ser铆a ManyToOne, donde la relaci贸n puede ser nullable, es decir, puede que la relaci贸n no existe. Para lograr eso, tienes que usar la anotaci贸n @ManyToOne en combinaci贸n con @JoinColumn, para definir el nombre de la columna

  4. Hola Oscar, quiero agradecerte por tan excelente articulo y de mucha ayuda para los principiantes, podr铆as apoyarme con un problema que tengo, Cannot add or update a child row: a foreign key constraint fails (`argentap_ArgentaGeneral`.`detalle_compras`, CONSTRAINT `detalle_compras_ibfk_1` FOREIGN KEY (`Folio_Compra`) REFERENCES `compras` (`Folio_Compra`)) … tengo varios objetos que se persisten a la hora de ejecutar el persist, para mi caso Compras –> oneToMany (detalle_compras) –> OnetoOne(Inventario), creo que tengo correctamente programado mis entities ya que si elimino la llave for谩nea en la base de datos SI funciona, hay forma de decirle a la aplicaci贸n en que orden me guarde los objetos? primero me cree los registros en compras o Inventarios, y al final en detalle_compras, te lo agradecer铆a mucho, saludos!

    1. Puedes definir la cascada para indicarle que Entidades se deben de persistir en cascada y cuales no, lo que si no sabr铆a decirte con certeza es si puede decir en que orden se deben de insetar, por lo que yo s茅, no se puede, y es que al final, el orden no deber铆a de ser un problema, pues JPA es lo suficientemente inteligente para determinar la jerarqu铆a de dependecias para insertar las entidades en el orden correcto.

  5. Hola Oscar, buenas noches, espero te encuentres bien, me ha ayudado mucho tus articulos pero tengo una situacion que me ha salido, en la construccion de una aplicacion tengo una entidad que se llama “Vehiculo” y dentro de esta entidad vehiculo tengo un atributo que se llama “tipo vehiculo” lo tengo de esta manera

    @ManyToOne(optional = false, cascade = CascadeType.ALL, fetch = FetchType.LAZY)
    @JoinColumn(name=”id_tipo_vehi” )
    private TipoVehiculo tipoVehiculo;

    Funciona bien cuando los valores en las 2 entidades son nuevos.
    La cantidad de tipos de vehiculos son mucho menores a los vehiculos, el tema es que cuando quiero ingresar un nuevo “vehiculo” con un “tipo vehiculo” que ya existe me sale error de llave duplicada en la tabla “TipoVehiculo”, como podria solucionar este incoveniente, agradecera mucho tu ayuda.

    1. Ese problema es muy frecuenta entre prinsipiantes con JPA, el problema es que cuando vas a relacionar una Entidad que ya existe, debes primero de agregarlo al contexto de persitencia mediante manager.find(), una vez que lo cargues, tendr谩s que reemplazar ese reemplazar el TipoVehiculo por el que te regrese el m茅todo find. En mi curso de Mastering JPA con Hibernate explico el ciclo de vida de las entidades a la perfecci贸n y vemos ejemplos de esto que comentas, por si quieres darle una revisada: https://codmind.com/courses/jpa

  6. Hola buena tarde soy nuevo en esto de jpa y tengo una consulta estoy creando una aplicaci贸n sencilla para un inventario ya tengo mi base de datos relacionada en la gran mayor铆a las relaciones me ha salido de uno a muchos pero viendo informaci贸n de internet y tutoriales algunos recomienda no usar muchas relaciones OneToMany entonces con base en esa informaci贸n decid铆 utilizar en todas mis entidades la relacion ManytoOne quer铆a saber tu opini贸n ya que estoy algo confundido.

    1. Hola Cristhian, quiz谩s entendiste mal el concepto, en realidad las anotaciones @ManyToOne y @OneToMany no son intercambiables, es decir, no es que por que sea mala una vas a usar la otra, por que no sirve para lo mismo, una es para el manejo de colecciones y la otra para relaciones a una Entidad, mi sugerencia es que ignores eso que leite y solo dise帽a las entidades para que te funcionen, con el tiempo aprender谩s a dominarlas y podr谩s comprender cuando es mejor una que otra, as铆 que mi recomendaci贸n es esa, por ahora enfocate en que funcione, luego lo perfeccionas.

Deja un comentario

Tu direcci贸n de correo electr贸nico no ser谩 publicada. Los campos obligatorios est谩n marcados con *