Relaciones @OneToOne

Las relaciones One to One (@OneToOne) se caracterizan porque solo puede existir una y solo una relaci贸n con la Entidad de destino, de esta forma, la entidad marcada como @OnoToOne deber谩 tener una referencia a la Entidad destino y por ning煤n motivo podr谩 ser una colecci贸n. De la misma forma, la Entidad destino no podr谩 pertenecer a otra Instancia de la Entidad origen.

Solo para ponernos en contexto, las relaciones @OneToOne se utilizan cuando existe una profunda relaci贸n entre la Entidad origen y destino,de tal forma que la entidad destino le pertenece a la Entidad origen y solo a ella, por ende, la existencia de la entidad destino depende de la Entidad origen.

Para comprender mejor todo este trabalenguas, analicemos el caso pr谩ctico de una factura, cuando creamos una factura, la llevamos con nuestro cliente para que la pague, este pago puede ser una entidad por separada que lleve el control de la fecha de pago, monto pagado, m茅todo de pago, etc. Veamos como quedar铆an esta Entidades

Entidad Invoice:

package com.obb.jpa.jpaturorial.entity;

import java.util.Calendar;
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;

    /** GET and SET */
    
}

Entidad Payment:

package com.obb.jpa.jpaturorial.entity;

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

/**
 * @author Oscar Blancarte <oscarblancarte3@gmail.com>
 */
@Entity
@Table(name = "payments")
public class Payment {
    
    @Id
    @Column(name = "ID")
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @Temporal(TemporalType.TIMESTAMP)
    @Column(name="PAY_DATE", nullable = false, updatable = false)
    private Calendar payDate;
    
    @Column(name = "AMOUNT", nullable = false, updatable = false)
    private double amount;
    
    @Column(name = "PAY_METHOD", nullable = false, updatable = false)
    private PaymentMethod paymentMethod;
    
    @OneToOne
    @JoinColumn(name = "FK_INVOICE", updatable = false, nullable = false)
    private Invoice invoice;

    /** GET and SET */
}

Estas dos Entidades crear谩n las siguientes tablas:

Como podemos observar, la tabla payments tiene la columna FK_INVOICE que hace relaci贸n con la tabla invoices.

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.

Algo que hay que tomar en cuenta, es que tanto la Entidad Invoice como Payment tiene anotada como @OneToOne a la otra entidad, sin embargo, solo payments ha creado la columna para hacer el JOIN, esto se debe a que esta es la que tiene la anotaci贸n @JoinColumn, la cual nos permite establecer como se llamar谩 la columna para realizar el JOIN, adicional, la Entidad Invoice tiene definida la propiedad mappedBy de la anotaci贸n @OneToOne, esta propiedad es muy importante, pues le permite hacer una relaci贸n bidireccional, si no la pusi茅ramos, JPA agregar铆a una columna adicional en la tabla invoices, y guardar铆a el pago como un nuevo registro.

Veamos un ejemplo de c贸mo se crear铆a un registro con esta relaci贸n:

public static void main(String[] args) {
        EntityManager manager = EntityManagerUtil.getEntityManager();
        
        Customer customer = new Customer();
        customer.setName("Oscar Blancarte");
        
        Payment payment = new Payment();
        payment.setAmount(100);
        payment.setPayDate(Calendar.getInstance());
        payment.setPaymentMethod(PaymentMethod.CASH);
                
        
        Invoice invoice = new Invoice();
        invoice.setCustomer(customer);
        invoice.setRegistDate(Calendar.getInstance());
        invoice.setStatus(Status.ACTIVE);
        
        payment.setInvoice(invoice);
        invoice.setPayment(payment);
        
        manager.getTransaction().begin();
        manager.persist(invoice);
        manager.getTransaction().commit();
    }

Dando como resultado lo siguiente:

JPA OneToOne payments table
Tabla payments
JPA OneToOne invoices table
Tabla invoices

Conclusiones

Crear relaciones OneToOne es muy simple como hemos podido comprobar, e incluso, podr铆amos crear una relaci贸n unidireccionar omitiendo la propiedad payment de la entidad Invoice, lo que diera como resultado exactamente la misma estructura de base de datos, con la 煤nica limitante que no podr铆amos recuperar el pago por medio de la factura y es por ello que utilizamos relacionar bidireccionales con mappedBy.

