Jak zmapować klucz złożony za pomocą JPA i Hibernacji?

203

W tym kodzie, jak wygenerować klasę Java dla klucza złożonego (jak złożyć klucz w trybie hibernacji):

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
źródło
1
Naprawdę dobry zestaw przykładów: vladmihalcea.com/2016/08/01/…
TecHunter

Odpowiedzi:

415

Mapować klucza kompozytowego, można użyć EmbeddedId albo na IdClassadnotacje. Wiem, że to pytanie nie dotyczy wyłącznie WZP, ale mają również zastosowanie reguły określone w specyfikacji. Oto one:

2.1.4 Klucze podstawowe i tożsamość jednostki

...

Złożony klucz główny musi odpowiadać albo jednemu trwałemu polu lub właściwości, albo zestawowi takich pól lub właściwości, jak opisano poniżej. Należy zdefiniować klasę klucza podstawowego, aby reprezentowała złożony klucz podstawowy. Złożone klucze podstawowe zwykle powstają podczas mapowania ze starszych baz danych, gdy klucz bazy danych składa się z kilku kolumn. I adnotacje stosowane są do oznaczenia złożonych kluczy pierwotnych. Patrz sekcje 9.1.14 i 9.1.15.EmbeddedIdIdClass

...

W przypadku złożonych kluczy podstawowych obowiązują następujące zasady:

  • Klasa klucza podstawowego musi być publiczna i musi mieć publiczny konstruktor bez argumentu.
  • Jeśli używany jest dostęp oparty na właściwościach, właściwości klasy klucza podstawowego muszą być publiczne lub chronione.
  • Podstawową klasą kluczy musi być serializable.
  • Klasa klucza podstawowego musi definiować equalsi hashCode metody. Semantyka równości wartości dla tych metod musi być spójna z równością bazy danych dla typów baz danych, na które mapowany jest klucz.
  • Złożony klucz podstawowy musi być reprezentowany i odwzorowany jako klasa, którą można osadzić (patrz Sekcja 9.1.14, „EmbeddedId Adnotation”) lub musi być reprezentowany i odwzorowany na wiele pól lub właściwości klasy encji (patrz Sekcja 9.1.15, „IdClass Adnotacja").
  • Jeśli złożona klasa klucza głównego jest odwzorowana na wiele pól lub właściwości klasy encji, nazwy pól lub właściwości klucza podstawowego w klasie klucza podstawowego i nazwy klasy encji muszą być zgodne, a ich typy muszą być takie same.

Z IdClass

Klasa złożonego klucza głównego może wyglądać (może być statyczną klasą wewnętrzną):

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
}

A jednostka:

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

IdClassAdnotacja odwzorowuje kilka pól do PK stołowego.

Z EmbeddedId

Klasa złożonego klucza głównego może wyglądać (może być statyczną klasą wewnętrzną):

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

A jednostka:

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

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

    //...
}

@EmbeddedIdAdnotacja odwzorowuje klasę PK PK do stołu.

Różnice:

  • Z punktu widzenia modelu fizycznego nie ma różnic
  • @EmbeddedIdw jakiś sposób komunikuje się jaśniej, że klucz jest kluczem złożonym, a IMO ma sens, gdy połączony pk jest samą znaczącą jednostką lub jest ponownie używany w kodzie .
  • @IdClass przydatne jest określenie, że niektóre kombinacje pól są unikalne, ale nie mają one specjalnego znaczenia .

Wpływają również na sposób pisania zapytań (co powoduje, że są mniej lub bardziej szczegółowe):

  • z IdClass

    select t.levelStation from Time t
  • z EmbeddedId

    select t.timePK.levelStation from Time t

Bibliografia

  • Specyfikacja JPA 1.0
    • Sekcja 2.1.4 „Klucze podstawowe i tożsamość jednostki”
    • Sekcja 9.1.14 „Adnotacja osadzona”
    • Sekcja 9.1.15 „Adnotacja IdClass”
Pascal Thivent
źródło
15
Istnieje również rozwiązanie specyficzne dla hibernacji: odwzoruj wiele właściwości jako właściwości @Id bez deklarowania klasy zewnętrznej jako typu identyfikatora (i użyj adnotacji IdClass). Patrz 5.1.2.1. Identyfikator złożony w podręczniku Hibernacja.
Johan Boberg
Czy mógłbyś rzucić okiem na to pytanie ? Mam problemy ze złożonym kluczem podstawowym, ponieważ pole członka idjest zawsze nulli nie jest generowane: /
nazwa wyświetlana
Mógłbym podać przykład z narzędziem pobierającym i ustawiającym, ponieważ w każdym przypadku mam trudności z ustaleniem, gdzie wchodzą w grę. Zwłaszcza przykład IdClass. dzięki. Aha, włącznie z nazwami kolumn, dzięki.
Jeremy
chociaż hibernacja specyficzne rozwiązanie jest przestarzałe.
Nikhil Sahu
Z dokumentów Hibernacji Adnotacje na temat @IdClass: „Został odziedziczony po ciemnych czasach EJB 2 dla wstecznej kompatybilności i zalecamy, aby go nie używać (dla uproszczenia)”.
Marco Ferrari
49

