Bagaimana cara memetakan kunci komposit dengan JPA dan Hibernate?


204

Dalam kode ini, cara membuat kelas Java untuk kunci komposit (cara kunci komposit di hibernasi):

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
Seperangkat contoh yang sangat baik: vladmihalcea.com/2016/08/01/...
TecHunter

Jawaban:


415

Untuk memetakan kunci komposit, Anda dapat menggunakan EmbeddedId atau yang IdClasspenjelasan. Saya tahu pertanyaan ini tidak sepenuhnya tentang JPA tetapi aturan yang ditentukan oleh spesifikasi juga berlaku. Jadi inilah mereka:

2.1.4 Kunci Utama dan Identitas Entitas

...

Kunci primer gabungan harus sesuai dengan bidang atau properti persisten tunggal atau ke set bidang atau properti seperti yang dijelaskan di bawah ini. Kelas kunci primer harus didefinisikan untuk mewakili kunci primer komposit. Kunci primer komposit biasanya muncul ketika memetakan dari database lama ketika kunci basis data terdiri dari beberapa kolom. The EmbeddedIddan IdClassanotasi digunakan untuk menunjukkan kunci primer komposit. Lihat bagian 9.1.14 dan 9.1.15.

...

Aturan berikut ini berlaku untuk kunci primer komposit:

  • Kelas kunci utama harus publik dan harus memiliki konstruktor no-arg publik.
  • Jika akses berbasis properti digunakan, properti dari kelas kunci utama harus publik atau dilindungi.
  • Kelas kunci primer harus serializable.
  • Kelas kunci utama harus didefinisikan equalsdan hashCode metode. Semantik persamaan nilai untuk metode-metode ini harus konsisten dengan kesetaraan basis data untuk tipe-tipe basis data yang dipetakan kuncinya.
  • Kunci primer komposit harus diwakili dan dipetakan sebagai kelas yang dapat disematkan (lihat Bagian 9.1.14, “Anotasi EmbeddedId”) atau harus diwakili dan dipetakan ke beberapa bidang atau properti dari kelas entitas (lihat Bagian 9.1.15, “IdClass Anotasi").
  • Jika kelas kunci primer komposit dipetakan ke beberapa bidang atau properti dari kelas entitas, nama-nama bidang kunci utama atau properti di kelas kunci primer dan orang-orang dari kelas entitas harus sesuai dan tipenya harus sama.

Dengan sebuah IdClass

Kelas untuk kunci primer komposit bisa seperti (bisa menjadi kelas dalam statis):

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
}

Dan entitas:

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

The IdClasspenjelasan peta beberapa bidang ke PK meja.

Dengan EmbeddedId

Kelas untuk kunci primer komposit bisa seperti (bisa menjadi kelas dalam statis):

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

Dan entitas:

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

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

    //...
}

The @EmbeddedIdpenjelasan peta kelas PK ke meja PK.

Perbedaan:

  • Dari sudut pandang model fisik, tidak ada perbedaan
  • @EmbeddedIdentah bagaimana berkomunikasi lebih jelas bahwa kuncinya adalah kunci komposit dan IMO masuk akal ketika pk gabungan adalah entitas yang bermakna itu sendiri atau digunakan kembali dalam kode Anda .
  • @IdClass berguna untuk menentukan bahwa beberapa kombinasi bidang adalah unik tetapi ini tidak memiliki arti khusus .

Mereka juga memengaruhi cara Anda menulis kueri (membuatnya lebih atau kurang bertele-tele):

  • dengan IdClass

    select t.levelStation from Time t
  • dengan EmbeddedId

    select t.timePK.levelStation from Time t

Referensi

  • Spesifikasi JPA 1.0
    • Bagian 2.1.4 "Kunci Utama dan Identitas Entitas"
    • Bagian 9.1.14 "Anotasi EmbeddedId"
    • Bagian 9.1.15 "Anotasi IdClass"

15
Ada juga solusi khusus Hibernate: Memetakan beberapa properti sebagai properti @Id tanpa mendeklarasikan kelas eksternal sebagai tipe pengidentifikasi (dan menggunakan anotasi IdClass). Lihat 5.1.2.1. Pengidentifikasi komposit dalam manual Hibernate.
Johan Boberg

Bisakah Anda melihat pertanyaan ini ? Saya mengalami masalah dengan kunci primer komposit karena bidang anggota idselalu nulldan tidak dapat dihasilkan: /
displayname

Bisa berikan contoh dengan pengambil dan penyetel karena saya mengalami kesulitan melihat di mana mereka bermain dalam kedua kasus. Terutama contoh IdClass. Terima kasih. Oh dan termasuk nama kolom, terima kasih.
Jeremy