Anterior脥ndiceSiguiente

24 thoughts to “Relaciones @OneToOne”

  1. Una consulta, si la relaci贸n a nivel de base de datos de este ejemplo es de uno a muchos, por qu茅 se utiliza la anotaci贸n @OneToOne ? No deber铆a utilizarse la anotaci贸n @OneToMany o @ManyToOne?

    Saludos,

    Christian

    1. Lo que dices es incorrecto, por que una factura tiene una relaci贸n uno a uno con los pagos, en otro caso estar铆amos diciendo que un pago pude pertenecer a muchas facturas o que una factura puede tener muchos pagos.

        1. Excelente pregunta estimado Christian, pero me gustaria hacerte una aclaraci贸n, el modelo F铆sico y el modelo L贸gico, sin diferentes, cuando modelamos entidades lo hacemos pensando en un modelo l贸gico, mientras que cuando construimos las tablas, lo hacemos pensando en un modelo f铆sico. dicho esto, la imagen que ves aqu铆 de las tablas, corresponde a un modelo f铆sico, mientras que la entidad es un modelo l贸gico.
          Ahora bien, te preguntas cual es la diferencia, la realidad es que el modelo l贸gico es imposible de hacer en un modelo f铆sico, solo piensa, lo siguiente, como puedes hacer un Many To Many en un modelo f铆sico, la realidad es que es imposible, por que necesitar铆as campos de tipo array para guardar todas las relaciones, en su lugar, el modelo fisico se hace mediante 3 tablas, donde cada una de ellas donde las dos principales se una por una intermedia.
          Espero que esto te de una peque帽a luz en tu duda.
          saludos.

  2. Me pasa algo Curioso, tengo una entidad Empresa y una entidad direcci贸n, con la misma relaci贸n que muestras, OneToOne, pero la generaci贸n de valor ID en la tabla direcci贸n es por secuencia a oracle. Cuando hago el persist con el objeto empresa, que contiene el objeto direccion, guarda perfectamente la id generada por el mapeo de la secuencia , pero en la tabla empresa me guarda la id en null, el JPA con eclipselink no es capaz de asignar el valor de id_direccion a en la tabla empresa, luego de aplicarse la secuencia en el objeto direcci贸n.

    1. Bueno, para comenzar seguramente tienes algo mal en el mapeo, ya que la base de datos no permite guardar null como llave primaria, por lo que seguramente no estas mapeando bien Entidad, por otro lado, revisa que estes usando un GeneratorValues de tipo de sequencia en la llave primaria

      1. Claro lo genero con sequence oracle y guarda bien el dato en el objeto direcci贸n, pero la id de direcci贸n en el objeto empresa lo deja nulo, parece que debiera usar flush() despu茅s de persist() porque la transacci贸n de jpa no se entera

        1. Es que regreso a lo mismo, no deber铆a de ser posible guardar NULL en una llave primaria, seguramente tu tabla no te la esta generando con llave primaria, por lo tanto, no le esta generando el ID, si el campo fuera realmente una llave primaria, ni siquiera pudieras guardar el registro.

          1. tal vez no me di a explicar bien, el id_direcci贸n de la tabla direcci贸n es clave primaria y la secuencia la genera y guarda un valor no nulo, la id_direcci贸n para la tabla Empresa queda en nulo, porque no se entera de @SequenceGenerator(name=”seq”, initialValue=1, allocationSize=1) de este mapeo de la tabla direcci贸n, la que queda nula es la fk en empresa, no la pk, la pk si genera la nueva id para el registro con la secuencia.

          2. Hola Ricardo, bueno es dificil para mi saber que esta pasando desde aqu铆, has intentado debugear el c贸digo para ver los valores de la entidad previo y despues de insertar?

  3. Estimado, estuve investigando y si tengo una tabla persona y una tabla usuario que emula una herencia, estuve viendo que funciona:

    public class Persona{

    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    @Column(unique=true, nullable=false)
    private int id_persona;

    @OneToOne(cascade=CascadeType.PERSIST, mappedBy = “persona”)
    private Usuario usuario;
    }

    public class Usuario{

    @Id
    @OneToOne
    @JoinColumn(name = “id_persona”)
    private Persona persona;
    }

    No he visto aqu铆 tratamiento de id como objeto y vi una segunda alternativa como tratamiento de herencia con algo as铆, que tampoco lo vi tratado aqu铆 como para ser implementado:

    @Entity
    @Table(name=”CUST”)
    @Inheritance(strategy=JOINED)
    @DiscriminatorValue(“CUST”)
    public class Customer { … }

    @Entity
    @Table(name=”VCUST”)
    @DiscriminatorValue(“VCUST”)
    @PrimaryKeyJoinColumn(name=”CUST_ID”)
    public class ValuedCustomer extends Customer { … }

    1. Fijate que yo nunca he usado la herencia con JPA mas all谩 de ejemplos de laboratorio, en lo particular no me gusta usar herencia para JPA

  4. Hola una pregunta por lo que le铆 del @JoinColumn tiene una propiedad de unique, mi pregunta es 驴En una relaci贸n de @OneToOne es necesario decirle al campo que va hacer 煤nico, o ya autom谩ticamente JPA lo realiza?
    Y otra duda en muchas ocasiones es necesario poner el “ON DELETE” o “ON UPDATE” en “CASCADE”, al menos usando postgresql es de esta manera, como se pondr铆a esto con JPA, por lo que veo, el por defecto me los deja en “NO ACTION”….. Muchas gracias

    1. hola Carlos,

      El unique te sirve para forzar a JPA para crear la restricci贸n, por lo que mi sugerencia es que la pongas si quieres que se cree, referente a si se crear por default en una relaci贸n onoToOne, bueno, es dificil darte una respuesta, por que muchas veces depende de la implementaci贸n de JPA.
      Respecto a tu segunda pregunta, puedes personalizar eso con la propiedad cascade.
      saludos.

  5. Hola! Tengo una relaci贸n oneToOne, de categorizaci贸n. Una Actividad pertenece a un TipoActividad, y ya. Pero me est谩n pidiendo mostrar una lista de actividades agrupadas por TipoActividad. Ac谩 no tengo manera de hacer un fecth. Obligatoriamente necesitar铆a un JPQL?

    1. Hola Mariana, me confunde tu modelo, por que si tienes una relaci贸n OneToOne, como podr铆as agruparlas, si en tenor铆a es una relaci贸n uno a uno, por lo tanto, no habr铆a m谩s de un registro.

  6. Hola! por qu茅 no ser铆a posible dejar el campo id de payments tanto como llave primaria y llave for谩nea a la vez ? y as铆 no tener la columna adicional de FK_INVOICE ?

  7. Hola que tal, estoy tratando de hacer un @OneToMany , la tabla padre tiene 5 pk’s y la tabla hija (detalle), no tiene pk pero se relacionan mediante dos PK de la tabla padre. Me esta saliendo un error de PK para la tabla hija y cuando le agrego @ID sobre @ManyToOne, me sale error de JoinColumns.
    pag (tabla padre)
    //bi-directional many-to-one association to Doc
    @OneToMany(mappedBy=”pag”, fetch=FetchType.EAGER)
    private List docs;

    doc(tabla hija)
    @ManyToOne
    @JoinColumns({
    @JoinColumn(name=”doc_formul”, referencedColumnName=”pag_formul”),
    @JoinColumn(name=”doc_numdoc”, referencedColumnName=”pag_ndocpa”)
    })
    private Pag pag;

    alguna recomendaci贸n ?.

    Saludos

    1. Lo que si veo, es que dices que la tabla Hija (detalle) no tiene PK, lo cual en JPA es inv谩lido, ya que todas las tablas deber铆an de tener Llave primaria, adem谩s, la lista docs, deber铆a de ser usando gen茅ricos List

  8. excelente info amigo, una duda por ejemplo si yo tengo la tabla direcciones y esta se relaciona a usuarios y sucursales y ambas cardinalidades forman 1to1 es decir mi sucursal solo tiene una direcci贸n y una direcci贸n solo tiene una sucursal, y esto se replica para usuarios es correcto hacer el mapeo en direcciones de la misma forma pero haciendo el mapeo con su correspondiente atributo

Deja un comentario

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