Как сопоставить составной ключ с JPA и Hibernate?

205

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

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
источник
1
Действительно хороший набор примеров: vladmihalcea.com/2016/08/01/…
TecHunter,

Ответы:

416

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

2.1.4 Первичные ключи и личность

...

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

...

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

  • Класс первичного ключа должен быть открытым и иметь открытый конструктор без аргументов.
  • Если используется доступ на основе свойств, свойства класса первичного ключа должны быть открытыми или защищенными.
  • Класс первичного ключа должен быть serializable.
  • Класс первичного ключа должен определять equalsи hashCode методы. Семантика равенства значений для этих методов должна согласовываться с равенством базы данных для типов базы данных, в которую отображается ключ.
  • Составной первичный ключ должен быть либо представлен и отображен как встраиваемый класс (см. Раздел 9.1.14, «Аннотация EmbeddedId»), либо должен быть представлен и сопоставлен с несколькими полями или свойствами класса сущности (см. Раздел 9.1.15, «IdClass»). Аннотация»).
  • Если составной класс первичного ключа сопоставлен с несколькими полями или свойствами класса сущности, имена полей или свойств первичного ключа в классе первичного ключа и имена класса сущности должны соответствовать, и их типы должны совпадать.

С 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 для настольных ПК.

Отличия:

  • С точки зрения физической модели, различий нет
  • @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»
Паскаль Тивент
источник
15
Существует также решение для Hibernate: сопоставьте несколько свойств как свойства @Id, не объявляя внешний класс типом идентификатора (и используйте аннотацию IdClass). См. 5.1.2.1. Составной идентификатор в руководстве по Hibernate.
Йохан Боберг
Не могли бы вы взглянуть на этот вопрос, пожалуйста? У меня проблемы с составным первичным ключом, поскольку поле члена idвсегда nullгенерируется и не генерируется: /
displayname
Не могли бы вы привести пример с геттером и сеттером, так как мне трудно увидеть, где они вступают в игру в любом случае. Особенно пример IdClass. Спасибо. Ох, и в том числе имена столбцов, спасибо.
Джереми
хотя спящий конкретный вариант устарел.
Нихил Саху
Из документации Hibernate Annotations о @IdClass: «Он был унаследован от темных веков EJB 2 для обратной совместимости, и мы рекомендуем вам не использовать его (для простоты ради»).
Марко Феррари
49

Вам необходимо использовать @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-DimitriRoy, как я могу назначить timeId.levelStation и timeId.confPathID. Не могли бы вы привести пример, пожалуйста?
Дык Тран
@ Thierry-DimitriRoy Может ли основной класс не быть статическим внутренним классом класса сущностей?
Нихил Саху,
Да, это может быть
Сами Омар
17

Как я объяснил в этой статье , при условии, что у вас есть следующие таблицы базы данных:

введите описание изображения здесь

Во-первых, вам нужно создать @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;
    }
}

Для более подробной информации, проверьте эту статью .

Влад Михалча
источник
Есть ли инструмент, который может генерировать EmployeeId из схемы БД?
Леон
Попробуйте Hibernate Tools. Для этого есть инструмент обратной разработки.
Влад Михалча
7

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

  1. При реализации equals вы должны использовать instanceof, чтобы разрешить сравнение с подклассами. Если Hibernate lazy загружает отношение один к одному или много к одному, у вас будет простой прокси-класс для класса. Прокси-это подкласс. Сравнение имен классов не удастся.
    Более технически: Вы должны следовать Принципу замещения Лисковса и игнорировать симметрию.
  2. Следующей ошибкой является использование чего-то вроде name.equals (that.name) вместо name.equals (that.getName ()) . Первый не удастся, если это прокси.

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

Майк
источник
6

Похоже, вы делаете это с нуля. Попробуйте использовать доступные инструменты обратного проектирования, такие как Netbeans Entities из базы данных, чтобы хотя бы автоматизировать основы (например, встроенные идентификаторы). Это может стать огромной головной болью, если у вас много столов. Я предлагаю не изобретать велосипед и использовать как можно больше инструментов, чтобы свести к минимуму кодирование до самой важной части того, что вы собираетесь делать.

javydreamercsw
источник
5

Давайте возьмем простой пример. Допустим, две таблицы названы testи customerтам описаны как:

create table test(
  test_id int(11) not null auto_increment,
  primary key(test_id));

create table customer(
  customer_id int(11) not null auto_increment,
  name varchar(50) not null,
  primary key(customer_id));

Есть еще одна таблица, которая отслеживает tests и customer:

create table tests_purchased(
  customer_id int(11) not null,
  test_id int(11) not null,
  created_date datetime not null,
  primary key(customer_id, test_id));

Мы можем видеть, что в таблице tests_purchasedпервичный ключ является составным ключом, поэтому мы будем использовать <composite-id ...>...</composite-id>тег в hbm.xmlфайле отображения. Так PurchasedTest.hbm.xmlбудет выглядеть так:

<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
  "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
  "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">

<hibernate-mapping>
  <class name="entities.PurchasedTest" table="tests_purchased">

    <composite-id name="purchasedTestId">
      <key-property name="testId" column="TEST_ID" />
      <key-property name="customerId" column="CUSTOMER_ID" />
    </composite-id>

    <property name="purchaseDate" type="timestamp">
      <column name="created_date" />
    </property>

  </class>
</hibernate-mapping>

Но это не заканчивается здесь. В Hibernate мы используем session.load ( entityClass, id_type_object), чтобы найти и загрузить объект, используя первичный ключ. В случае составных ключей объект ID должен быть отдельным классом идентификатора (в вышеупомянутом случае PurchasedTestIdклассом), который просто объявляет атрибуты первичного ключа, как показано ниже :

