Làm cách nào để ánh xạ khóa tổng hợp với JPA và Hibernate?


204

Trong mã này, cách tạo lớp Java cho khóa tổng hợp (cách kết hợp khóa trong chế độ ngủ đông):

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
Một tập hợp các ví dụ thực sự tốt: vladmihalcea.com/2016/08/01/NH
TecHunter

Câu trả lời:


415

Để ánh xạ một phím composite, bạn có thể sử dụng EmbeddedId hoặc các IdClasschú thích. Tôi biết câu hỏi này không nghiêm túc về JPA nhưng các quy tắc được xác định bởi đặc tả cũng được áp dụng. Vì vậy, đây là:

2.1.4 Khóa chính và danh tính thực thể

...

Khóa chính tổng hợp phải tương ứng với một trường hoặc thuộc tính liên tục duy nhất hoặc với một tập hợp các trường hoặc thuộc tính như được mô tả dưới đây. Một lớp khóa chính phải được xác định để thể hiện khóa chính tổng hợp. Các khóa chính tổng hợp thường phát sinh khi ánh xạ từ cơ sở dữ liệu cũ khi khóa cơ sở dữ liệu bao gồm một số cột. Các chú thích EmbeddedIdIdClassđược sử dụng để biểu thị các khóa chính tổng hợp. Xem phần 9.1,14 và 9.1.15.

...

Các quy tắc sau đây áp dụng cho các khóa chính tổng hợp:

  • Lớp khóa chính phải là công khai và phải có một hàm tạo không có đối số công khai.
  • Nếu truy cập dựa trên thuộc tính được sử dụng, các thuộc tính của lớp khóa chính phải được công khai hoặc được bảo vệ.
  • Lớp khóa chính phải là serializable.
  • Lớp khóa chính phải định nghĩa equalshashCode phương thức. Các ngữ nghĩa của đẳng thức giá trị cho các phương thức này phải phù hợp với đẳng thức cơ sở dữ liệu cho các loại cơ sở dữ liệu mà khóa được ánh xạ.
  • Khóa chính tổng hợp phải được biểu diễn và ánh xạ dưới dạng một lớp có thể nhúng (xem Phần 9.1,14, Chú thích nhúng nhúng Chú thích) hoặc phải được biểu diễn và ánh xạ tới nhiều trường hoặc thuộc tính của lớp thực thể (xem Phần 9.1.15, Id Idlass Chú thích trực tiếp).
  • Nếu lớp khóa chính tổng hợp được ánh xạ tới nhiều trường hoặc thuộc tính của lớp thực thể, tên của các trường hoặc thuộc tính của khóa chính trong lớp khóa chính và các lớp của lớp thực thể phải tương ứng và các kiểu của chúng phải giống nhau.

Với một IdClass

Lớp cho khóa chính tổng hợp có thể trông giống như (có thể là lớp bên trong tĩnh):

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
}

Và thực thể:

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

Các IdClasschú thích bản đồ nhiều lĩnh vực đến PK bảng.

Với EmbeddedId

Lớp cho khóa chính tổng hợp có thể trông giống như (có thể là lớp bên trong tĩnh):

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

Và thực thể:

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

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

    //...
}

Các @EmbeddedIdchú thích bản đồ một lớp PK bảng PK.

Sự khác biệt:

  • Từ quan điểm mô hình vật lý, không có sự khác biệt
  • @EmbeddedIdbằng cách nào đó truyền đạt rõ ràng hơn rằng khóa là khóa tổng hợp và IMO có ý nghĩa khi pk kết hợp là chính thực thể có ý nghĩa hoặc được sử dụng lại trong mã của bạn .
  • @IdClass là hữu ích để xác định rằng một số kết hợp các trường là duy nhất nhưng chúng không có ý nghĩa đặc biệt .

Chúng cũng ảnh hưởng đến cách bạn viết truy vấn (khiến chúng dài hơn hoặc ít hơn):

  • với IdClass

    select t.levelStation from Time t
  • với EmbeddedId

    select t.timePK.levelStation from Time t

Người giới thiệu

  • Thông số kỹ thuật JPA 1.0
    • Mục 2.1.4 "Khóa chính và danh tính thực thể"
    • Mục 9.1,14 "Chú thích nhúng"
    • Mục 9.1.15 "Chú thích IdClass"

15
Ngoài ra còn có một giải pháp dành riêng cho Hibernate: Ánh xạ nhiều thuộc tính dưới dạng các thuộc tính @Id mà không khai báo một lớp bên ngoài là loại định danh (và sử dụng chú thích IdClass). Xem 5.1.2.1. Định danh tổng hợp trong hướng dẫn sử dụng Hibernate.
Johan Boberg

Bạn có thể xem câu hỏi này xin vui lòng? Tôi gặp sự cố với khóa chính tổng hợp do trường thành viên idluôn luôn nullvà không được tạo: /
displayname

