Wie ordne ich einen zusammengesetzten Schlüssel mit JPA und Hibernate zu?

203

In diesem Code wird erläutert, wie eine Java-Klasse für den zusammengesetzten Schlüssel generiert wird (wie ein zusammengesetzter Schlüssel im Ruhezustand erstellt wird):

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
quelle
1
Eine wirklich gute Reihe von Beispielen: vladmihalcea.com/2016/08/01/…
TecHunter

Antworten:

415

Um einen zusammengesetzten Schlüssel zuzuordnen, können Sie die Anmerkungen EmbeddedId oder verwendenIdClass . Ich weiß, dass es bei dieser Frage nicht ausschließlich um JPA geht, aber es gelten auch die in der Spezifikation definierten Regeln. Also hier sind sie:

2.1.4 Primärschlüssel und Entitätsidentität

...

Ein zusammengesetzter Primärschlüssel muss entweder einem einzelnen persistenten Feld oder einer Eigenschaft entsprechen oder einer Reihe solcher Felder oder Eigenschaften, wie nachstehend beschrieben. Eine Primärschlüsselklasse muss definiert werden, um einen zusammengesetzten Primärschlüssel darzustellen. Zusammengesetzte Primärschlüssel entstehen normalerweise bei der Zuordnung aus älteren Datenbanken, wenn der Datenbankschlüssel aus mehreren Spalten besteht. Die Anmerkungen EmbeddedIdund IdClasswerden verwendet, um zusammengesetzte Primärschlüssel zu bezeichnen. Siehe Abschnitte 9.1.14 und 9.1.15.

...

Die folgenden Regeln gelten für zusammengesetzte Primärschlüssel:

  • Die Primärschlüsselklasse muss öffentlich sein und einen öffentlichen Konstruktor ohne Argumente haben.
  • Wenn ein eigenschaftsbasierter Zugriff verwendet wird, müssen die Eigenschaften der Primärschlüsselklasse öffentlich oder geschützt sein.
  • Die Primärschlüsselklasse muss sein serializable.
  • Die Primärschlüsselklasse muss equalsund hashCode Methoden definieren . Die Semantik der Wertgleichheit für diese Methoden muss mit der Datenbankgleichheit für die Datenbanktypen übereinstimmen, denen der Schlüssel zugeordnet ist.
  • Ein zusammengesetzter Primärschlüssel muss entweder als einbettbare Klasse dargestellt und zugeordnet werden (siehe Abschnitt 9.1.14, „EmbeddedId-Annotation“) oder muss dargestellt und mehreren Feldern oder Eigenschaften der Entitätsklasse zugeordnet werden (siehe Abschnitt 9.1.15, „IdClass“) Anmerkung").
  • Wenn die zusammengesetzte Primärschlüsselklasse mehreren Feldern oder Eigenschaften der Entitätsklasse zugeordnet ist, müssen die Namen der Primärschlüsselfelder oder -eigenschaften in der Primärschlüsselklasse und die der Entitätsklasse übereinstimmen und ihre Typen müssen identisch sein.

Mit einem IdClass

Die Klasse für den zusammengesetzten Primärschlüssel könnte folgendermaßen aussehen (könnte eine statische innere Klasse sein):

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
}

Und die Entität:

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

Die IdClassAnmerkung ordnet der Tabelle PK mehrere Felder zu.

Mit EmbeddedId

Die Klasse für den zusammengesetzten Primärschlüssel könnte folgendermaßen aussehen (könnte eine statische innere Klasse sein):

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

Und die Entität:

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

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

    //...
}

Die @EmbeddedIdAnnotation ordnet eine PK-Klasse der Tabelle PK zu.

Unterschiede:

  • Aus Sicht des physikalischen Modells gibt es keine Unterschiede
  • @EmbeddedIdkommuniziert irgendwie klarer, dass der Schlüssel ein zusammengesetzter Schlüssel ist und IMO sinnvoll ist, wenn das kombinierte pk entweder selbst eine sinnvolle Entität ist oder in Ihrem Code wiederverwendet wird .
  • @IdClass Es ist nützlich anzugeben, dass eine Kombination von Feldern eindeutig ist, diese jedoch keine besondere Bedeutung haben .

Sie wirken sich auch auf die Art und Weise aus, wie Sie Abfragen schreiben (wodurch sie mehr oder weniger ausführlich sind):

  • mit IdClass

    select t.levelStation from Time t
  • mit EmbeddedId

    select t.timePK.levelStation from Time t

Verweise

  • JPA 1.0-Spezifikation
    • Abschnitt 2.1.4 "Primärschlüssel und Entitätsidentität"
    • Abschnitt 9.1.14 "EmbeddedId Annotation"
    • Abschnitt 9.1.15 "IdClass Annotation"
Pascal Thivent
quelle
15
Es gibt auch eine Hibernate-spezifische Lösung: Ordnen Sie mehrere Eigenschaften als @ ID-Eigenschaften zu, ohne eine externe Klasse als Bezeichner zu deklarieren (und verwenden Sie die Annotation IdClass). Siehe 5.1.2.1. Zusammengesetzte Kennung im Handbuch für den Ruhezustand.
Johan Boberg
Könnten Sie sich diese Frage bitte ansehen ? Ich habe Probleme mit einem zusammengesetzten Primärschlüssel, da das Mitgliedsfeld idimmer nullgeneriert wird und nicht generiert wird: /
Anzeigename
Könnte bitte ein Beispiel mit einem Getter und Setter geben, da ich in beiden Fällen Schwierigkeiten habe zu sehen, wo sie ins Spiel kommen. Besonders das IdClass-Beispiel. Vielen Dank. Oh und einschließlich Spaltennamen, danke.
Jeremy
Die spezifische Lösung für den Ruhezustand ist jedoch veraltet.
Nikhil Sahu
Aus den Hibernate Annotations-Dokumenten geht @IdClasses um Folgendes: "Es wurde aus Gründen der Abwärtskompatibilität aus dem dunklen Zeitalter von EJB 2 geerbt, und wir empfehlen, es aus Gründen der Einfachheit nicht zu verwenden."
Marco Ferrari
49

