Embeber llave primaria con @EmbeddedId

Tutorial de JPAOtra manera de definir llaves primarías compuestas es con la Enumeración @EmbeddedId, la cual permite marcar una clase como ID. A diferencia de @IdClass, este método no requiere definir los atriburos de la llave primaria en la entidad, sino que solo hace falta agregar como atributo la clase que contiene todos los campos.

Una diferencia que tiene este método con respecto al @IdClass, es qué es necesario que la clase ID esta anotada a nivel de clase con la anotación @Embeddable. Esto le dice a JPA que esta clase se puede embeber dentro de otra.

Vamos a retomar la entidad Telephone y la clase TelephonePK y las modificaremos que en lugar de usar @Id, utilice @EmbeddedId. Modificaremos la clase TelephonePK y quedaría de la siguiente manera:

package com.obb.jpa.jpaturorial.entity.pk;

import java.util.Objects;
import javax.persistence.Embeddable;

/**
 * @author Oscar Blancarte
 */
@Embeddable
public class TelephonePK {
    private Long employeId;
    private String telType;

    @Override
    public int hashCode() {
        int hash = 7;
        hash = 59 * hash + Objects.hashCode(this.employeId);
        hash = 59 * hash + Objects.hashCode(this.telType);
        return hash;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if (obj == null) {
            return false;
        }
        if (getClass() != obj.getClass()) {
            return false;
        }
        final TelephonePK other = (TelephonePK) obj;
        if (!Objects.equals(this.telType, other.telType)) {
            return false;
        }
        if (!Objects.equals(this.employeId, other.employeId)) {
            return false;
        }
        return true;
    }
    /** GET and SET */
}

Observemos primero que nada que se le agrego la anotación @Embeddable a nivel de clase y los método equals y hashCode continuan definidos.

En segundo lugar, modificaremos la entidad Telephone y quedaría de la siguiente manera:

package com.obb.jpa.jpaturorial.entity;

import com.obb.jpa.jpaturorial.entity.pk.TelephonePK;
import javax.persistence.EmbeddedId;
import javax.persistence.Entity;
import javax.persistence.Table;

/**
 * @author Oscar Blancarte
 */
@Entity
@Table(name = "Telephones")
public class Telephone {
    
    @EmbeddedId
    private TelephonePK id;
    
    private String number;

    /** GET and SET */
}

Primero que nada, observemos que hemos retirado las propiedades employeeId y telType para ser sustituido por la case TelephonePK, a la cual le agregamos la anotación @EmbeddedId.

La anotación @EmbeddedId provocara que los campos definidos en la clase TelephonePK sean considerados como si fueran parte de la clase Telephone.

Cabe mencionar que tanto @IdClass como @EmbeddedId darán el mismo resultado en tiempo de ejecución, por lo que la única diferencia es solo a nivel del código. Recordemos que con @IdClass es necesario definir los campo que serán la llave primaria tanto en la Entidad como en las clases Id, en cambio, con @EmbeddedId solo será necesario embeber la llave como una propiedad más de la case. Particularmente yo prefiero trabajar con @EmbeddedId para evitar repetir atributos.

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.
👈👈

Finalmente veamos cómo quedaría la tabla generada por JPA, la cual es igual en estructura a la tabla generada utilizando @IdClass que vimos en la sección anterior:

Tabla con @EmbeddedId

NOTA: Este artículo es solo una sección del Tutorial de JPA, para ver el contenido completo de este tutorial regresa al Índice en el botón de abajo.

AnteriorÍndiceSiguiente