import java.io.Serializable;

public class PurchasedTestId implements Serializable {
  private Long testId;
  private Long customerId;

  // an easy initializing constructor
  public PurchasedTestId(Long testId, Long customerId) {
    this.testId = testId;
    this.customerId = customerId;
  }

  public Long getTestId() {
    return testId;
  }

  public void setTestId(Long testId) {
    this.testId = testId;
  }

  public Long getCustomerId() {
    return customerId;
  }

  public void setCustomerId(Long customerId) {
    this.customerId = customerId;
  }

  @Override
  public boolean equals(Object arg0) {
    if(arg0 == null) return false;
    if(!(arg0 instanceof PurchasedTestId)) return false;
    PurchasedTestId arg1 = (PurchasedTestId) arg0;
    return (this.testId.longValue() == arg1.getTestId().longValue()) &&
           (this.customerId.longValue() == arg1.getCustomerId().longValue());
  }

  @Override
  public int hashCode() {
    int hsCode;
    hsCode = testId.hashCode();
    hsCode = 19 * hsCode+ customerId.hashCode();
    return hsCode;
  }
}

Важным моментом является то, что мы также реализуем две функции, hashCode()и, equals()поскольку Hibernate полагается на них.

Динеш Кандпал
источник
2

Другим вариантом является сопоставление в виде карты составных элементов в таблице ConfPath.

Это сопоставление выиграет от индекса на (ConfPathID, levelStation), хотя.

public class ConfPath {
    private Map<Long,Time> timeForLevelStation = new HashMap<Long,Time>();

    public Time getTime(long levelStation) {
        return timeForLevelStation.get(levelStation);
    }

    public void putTime(long levelStation, Time newValue) {
        timeForLevelStation.put(levelStation, newValue);
    }
}

public class Time {
    String src;
    String dst;
    long distance;
    long price;

    public long getDistance() {
        return distance;
    }

    public void setDistance(long distance) {
        this.distance = distance;
    }

    public String getDst() {
        return dst;
    }

    public void setDst(String dst) {
        this.dst = dst;
    }

    public long getPrice() {
        return price;
    }

    public void setPrice(long price) {
        this.price = price;
    }

    public String getSrc() {
        return src;
    }

    public void setSrc(String src) {
        this.src = src;
    }
}

Отображение:

<class name="ConfPath" table="ConfPath">
    <id column="ID" name="id">
        <generator class="native"/>
    </id>
    <map cascade="all-delete-orphan" name="values" table="example"
            lazy="extra">
        <key column="ConfPathID"/>
        <map-key type="long" column="levelStation"/>
        <composite-element class="Time">
            <property name="src" column="src" type="string" length="100"/>
            <property name="dst" column="dst" type="string" length="100"/>
            <property name="distance" column="distance"/>
            <property name="price" column="price"/>
        </composite-element>
    </map>
</class>
Морис Перри
источник
1

Использование hbm.xml

    <composite-id>

        <!--<key-many-to-one name="productId" class="databaselayer.users.UserDB" column="user_name"/>-->
        <key-property name="productId" column="PRODUCT_Product_ID" type="int"/>
        <key-property name="categoryId" column="categories_id" type="int" />
    </composite-id>  

Использование аннотации

Класс составного ключа

public  class PK implements Serializable{
    private int PRODUCT_Product_ID ;    
    private int categories_id ;

    public PK(int productId, int categoryId) {
        this.PRODUCT_Product_ID = productId;
        this.categories_id = categoryId;
    }

    public int getPRODUCT_Product_ID() {
        return PRODUCT_Product_ID;
    }

    public void setPRODUCT_Product_ID(int PRODUCT_Product_ID) {
        this.PRODUCT_Product_ID = PRODUCT_Product_ID;
    }

    public int getCategories_id() {
        return categories_id;
    }

    public void setCategories_id(int categories_id) {
        this.categories_id = categories_id;
    }

    private PK() { }

    @Override
    public boolean equals(Object o) {
        if ( this == o ) {
            return true;
        }

        if ( o == null || getClass() != o.getClass() ) {
            return false;
        }

        PK pk = (PK) o;
        return Objects.equals(PRODUCT_Product_ID, pk.PRODUCT_Product_ID ) &&
                Objects.equals(categories_id, pk.categories_id );
    }

    @Override
    public int hashCode() {
        return Objects.hash(PRODUCT_Product_ID, categories_id );
    }
}

Класс сущности

@Entity(name = "product_category")
@IdClass( PK.class )
public  class ProductCategory implements Serializable {
    @Id    
    private int PRODUCT_Product_ID ;   

    @Id 
    private int categories_id ;

    public ProductCategory(int productId, int categoryId) {
        this.PRODUCT_Product_ID = productId ;
        this.categories_id = categoryId;
    }

    public ProductCategory() { }

    public int getPRODUCT_Product_ID() {
        return PRODUCT_Product_ID;
    }

    public void setPRODUCT_Product_ID(int PRODUCT_Product_ID) {
        this.PRODUCT_Product_ID = PRODUCT_Product_ID;
    }

    public int getCategories_id() {
        return categories_id;
    }

    public void setCategories_id(int categories_id) {
        this.categories_id = categories_id;
    }

    public void setId(PK id) {
        this.PRODUCT_Product_ID = id.getPRODUCT_Product_ID();
        this.categories_id = id.getCategories_id();
    }

    public PK getId() {
        return new PK(
            PRODUCT_Product_ID,
            categories_id
        );
    }    
}
Mazen Embaby
источник
1
Это не имеет смысла, ему нужен первичный ключ
Mazen Embaby
в заголовке он говорит составной ключ, который не должен быть первичным
Enerccio
пожалуйста, проверьте, что он написал первичный ключ
Mazen Embaby