¿Cómo mapear una clave compuesta con JPA e Hibernate?

Resuelto kaaf asked hace 14 años • 0 respuestas

En este código, cómo generar una clase Java para la clave compuesta (cómo componer la clave en hibernación):

create table Time (
     levelStation int(15) not null,
     src varchar(100) not null,
     dst varchar(100) not null,
     distance int(15) not null,
     price int(15) not null,
     confPathID int(15) not null,
     constraint ConfPath_fk foreign key(confPathID) references ConfPath(confPathID),
     primary key (levelStation, confPathID)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
kaaf avatar Aug 27 '10 21:08 kaaf
Aceptado

Para asignar una clave compuesta, puede utilizar las anotaciones EmbeddedId oIdClass . Sé que esta pregunta no se trata estrictamente de JPA, pero también se aplican las reglas definidas por la especificación. Así que aquí están:

2.1.4 Claves primarias e identidad de la entidad

...

Una clave primaria compuesta debe corresponder a un único campo o propiedad persistente o a un conjunto de dichos campos o propiedades como se describe a continuación. Se debe definir una clase de clave primaria para representar una clave primaria compuesta. Las claves primarias compuestas suelen surgir cuando se asignan desde bases de datos heredadas cuando la clave de la base de datos se compone de varias columnas. Las anotaciones EmbeddedIdy IdClassse utilizan para indicar claves primarias compuestas. Ver secciones 9.1.14 y 9.1.15.

...

Se aplican las siguientes reglas para las claves primarias compuestas:

  • La clase de clave principal debe ser pública y debe tener un constructor público sin argumentos.
  • Si se utiliza el acceso basado en propiedades, las propiedades de la clase de clave principal deben ser públicas o protegidas.
  • La clase de clave principal debe ser serializable.
  • La clase de clave principal debe definir equalsy hashCode métodos. La semántica de igualdad de valores para estos métodos debe ser coherente con la igualdad de la base de datos para los tipos de bases de datos a los que está asignada la clave.
  • Una clave primaria compuesta debe representarse y asignarse como una clase integrable (consulte la Sección 9.1.14, “Anotación EmbeddedId”) o debe representarse y asignarse a múltiples campos o propiedades de la clase de entidad (consulte la Sección 9.1.15, “IdClass Anotación").
  • Si la clase de clave primaria compuesta se asigna a múltiples campos o propiedades de la clase de entidad, los nombres de los campos o propiedades de clave primaria en la clase de clave primaria y los de la clase de entidad deben corresponder y sus tipos deben ser los mismos.

Con unIdClass

La clase para la clave primaria compuesta podría verse así (podría ser una clase interna estática):

public class TimePK implements Serializable {
    protected Integer levelStation;
    protected Integer confPathID;

    public TimePK() {}

    public TimePK(Integer levelStation, Integer confPathID) {
        this.levelStation = levelStation;
        this.confPathID = confPathID;
    }
    // equals, hashCode
}

Y la entidad:

@Entity
@IdClass(TimePK.class)
class Time implements Serializable {
    @Id
    private Integer levelStation;
    @Id
    private Integer confPathID;

    private String src;
    private String dst;
    private Integer distance;
    private Integer price;

    // getters, setters
}

La IdClassanotación asigna varios campos a la tabla PK.

ConEmbeddedId

La clase para la clave primaria compuesta podría verse así (podría ser una clase interna estática):

@Embeddable
public class TimePK implements Serializable {
    protected Integer levelStation;
    protected Integer confPathID;

    public TimePK() {}

    public TimePK(Integer levelStation, Integer confPathID) {
        this.levelStation = levelStation;
        this.confPathID = confPathID;
    }
    // equals, hashCode
}

Y la entidad:

@Entity
class Time implements Serializable {
    @EmbeddedId
    private TimePK timePK;

    private String src;
    private String dst;
    private Integer distance;
    private Integer price;

    //...
}

La @EmbeddedIdanotación asigna una clase PK a la tabla PK.

Diferencias:

  • Desde el punto de vista del modelo físico, no existen diferencias.
  • @EmbeddedIdde alguna manera comunica más claramente que la clave es una clave compuesta y, en mi opinión, tiene sentido cuando el pk combinado es una entidad significativa en sí misma o se reutiliza en su código .
  • @IdClass Es útil especificar que alguna combinación de campos es única pero estos no tienen un significado especial .

También afectan la forma en que escribe las consultas (haciéndolas más o menos detalladas):

  • conIdClass

    select t.levelStation from Time t
    
  • conEmbeddedId

    select t.timePK.levelStation from Time t
    

Referencias

  • Especificación JPA 1.0
    • Sección 2.1.4 "Claves primarias e identidad de entidad"
    • Sección 9.1.14 "Anotación EmbeddedId"
    • Sección 9.1.15 "Anotación IdClass"
Pascal Thivent avatar Aug 27 '2010 21:08 Pascal Thivent

Necesitas usar @EmbeddedId:

@Entity
class Time {
    @EmbeddedId
    TimeId id;

    String src;
    String dst;
    Integer distance;
    Integer price;
}

@Embeddable
class TimeId implements Serializable {
    Integer levelStation;
    Integer confPathID;
}
Thierry Roy avatar Aug 27 '2010 14:08 Thierry Roy

Suponiendo que tiene las siguientes tablas de base de datos:

tablas de base de datos

Primero, debe crear el @Embeddableidentificador compuesto que contiene:

@Embeddable
public class EmployeeId implements Serializable {
 
    @Column(name = "company_id")
    private Long companyId;
 
    @Column(name = "employee_number")
    private Long employeeNumber;
 
    public EmployeeId() {
    }
 
    public EmployeeId(Long companyId, Long employeeId) {
        this.companyId = companyId;
        this.employeeNumber = employeeId;
    }
 
    public Long getCompanyId() {
        return companyId;
    }
 
    public Long getEmployeeNumber() {
        return employeeNumber;
    }
 
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof EmployeeId)) return false;
        EmployeeId that = (EmployeeId) o;
        return Objects.equals(getCompanyId(), that.getCompanyId()) &&
                Objects.equals(getEmployeeNumber(), that.getEmployeeNumber());
    }
 
    @Override
    public int hashCode() {
        return Objects.hash(getCompanyId(), getEmployeeNumber());
    }
}

