Relación muchos a uno con @ManyToOne

Relación muchos a uno con @ManyToOneUna de las grandes ventajas que tiene trabajar con JPA, es que te permite hacer relaciones con otras entidades, de esta forma, es posible agregar otras Entidades como atributos de clase y JPA se encargará de realizar el SELECT adicional para cargar esas Entidades.

 

Para comprender esto, analicemos el ejemplo de las facturas, estas siempre son emitidas a un cliente, por lo que el objeto Factura siempre deberá tener una propiedad que represente al cliente. Normalmente, esta propiedad seria el ID, sin embargo, JPA nos permite tener una referencia a la Entidad cliente. Veamos cómo quedaría la Entidad Invoice  (factura)

package com.obb.jpa.jpaturorial.entity;

import java.util.Calendar;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.ManyToOne;
import javax.persistence.Table;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;

@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;
    
    @ManyToOne(optional = false, cascade = CascadeType.ALL, fetch = FetchType.EAGER)
    private Customer customer;

    /** GET and SET */
}

 

Como podemos apreciar, hemos utiliza la anotación @ManyToOne , la cual nos permite mapear una entidad con otra. Como única regla, es necesario la clase que sea una entidad, es decir, que también esté anotada con @Entity .

 

Por otro lado, tenemos la entidad Customer , la cual se ve de la siguiente manera:

package com.obb.jpa.jpaturorial.entity;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;

@Entity
@Table(name = "CUSTOMERS")
public class Customer {
    
    @Id
    @Column(name = "ID")
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private long id;
    
    @Column(name = "NAME", nullable = false, length = 100)
    private String name;

    /** GET and SET */
}

 

Estas dos entidades nos crearán las siguientes tablas:

JPA ManyToOne tables

 

La anotación @ManyToOne cuenta con los siguientes atributos:

  • Optional: indica si la relación es opcional, es decir, si el objeto puede ser null. Esta propiedad se utiliza optimizar las consultas. Si JPA sabe que una relación es opcional, entonces puede realizar un RIGHT JOIN  o realizar la consulta por separado, mientras que, si no es opcional, puede realizar un INNER JOIN  para realizar una solo consulta.
  • Cascade: Esta propiedad le indica que operaciones en cascada puede realizar con la Entidad relacionada, los valores posibles son ALL , PERSIST , MERGE , REMOVE , REFRESH , DETACH y están definidos en la enumeración javax.persistence.CascadeType .
  • Fetch: Esta propiedad se utiliza para determinar cómo debe ser cargada la entidad, los valores están definidos en la enumeración javax.persistence.FetchType  y los valores posibles son:
    • EAGER (ansioso): Indica que la relación debe de ser cargada al momento de cargar la entidad.
    • LAZY (perezoso): Indica que la relación solo se cargará cuando la propiedad sea leída por primera vez.
  • targetEntity: Esta propiedad recibe una clase (Class ) la cual corresponde a la clase de la relación. No suele ser utilizada, pues JPA puede inferir la clase por el tipo de la propiedad.

En nuestro caso, hemos definido la propiedad optional en false, pues toda factura debe de tener forzosamente un Cliente (customer). La propiedad cascade la definimos en ALL  para facilitarnos la explicación de este ejemplo, pero más adelante en este tutorial analizaremos las cascadas en profundidad. La propiedad fetch la definimos en EAGER , aun que para este tipo de relación es su valor por default y no sería necesario definirlo.

 

Ejemplo práctico

Crearemos un pequeño ejemplo que nos permita comprobar cómo trabajar con esta relación, para ello, crearemos una nueva factura con un cliente:

public static void main(String[] args) {
    EntityManager manager = EntityManagerUtil.getEntityManager();
        
    Customer customer = new Customer();
    customer.setName("ACME Corp");
      
    Invoice invoice = new Invoice();
    invoice.setCustomer(customer);
    invoice.setRegistDate(Calendar.getInstance());
    invoice.setStatus(Status.ACTIVE);
        
    manager.getTransaction().begin();
    manager.persist(invoice);
    manager.getTransaction().commit();
}

 

Como resultado, podemos observar como han sido insertados la Factura y el Cliente:

JPA Invoices ManyToOne relationship
Tabla de las facturas
JPA Customers ManyToOne relationship
Tabla de los clientes

Por otra parte, si consultamos la Entidad Invoice, JPA se encargará de cargar al Empleado, vamos el siguiente ejemplo:

public static void main(String[] args) {
    EntityManager manager = EntityManagerUtil.getEntityManager();
    Invoice invoice = manager.find(Invoice.class, 1L);
        
    System.out.println("Invoice ID > " + invoice.getId());
    System.out.println("Customer ID > " + invoice.getCustomer().getId());
    System.out.println("Customer Name > " + invoice.getCustomer().getName());
}

 