meskipun solusi khusus hibernasi sudah tidak digunakan lagi.
Nikhil Sahu

Dari dokumen Hibernate Annotations , tentang @IdClass: "Ini telah diwarisi dari zaman kegelapan EJB 2 untuk kompatibilitas mundur dan kami menyarankan Anda untuk tidak menggunakannya (untuk kesederhanaan)."
Marco Ferrari

49

Anda harus menggunakan @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 bagaimana saya bisa menetapkan timeId.levelStation dan timeId.confPathID. Bisakah Anda memberikan contoh?
Duc Tran

@ Thierry-DimitriRoy Bisakah kelas primer tidak menjadi kelas dalam statis dari kelas entitas?
Nikhil Sahu

Ya, bisa jadi
Samy Omar

17

Seperti yang saya jelaskan dalam artikel ini , dengan asumsi Anda memiliki tabel database berikut:

masukkan deskripsi gambar di sini

Pertama, Anda perlu membuat @Embeddableholding identifier komposit:

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

Dengan ini, kita dapat memetakan Employeeentitas yang menggunakan pengidentifikasi komposit dengan memberi anotasi dengan @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;
    }
}

The Phoneentitas yang memiliki @ManyToOneasosiasi untuk Employee, perlu referensi identifier komposit dari kelas induk melalui dua @JoinColumnpemetaan:

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

Untuk lebih jelasnya, lihat artikel ini .


Apakah ada alat yang dapat menghasilkan EmployeeId dari skema db?
Leon

Coba Hibernasi Alat. Ini memiliki alat rekayasa terbalik untuk itu.
Vlad Mihalcea

7

Kelas kunci utama harus mendefinisikan metode equals dan hashCode

  1. Ketika menerapkan equals, Anda harus menggunakan instanceof untuk memungkinkan membandingkan dengan subclass. Jika Hibernate lazy memuat hubungan satu ke satu atau banyak ke satu, Anda akan memiliki proxy untuk kelas alih-alih kelas biasa. Proxy adalah subkelas. Membandingkan nama kelas akan gagal.
    Lebih teknis: Anda harus mengikuti Prinsip Pergantian Liskows dan mengabaikan simetrisitas.
  2. Jebakan berikutnya menggunakan sesuatu seperti name.equals (that.name) alih-alih name.equals (that.getName ()) . Yang pertama akan gagal, jika itu adalah proxy.

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


6

Sepertinya Anda melakukan ini dari awal. Coba gunakan alat reverse engineering yang tersedia seperti Netbeans Entities dari Database untuk setidaknya mendapatkan dasar-dasar otomatis (seperti id tertanam). Ini bisa menjadi sakit kepala besar jika Anda memiliki banyak meja. Saya sarankan menghindari menciptakan kembali roda dan menggunakan sebanyak mungkin alat yang tersedia untuk mengurangi pengkodean ke bagian minimum dan paling penting, apa yang ingin Anda lakukan.


5

Mari kita ambil contoh sederhana. Katakanlah dua tabel bernama testdan di customersana digambarkan sebagai:

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

Satu lagi meja ada yang melacak testdan 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));

Kita dapat melihat bahwa dalam tabel tests_purchasedkunci utama adalah kunci komposit, jadi kita akan menggunakan <composite-id ...>...</composite-id>tag pada hbm.xmlfile pemetaan. Jadi PurchasedTest.hbm.xmlakan terlihat seperti:

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

Tapi itu tidak berakhir di sini. Di Hibernate kami menggunakan session.load ( entityClass, id_type_object) untuk menemukan dan memuat entitas menggunakan kunci utama. Dalam kasus kunci komposit, objek ID harus kelas ID yang terpisah (dalam kasus di atas PurchasedTestIdkelas) yang hanya menyatakan atribut kunci utama seperti di bawah ini :

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

Poin penting adalah bahwa kami juga mengimplementasikan dua fungsi hashCode()dan equals()sebagai Hibernate bergantung padanya.


2

Pilihan lain adalah memetakan adalah sebagai Peta elemen komposit dalam tabel ConfPath.

Pemetaan ini akan mendapat manfaat dari indeks aktif (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;
    }
}

Pemetaan:

<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

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

Menggunakan Anotasi

Kelas Kunci Komposit

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

Kelas Entitas

@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
Tidak masuk akal, Dia membutuhkan kunci utama
Mazen Embaby

dalam judul dia mengatakan kunci komposit, yang tidak harus utama
Enerccio

silakan periksa apa yang dia tulis kunci primer
Mazen Embaby
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.