Musisz użyć @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-Dimitri Roy
źródło
@ Thierry-DimitriRoy, jak mogę przypisać timeId.levelStation i timeId.confPathID. Czy możesz podać przykład?
Duc Tran
@ Thierry-DimitriRoy Czy klasa podstawowa nie może być statyczną klasą wewnętrzną klasy bytu?
Nikhil Sahu
Tak, może być
Samy Omar
17

Jak wyjaśniłem w tym artykule , zakładając, że masz następujące tabele bazy danych:

wprowadź opis zdjęcia tutaj

Najpierw musisz utworzyć @Embeddableholding złożony identyfikator:

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

Dzięki temu możemy zmapować Employeebyt, który korzysta ze złożonego identyfikatora, dodając do niego adnotacje @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;
    }
}

PhonePodmiot, który ma @ManyToOneskojarzenie z Employee, musi odnosić się do kompozytowego identyfikator z grupy macierzystej przez dwóch @JoinColumnprzekształceń:

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

Aby uzyskać więcej informacji, sprawdź ten artykuł .

Vlad Mihalcea
źródło
Czy istnieje narzędzie, które może generować EmployeeId ze schematu db?
Leon
Wypróbuj narzędzia hibernacji. Ma do tego narzędzie inżynierii odwrotnej.
Vlad Mihalcea
7

Klasa klucza podstawowego musi definiować metody equals i hashCode

  1. Wdrażając równe, powinieneś użyć instanceof, aby umożliwić porównanie z podklasami. Jeśli Hibernacja leniwy ładuje relację jeden do jednego lub wiele do jednego, będziesz mieć proxy dla klasy zamiast zwykłej klasy. Serwer proxy jest podklasą. Porównanie nazw klas nie powiedzie się.
    Mówiąc bardziej technicznie: powinieneś postępować zgodnie z zasadą substytucji Liskowsa i ignorować symetryczność.
  2. Następnym pułapkiem jest użycie czegoś takiego jak name.equals (that.name) zamiast name.equals (that.getName ()) . Pierwszy zawiedzie, jeśli jest to serwer proxy.

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

Mikrofon
źródło
6

Wygląda na to, że robisz to od zera. Spróbuj użyć dostępnych narzędzi inżynierii odwrotnej, takich jak Netbeans Entities z bazy danych, aby przynajmniej zautomatyzować podstawy (np. Osadzone identyfikatory). To może stać się ogromnym bólem głowy, jeśli masz wiele stolików. Sugeruję, aby nie wymyślać na nowo koła i używać jak największej liczby dostępnych narzędzi, aby zredukować kodowanie do minimum i najważniejszej części tego, co zamierzasz zrobić.

javydreamercsw
źródło
5

Weźmy prosty przykład. Powiedzmy dwie tabele o nazwie testi customersą tam opisane jako:

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

Jest jeszcze jeden stół, który śledzi tests i 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));

Widzimy, że w tabeli tests_purchasedklucz podstawowy jest kluczem złożonym, więc użyjemy <composite-id ...>...</composite-id>znacznika w hbm.xmlpliku odwzorowania. PurchasedTest.hbm.xmlBędzie więc wyglądać następująco:

<?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>

Ale to nie koniec. W Hibernacji używamy session.load ( entityClass, id_type_object), aby znaleźć i załadować byt za pomocą klucza podstawowego. W przypadku kluczy złożonych, obiekt ID powinien być osobną klasą ID (w powyższym przypadku PurchasedTestIdklasą), która po prostu deklaruje podstawowe atrybuty klucza, jak poniżej :

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

Ważną kwestią jest to, że implementujemy również dwie funkcje hashCode()i equals()ponieważ Hibernacja na nich polega.

Dinesh Kandpal
źródło
2

Inną opcją jest mapowanie jako Mapa elementów kompozytowych w tabeli ConfPath.

To mapowanie skorzystałoby z indeksu na (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;
    }
}

Mapowanie:

<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>
Maurice Perry
źródło
1

Korzystanie z 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>  

Korzystanie z adnotacji

Klasa klucza kompozytowego

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

Klasa podmiotu

@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
źródło
1
To nie ma sensu, potrzebuje klucza podstawowego
Mazen Embaby
w tytule mówi klucz złożony, który nie musi być pierwotny
Enerccio
proszę sprawdzić, co napisał klucz podstawowy
Mazen Embaby