Как сопоставить составной ключ с Hibernate? - PullRequest
179 голосов
/ 27 августа 2010

В этом коде, как создать класс 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;

Ответы [ 8 ]

392 голосов
/ 28 августа 2010

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

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

...

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

...

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

  • Класс первичного ключа должен быть открытым и иметь открытый аргумент no-arg. конструктор.
  • Если используется доступ на основе свойств, свойства первичного ключа класс должен быть публичным или защищенным.
  • Класс первичного ключа должен быть serializable.
  • Класс первичного ключа должны определить equals и hashCode методы. Семантика значения равенство для этих методов должно быть в соответствии с равенством базы данных для типов баз данных, к которым ключ сопоставлен.
  • Составной первичный ключ должен быть представлен и отображен как встраиваемый класс (см. раздел 9.1.14, «EmbeddedId Annotation») или должно быть представлены и сопоставлены с несколькими поля или свойства объекта класс (см. раздел 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 в таблицу 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»
47 голосов
/ 27 августа 2010

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

@Entity
class Time {
    @EmbeddedId
    TimeId id;

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

@Embeddable
class TimeId implements Serializable {
    Integer levelStation;
    Integer confPathID;
}
13 голосов
/ 15 января 2018

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

enter image description here

Сначала вам нужно создать @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;
    }
}

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

6 голосов
/ 18 января 2013

Класс первичного ключа должен определять методы 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 голосов
/ 28 августа 2010

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

4 голосов
/ 09 марта 2017

Давайте рассмотрим простой пример.Допустим, две таблицы с именами 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));

Еще одна таблица, которая отслеживает test s и 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 голосов
/ 29 августа 2010

Другим вариантом является сопоставление в виде карты составных элементов в таблице 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>
0 голосов
/ 14 декабря 2017

Использование 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
        );
    }    
}
...