Вопрос-ответ

How to map a composite key with JPA and Hibernate?

Как сопоставить составной ключ с JPA и гибернацией?

В этом коде показано, как сгенерировать класс Java для составного ключа (как создать составной ключ в hibernate):

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;
Переведено автоматически
Ответ 1

Для сопоставления составного ключа вы можете использовать EmbeddedId или IdClass аннотации. Я знаю, что этот вопрос не относится строго к JPA, но правила, определенные спецификацией, также применимы. Итак, вот они:


2.1.4 Первичные ключи и идентификация сущности


...


Составной первичный ключ должен соответствовать либо одному постоянному полю или свойству, либо набору таких полей или свойств, как описано ниже. Класс первичного ключа должен быть определен для представления составного первичного ключа. Составные первичные ключи обычно возникают при сопоставлении из устаревших баз данных, когда ключ базы данных состоит из нескольких столбцов. Аннотации EmbeddedId и IdClass используются для обозначения составных первичных ключей. Смотрите разделы 9.1.14 и 9.1.15.


...


Для составных первичных ключей применяются следующие правила:



  • Класс первичного ключа должен быть открытым и должен иметь открытый конструктор без аргументов.

  • Если используется доступ на основе свойств, свойства класса первичного ключа должны быть общедоступными или защищенными.

  • Класс первичного ключа должен быть serializable.

  • Класс первичного ключа должен определять equals и hashCode методы. Семантика равенства значений для этих методов должна соответствовать равенству базы данных для типов баз данных, которым сопоставляется ключ.

  • Составной первичный ключ должен быть либо представлен и сопоставлен как встраиваемый класс (см. Раздел 9.1.14, “Аннотация EmbeddedId”), либо должен быть представлен и сопоставлен с несколькими полями или свойствами класса entity (см. Раздел 9.1.15, “Аннотация IdClass”).

  • Если класс составного первичного ключа сопоставлен нескольким полям или свойствам класса entity, имена полей или свойств первичного ключа в классе primary key и в классе entity должны соответствовать и их типы должны быть одинаковыми.


С помощью IdClass

Класс для составного первичного ключа может выглядеть следующим образом (может быть статическим внутренним классом):

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
}

И сущность:

@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
}

В IdClass аннотации несколько полей сопоставляются с таблицей PK.

С EmbeddedId

Класс для составного первичного ключа может выглядеть следующим образом (может быть статическим внутренним классом):

@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
}

И сущность:

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

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

//...
}

В @EmbeddedId аннотации класс PK сопоставляется с таблицей PK.

Различия:


  • С точки зрения физической модели различий нет

  • @EmbeddedId каким-то образом более четко сообщается, что ключ является составным ключом, и IMO имеет смысл когда объединенный pk либо сам по себе является значимой сущностью, либо повторно используется в вашем коде.

  • @IdClass полезно указать, что некоторая комбинация полей уникальна, но они не имеют особого значения.

Они также влияют на способ написания запросов (делая их более или менее подробными):


  • с IdClass


    select t.levelStation from Time t

  • с EmbeddedId


    select t.timePK.levelStation from Time t

Ссылки


  • Спецификация JPA 1.0

    • Раздел 2.1.4 "Первичные ключи и идентификация сущности"

    • Раздел 9.1.14 "Аннотация EmbeddedId"

    • Раздел 9.1.15 "Аннотация IdClass"


Ответ 2

Вам нужно использовать @EmbeddedId:

@Entity
class Time {
@EmbeddedId
TimeId id;

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

@Embeddable
class TimeId implements Serializable {
Integer levelStation;
Integer confPathID;
}
Ответ 3

Предполагая, что у вас есть следующие таблицы базы данных:

Таблицы базы данных

Сначала вам нужно создать @Embeddable содержащий составной идентификатор:

@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());
}
}

Используя это, мы можем сопоставить Employee объект, который использует составной идентификатор, пометив его с помощью @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;
}
}

Phone Сущность, которая имеет @ManyToOne ассоциацию с Employee, должна ссылаться на составной идентификатор из родительского класса с помощью двух @JoinColumnсопоставлений:

@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;
}
}
Ответ 4

Класс первичного ключа должен определять методы equals и hashCode


  1. При реализации equals вы должны использовать instanceof, чтобы разрешить сравнение с подклассами. Если Hibernate lazy загружает отношение "один к одному" или "многие к одному", у вас будет прокси для класса вместо обычного класса. Прокси - это подкласс. Сравнение имен классов завершится неудачей.
    Более технически: вы должны следовать принципу подстановки Liskows и игнорировать симметричность.

  2. Следующая ошибка заключается в использовании чего-то вроде name.equals(that.name) вместо name.equals(это.getName()). Первый завершится ошибкой, если это прокси.

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

java hibernate jpa