Con esto implementado, podemos mapear la Employeeentidad que usa el identificador compuesto anotándola con @EmbeddedId:

@Entity(name = "Employee")
@Table(name = "employee")
public class Employee {
 
    @EmbeddedId
    private EmployeeId id;
 
    private String name;
 
    public EmployeeId getId() {
        return id;
    }
 
    public void setId(EmployeeId id) {
        this.id = id;
    }
 
    public String getName() {
        return name;
    }
 
    public void setName(String name) {
        this.name = name;
    }
}

La Phoneentidad que tiene una @ManyToOneasociación con Employee, necesita hacer referencia al identificador compuesto de la clase principal a través de dos @JoinColumnasignaciones:

@Entity(name = "Phone")
@Table(name = "phone")
public class Phone {
 
    @Id
    @Column(name = "`number`")
    private String number;
 
    @ManyToOne
    @JoinColumns({
        @JoinColumn(
            name = "company_id",
            referencedColumnName = "company_id"),
        @JoinColumn(
            name = "employee_number",
            referencedColumnName = "employee_number")
    })
    private Employee employee;
 
    public Employee getEmployee() {
        return employee;
    }
 
    public void setEmployee(Employee employee) {
        this.employee = employee;
    }
 
    public String getNumber() {
        return number;
    }
 
    public void setNumber(String number) {
        this.number = number;
    }
}
Vlad Mihalcea avatar Jan 15 '2018 19:01 Vlad Mihalcea

La clase de clave principal debe definir métodos iguales y hashCode

  1. Al implementar iguales, debe usar instancia de para permitir la comparación con subclases. Si Hibernate carga de forma diferida una relación uno a uno o muchos a uno, tendrá un proxy para la clase en lugar de la clase simple. Un proxy es una subclase. Comparar los nombres de las clases fallaría.
    Más técnicamente: debes seguir el principio de sustitución de Liskows e ignorar la simetría.
  2. El siguiente problema es usar algo como name.equals(that.name) en lugar de name.equals(that.getName()) . El primero fallará, si es un proxy.

http://www.laliluna.de/jpa-hibernate-guide/ch06s06.html

Mike avatar Jan 18 '2013 16:01 Mike

Parece que estás haciendo esto desde cero. Intente utilizar herramientas de ingeniería inversa disponibles, como Netbeans Entities from Database, para al menos automatizar los conceptos básicos (como identificadores integrados). Esto puede convertirse en un gran dolor de cabeza si tienes muchas mesas. Sugiero evitar reinventar la rueda y utilizar tantas herramientas disponibles como sea posible para reducir la codificación a la parte mínima y más importante, lo que pretendes hacer.

javydreamercsw avatar Aug 27 '2010 20:08 javydreamercsw