Como podemos apreciar en la siguiente imagen, no fue necesario ir a consultar el Cliente por separado, si no que JPA se encarga de recuperarlo justo en el momento en que lo necesitamos.

JPA ManyToOne select

 

Finalmente, podemos ver en las siguiente imágenes como es que nos auto genero la tabla de las facturas, en ella podemos apreciar el campo CUSTOMER_ID , el cual es en realidad una llave foránea a la tabla de clientes:

JPA ManyToOne table

 

JPA también nos ayuda a crear los Foreign Keys con la tabla de Clientes:

JPA ManyToOne foreign keys

 

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.

 

Conclusiones

Hemos podido observar como JPA se encarga de todo lo relacionado a persistir o recuperar el cliente de la base de datos, sin embargo, hay algo que no hemos cubierto, y es la forma en que podemos personalizar la columna, es decir, como podemos indicarle el nombre, si admite nulos o no, etc. Todas esta propiedades las podremos definir en la anotación @JoinColumn  que analizaremos en la siguiente parte del tutorial

 

AnteriorÍndiceSiguiente

10 thoughts to “Relación muchos a uno con @ManyToOne”

  1. Por cierto, Óscar, en mi empresa anterior (Everis) tuve un jefe que se dedicaba a resolver problemas en los diversos proyectos en los que participaba o le llamaban y nos comentó que se dedicó a reparar una aplicación Java con JPA donde los programadores habían puesto “fetch.Type=EAGER” en todas las relaciones… eso originaba unos cuellos de botella en los accesos que ralentizaba muchas operaciones. Viendo eso, creo que tal vez sea casi siempre recomendable utilizar “LAZY”… ¿qué opinas tú? Un saludo y enhorabuena por el buen trabajo del tutorial.

    1. Hola Pedro, en realidad no hay un formular para esto, pues todo dependerá de como tengas la información y el uso que le des a la misma, hay escenarios donde es mejor EAGER y otros donde es mejor utilizar LAZY, EAGER nos puede llevar a tener el problema conocido como N querys + 1, pero por otro lado, utilizar LAZY puede que provoque que la consulta en lugar de armar las relaciones con INNER JOIN, las arme mediante query separados, lo cual también degrada el performance.

  2. Y porque en la relacion @ManyToOne no se especifica el JoinColum? para indicarle a JPA cual es el campo a relacionar? Algo asi:

    @ManyToOne(optional = false, cascade = CascadeType.ALL, fetch = FetchType.EAGER)
    @JoinColumn(name = “CUSTOMER_ID”, nullable = false, updatable = false)
    private Customer customer;

    1. Lo que comentas tiene todo el sentido, ya que hay que indicarle en que columna guardará la llave de la otra entidad, sin embargo, si no la indicamos, la implementación concreta de JPA la crea dinámicamente, no es lo recomendado, pero lo hace, entonces, lo que tu comentas es lo correcto, agregar la anotación @JoinColumn.
      Saludos.

  3. Es posible agregar una nueva “invoice” y reutilizar algun customer que ya exista en la Base de Datos en lugar de crear uno nuevo cada vez que se persiste una nueva “invoice”?

    1. Claro que es posible, de echo es la forma correcta de hacerlo. Para reutilizar un Customer tan solo es necesario hacerle un Find con el EntityManager y luego asignarlo a la nueva invoice, JPA sabrá si se trata de un nuevo cliente o uno existente por el ID, si el ID es nulo, asume que es nuevo e intentará insertarlo (si definimos la cascada) o en su caso, si el ID es diferente de null, entonces intententará insertar ese ID.
      Saludos.

  4. Hola una duda, en el ejemplo es con un customer que no existe y también lo esta insertando; la pregunta es: Como quedaría la relación si queremos insertar una invoice con un customer que ya existe, no necesariamente insertarlo al mismo tiempo

    1. Hola Edgar, es muy simple, solo haces la búsqueda del cliente existente con EntityManager.find y el cliente que te regrese lo asignas a la factura, de esta forma, JPA sabrá que se trata de un cliente existente, por lo que en lugar de crearlo, solo lo relacionará al cliente existente.

  5. Tengo un problema donde Tengo multiples horarios para una actividad, en Horario tengo:ç
    @ManyToOne(optional = false, cascade = CascadeType.ALL)
    private Actividad unaActividad;

    y cada vez que inserto un horario le estoy pasando una actividad existente, pero de todas formas cada vez que se agrega un horario nuevo tambien se agrega un nueva actividad, no estoy pudiendo reutlizar las actividades existentes, esto en PGSQL no me pasaba pero si me esta pasando ahora en Sql Server, sabes cual podria ser el problema?

    1. Hola Marcelo, para poder reuitilizar una Entidad, tienes que obtener su referencia con manager.find(), lo que te regresa, lo asocias a tu otra Entidad y verás que funciona sin problemas

Deja un comentario

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