Sie müssen verwenden @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
quelle
@ Thierry-DimitriRoy Wie könnte ich die timeId.levelStation und timeId.confPathID zuweisen? Könnten Sie bitte ein Beispiel geben?
Duc Tran
@ Thierry-DimitriRoy Kann die Primärklasse keine statische innere Klasse der Entitätsklasse sein?
Nikhil Sahu
Ja, es könnte sein
Samy Omar
17

Wie ich in diesem Artikel erklärt habe , vorausgesetzt, Sie haben die folgenden Datenbanktabellen:

Geben Sie hier die Bildbeschreibung ein

Zunächst müssen Sie den Speicher erstellen, der @Embeddabledie zusammengesetzte Kennung enthält:

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

Mit dieser Option können wir die EmployeeEntität zuordnen, die den zusammengesetzten Bezeichner verwendet, indem wir sie mit @EmbeddedIdfolgenden Anmerkungen versehen :

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

Die PhoneEntität, zu der eine @ManyToOneZuordnung besteht Employee, muss über zwei @JoinColumnZuordnungen auf den zusammengesetzten Bezeichner aus der übergeordneten Klasse verweisen :

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

Weitere Informationen finden Sie in diesem Artikel .

Vlad Mihalcea
quelle
Gibt es ein Tool, das EmployeeId aus dem Datenbankschema generieren kann?
Leon
Versuchen Sie Hibernate Tools. Dafür gibt es ein Reverse Engineering-Tool.
Vlad Mihalcea
7

Die Primärschlüsselklasse muss die Methoden equals und hashCode definieren

  1. Bei der Implementierung von equals sollten Sie instanceof verwenden , um einen Vergleich mit Unterklassen zu ermöglichen. Wenn Hibernate Lazy eine Eins-zu-Eins- oder mehrere-zu-Eins-Beziehung lädt, haben Sie einen Proxy für die Klasse anstelle der einfachen Klasse. Ein Proxy ist eine Unterklasse. Der Vergleich der Klassennamen würde fehlschlagen.
    Technischer: Sie sollten das Liskows-Substitutionsprinzip befolgen und die Symmetrizität ignorieren.
  2. Die nächste Gefahr besteht darin, anstelle von name.equals (that.getName ()) so etwas wie name.equals (that.name) zu verwenden . Der erste schlägt fehl, wenn dies ein Proxy ist.

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

Mike
quelle
6

Sieht so aus, als würden Sie das von Grund auf neu machen. Versuchen Sie, verfügbare Reverse Engineering-Tools wie Netbeans Entities from Database zu verwenden, um zumindest die Grundlagen zu automatisieren (wie eingebettete IDs). Dies kann zu großen Kopfschmerzen führen, wenn Sie viele Tabellen haben. Ich schlage vor, das Rad nicht neu zu erfinden und so viele Werkzeuge wie möglich zu verwenden, um die Codierung auf das Minimum und das Wichtigste zu reduzieren, was Sie vorhaben.

javydreamercsw
quelle
5

Nehmen wir ein einfaches Beispiel. Nehmen wir an, zwei Tabellen werden benannt testund customerdort beschrieben als:

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

Es gibt noch eine Tabelle, die den Überblick über tests und 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));

Wir können sehen, dass in der Tabelle tests_purchasedder Primärschlüssel ein zusammengesetzter Schlüssel ist, daher werden wir das <composite-id ...>...</composite-id>Tag in der hbm.xmlZuordnungsdatei verwenden. Das PurchasedTest.hbm.xmlwird also so aussehen:

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

Aber hier endet es nicht. Im Ruhezustand verwenden wir session.load ( entityClass, id_type_object), um die Entität mithilfe des Primärschlüssels zu finden und zu laden. Bei zusammengesetzten Schlüsseln sollte das ID-Objekt eine separate ID-Klasse sein (im obigen Fall eine PurchasedTestIdKlasse), die nur die Primärschlüsselattribute wie folgt deklariert :

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

Wichtig ist, dass wir auch die beiden Funktionen implementieren hashCode()und uns equals()im Ruhezustand darauf verlassen.

Dinesh Kandpal
quelle
2

Eine andere Option ist die Zuordnung als Karte zusammengesetzter Elemente in der ConfPath-Tabelle.

Diese Zuordnung würde jedoch von einem Index für (ConfPathID, levelStation) profitieren.

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

Kartierung:

<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
quelle
1

Verwenden von 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>  

Annotation verwenden

Zusammengesetzte Schlüsselklasse

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

Entitätsklasse

@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
quelle
1
Es macht keinen Sinn, er braucht den Primärschlüssel
Mazen Embaby
im Titel sagt er zusammengesetzten Schlüssel, der nicht primär sein muss
Enerccio
Bitte überprüfen Sie, was er SQL- Primärschlüssel (LevelStation, confPathID) geschrieben hat
Mazen Embaby