Як скласти складовий ключ за допомогою JPA та Hibernate?


203

У цьому коді, як створити клас 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;


1
Дійсно хороший набір прикладів: vladmihalcea.com/2016/08/01/…
TecHunter

Відповіді:


415

Для відображення складеного ключа, ви можете використовувати EmbeddedId або в IdClassанотації. Мені відомо, що це питання не стосується суто JPA, але також застосовуються правила, визначені специфікацією. Так ось вони:

2.1.4 Первинні ключі та особистість особи

...

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

...

Для складених первинних ключів застосовуються такі правила:

  • Клас первинного ключа повинен бути загальнодоступним і мати публічний конструктор без аргументів.
  • Якщо використовується доступ на основі властивостей, властивості класу первинного ключа повинні бути відкритими або захищеними.
  • Клас первинного ключа повинен бути 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 для настільних ПК.

Відмінності:

  • З точки зору фізичної моделі, відмінностей немає
  • @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 "Вбудована анотація"
    • Розділ 9.1.15 "Анотація IdClass"

15
Існує також специфічне для Hibernate рішення: Позначте декілька властивостей як властивості @Id, не оголосивши зовнішній клас типом ідентифікатора (та використовуйте анотацію IdClass). Див. 5.1.2.1. Складений ідентифікатор у посібнику зі сну.
Йохан Бобберг

Чи можете ви, будь ласка, поглянути на це питання ? У мене виникають проблеми зі складеним первинним ключем, оскільки поле члена idзавжди nullта не генерується: /
displayname

Чи можете, будь ласка, навести приклад із геттером та сетером, оскільки у мене виникають труднощі бачити, де вони вступають у гру в будь-якому випадку. Особливо приклад IdClass. Дякую. О, включаючи назви стовпців, дякую.
Джеремі

хоча специфічне рішення, що перебуває у сплячому режимі, не використовується.
Нікхіл Саху

З Документів анотацій зі сплячого сну про @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. Чи можете ви надати приклад?
Duc Tran

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

Більш детально ознайомтеся з цією статтею .


Чи є інструмент, який може генерувати EmployeeIId із схеми db?
Леон

Спробуйте засоби сплячого режиму. Для цього є інструмент зворотного проектування.
Влад Михальча

7

Клас первинного ключа повинен визначати рівні та методи hashCode

  1. При реалізації рівних вам слід використовувати instanceof, щоб дозволити порівняння з підкласами. Якщо в режимі глибокого сну завантажується відношення один до одного або багато хто до одного, у вас буде проксі-клас для класу замість простого класу. Проксі - це підклас. Порівнювати назви класів не вдалося б.
    Більш технічно: Ви повинні слідувати Принципу заміни Liskows та ігнорувати симетричність.
  2. Наступний підводний камінь використовує щось на зразок name.equals (that.name) замість name.equals (that.getName ()) . Перший вийде з ладу, якщо це проксі.

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


6

Схоже, ви робите це з нуля. Спробуйте використовувати доступні інструменти зворотного інжинірингу, такі як Netbeans Entities з Database, щоб принаймні автоматизувати основи (наприклад, вбудовані ідентифікатори). Це може стати величезним головним болем, якщо у вас багато таблиць. Я пропоную уникати винаходиння колеса і використовувати якомога більше інструментів, щоб зменшити кодування до мінімальної та найважливішої частини, що ви маєте намір зробити.


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 повинен бути окремий клас 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()коли сплячий режим покладається на них.


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

1
Це не має сенсу, Йому потрібен первинний ключ
Мазен Ембабі

в заголовку він каже, що складений ключ, який не повинен бути первинним
Enerccio

будь ласка, перевірте, що він написав sql первинний ключ (levelStation, confPathID)
Mazen Embaby
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.