Có thể vui lòng nêu một ví dụ với getter và setter vì tôi gặp khó khăn khi xem chúng xuất hiện ở đâu trong cả hai trường hợp. Đặc biệt là ví dụ IdClass. cảm ơn. Oh và bao gồm tên cột, cảm ơn.
Jeremy

mặc dù giải pháp cụ thể ngủ đông bị phản đối.
Nikhil Sahu

Từ các tài liệu Chú thích Hibernate , về @IdClass: "Nó đã được kế thừa từ thời kỳ đen tối của EJB 2 vì sự tương thích ngược và chúng tôi khuyên bạn không nên sử dụng nó (vì đơn giản)."
Marco Ferrari

49

Bạn cần sử dụng @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 làm cách nào tôi có thể gán timeId.levelStation và timeId.confPathID. Bạn có thể cung cấp một ví dụ xin vui lòng?
Đức Trần

@ Thierry-DimitriRoy Lớp chính có thể không phải là lớp bên trong tĩnh của lớp thực thể không?
Nikhil Sahu

Vâng, nó có thể là
Samy Omar

17

Như tôi đã giải thích trong bài viết này , giả sử bạn có các bảng cơ sở dữ liệu sau:

nhập mô tả hình ảnh ở đây

Trước tiên, bạn cần tạo @Embeddableđịnh danh tổng hợp:

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

Với vị trí này, chúng ta có thể ánh xạ Employeethực thể sử dụng định danh tổng hợp bằng cách chú thích nó với @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;
    }
}

Thực Phonethể có @ManyToOneliên kết đến Employee, cần tham chiếu định danh tổng hợp từ lớp cha thông qua hai @JoinColumnánh xạ:

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

Để biết thêm chi tiết, hãy xem bài viết này .


Có một công cụ nào có thể tạo EmployeeId từ lược đồ db không?
Leon

Hãy thử Công cụ Hibernate. Nó có một công cụ kỹ thuật Reverse cho điều đó.
Vlad Mihalcea

7

Lớp khóa chính phải định nghĩa các phương thức bằng và hashCode

  1. Khi thực hiện bằng, bạn nên sử dụng instanceof để cho phép so sánh với các lớp con. Nếu Hibernate lười biếng tải một đến một hoặc nhiều mối quan hệ, bạn sẽ có một proxy cho lớp thay vì lớp đơn giản. Một proxy là một lớp con. So sánh tên lớp sẽ thất bại.
    Về mặt kỹ thuật hơn: Bạn nên tuân theo Nguyên tắc thay thế Liskows và bỏ qua tính đối xứng.
  2. Cạm bẫy tiếp theo là sử dụng một cái gì đó như name.equals (that.name) thay vì name.equals (that.getName ()) . Đầu tiên sẽ thất bại, nếu đó là một proxy.

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


6

Có vẻ như bạn đang làm điều này từ đầu. Hãy thử sử dụng các công cụ kỹ thuật đảo ngược có sẵn như Thực thể Netbeans từ Cơ sở dữ liệu để ít nhất có được các tính năng cơ bản tự động (như id nhúng). Điều này có thể trở thành một vấn đề đau đầu nếu bạn có nhiều bàn. Tôi đề nghị tránh phát minh lại bánh xe và sử dụng càng nhiều công cụ càng tốt để giảm mã hóa xuống mức tối thiểu và quan trọng nhất, những gì bạn định làm.


5

Hãy lấy một ví dụ đơn giản. Giả sử hai bảng được đặt tên testcustomerđược mô tả là:

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

Có thêm một bảng để theo dõi tests và 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));

Chúng ta có thể thấy rằng trong bảng tests_purchased, khóa chính là khóa tổng hợp, vì vậy chúng ta sẽ sử dụng <composite-id ...>...</composite-id>thẻ trong hbm.xmltệp ánh xạ. Vì vậy, PurchasedTest.hbm.xmlsẽ trông như:

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

Nhưng nó không kết thúc ở đây. Trong Hibernate, chúng tôi sử dụng session.load ( entityClass, id_type_object) để tìm và tải thực thể bằng khóa chính. Trong trường hợp các khóa tổng hợp, đối tượng ID phải là một lớp ID riêng (trong trường hợp trên là một PurchasedTestIdlớp) chỉ khai báo các thuộc tính khóa chính như bên dưới :

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

Điểm quan trọng là chúng tôi cũng thực hiện hai chức năng hashCode()equals()vì Hibernate dựa vào chúng.


2

Một tùy chọn khác là ánh xạ dưới dạng Bản đồ của các thành phần tổng hợp trong bảng ConfPath.

Ánh xạ này sẽ được hưởng lợi từ một chỉ mục trên (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;
    }
}

Ánh xạ:

<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

Sử dụng 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>  

Sử dụng chú thích

Lớp khóa tổng hợp

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

Lớp thực thể

@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
Điều đó không có nghĩa gì, Anh ta cần chìa khóa chính
Mazen Embaby

trong tiêu đề, ông nói khóa tổng hợp, không phải là khóa chính
Enerccio

vui lòng kiểm tra những gì anh ấy đã viết khóa chính
Mazen Embaby
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.