7 thoughts to “Embeber llave primaria con @EmbeddedId”

  1. Saludos: en mi caso he estado manejando entidades con dos llaves de una manera muy sencilla mas o menos de la siguiente manera:
    En mi clase Usuario ademas de los campos propios de la clase, tengo esta anotacion y me crea una tabla UsuarioPantalla con 2 campos idUsuario e idPantalla. La cual me mantiene una relacion entre la clase Usuario y la clase Pantalla.
    @Entity
    @Table(name = "Usuario")
    public class Usuario extends EntidadBase implements Serializable {
    private static final long serialVersionUID = -6695788587077076257L;
    @Column(name = "clave", unique = true, nullable = false)
    private String clave;

    @Column(name = "nombre", nullable = false)
    private String nombre;

    @ManyToMany(fetch = FetchType.LAZY)
    @JoinTable(name = "UsuarioPantalla"
    , joinColumns = { @JoinColumn(name = "idUsuario", nullable = false, updatable = false) }
    , inverseJoinColumns = { @JoinColumn(name = "idPantalla", nullable = false, updatable = false) })
    private Set pantallas = new HashSet();

    public Set getPantallas() {
    return pantallas;
    }
    public void setPantallas(Set pantallas) {
    this.pantallas = pantallas;
    }

    …..
    en JS lo implemento asi mas o menos
    var Ousuariodb = db.get(Usuario, idUsuario);
    var Opantalladb = db.get(Pantalla, idPantalla);
    Ousuariodb.getPantallas().add(Opantalladb);

    El metodos getPantallas() tambien me trea una lista con todos las pantallas de un usuario, ahi todo bien.

    Hasta el momento no he tenido problemas al manejar relacionadores con llaves dobles El PROBLEMA viene cuando quiero crear un relacionador con tres llaves supongamos que quiciera un relacionador entre las clases Usuario, Pantalla y Empresa. ¿Cual seria la manera mas sencilla de implemetar esto?
    Veo tu codigo tan diferente que no se si sea porque el mio este implementado en eclipse o de plano sean cosas totalmente diferentes.
    Muchas gracias por su tiempo.

    1. Hola Mario, para empezar, tu propiedad pantallas no está muy clara, por que no tiene el tipo genérico, asumo que guardas otras entidades dentro.

      Lo que utilizaría para crear una llave compuesta de dos o más campos sería crear una clase con las 3 propiedades y anotarla con @Embeddable, luego, en la entidad crear una propiedad de la clase anterior y anotarla con @EmbeddedId, esto le dirá a JPA que los campos de la primera clase será la llave primaria.
      espero que esto resuelva tu duda.

  2. Hola Óscar, estoy en un proyecto que tengo 4 tablas que utiliza la mismas pk que la tabla padre, he creado una clase para meter las pk compuestas y desde el la clase padre llamo a esa clase para crear una sola pk, pero para utilizar esas pk como seria las anotaciones en las clases hijas para poder utilizar esas pk que serían de las 3 clases hijas restantes. Mi duda es llamo desde las clases hijas a la clase padre o a la clase que tiene las pk compuestas en cada variable que es pk en la clase hijas. Debo poner en la clase hijo mencion a la pk del padre ej:
    @Column(name= “ncampo”)
    private ClasepadrePk id.

    Sería correcto, si no es así díme como debería ser. Gracias un abrazo.

    1. Hola Juan, no estoy seguro de haber entendido tu pregunta, pero creo que tienes el siguiente escenario, tienes una Entidad padre con una llave compuesta, y luego tiene 3 clases hijas que quieres que tengan una referencia al padre, entonces el problema que tienes es que, no sabemos como anotar la relación en las clases hijas para hacer referencia al padre. si mi premisa es correcta, entonces tendrás que hacer los siguiente, deberás crear una propiedad del tipo Padre y anotarla con @JoinColumns, observa que termina con S, esa anotación, te permitirá meter un array de @JoinColun (sin S), y deberás meter un @JoinColum por cada columna que quieres mapear, por ejemplo:

      @JoinColumns({
              @JoinColumn(
                  name = "company_id",
                  referencedColumnName = "company_id"),
              @JoinColumn(
                  name = "employee_number",
                  referencedColumnName = "employee_number")
          })

  3. Una consulta, estimado, en la clase @Embeddable ¿puedo definir los nombres de columnas diferentes a la tabla como lo mapeo comunmente?
    me refiero: @Column(name = “nombre_id”)

    ¿ O me veo obligado a coincidir con los mismos nombres de capos de la tabla en la BD?

    1. Hola Ricardo, no entendí muy bien la pregunta, pero entiendo que lo que quieres hacer es sobre escribir el nombre de las columnas que definen la clase Embeddable, en tal caso, deberías de utilizar la anotación @AttributeOverrides, la cual permite sobreescribir las propiedades previamente definidas.
      Saludos.

  4. Cual sería la diferencia, sí en lugar de crear una nueva clase TelephonePK con @Embeddable…. solo creamos
    las relaciones desde la clase Telephone @ManyToOne y desde employe @OneToMany___ si el resultado es el mismo??

Deja un comentario

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