How to map a composite key with JPA and Hibernate?

前端 未结 8 1235
旧时难觅i
旧时难觅i 2020-11-22 10:37

In this code, how to generate a Java class for the composite key (how to composite key in hibernate):

create table Time (
     levelStation int(15) not null,         


        
相关标签:
8条回答
  • Let's take a simple example. Let's say two tables named test and customer are there described as:

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

    One more table is there which keeps the track of tests and 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));
    

    We can see that in the table tests_purchased the primary key is a composite key, so we will use the <composite-id ...>...</composite-id> tag in the hbm.xml mapping file. So the PurchasedTest.hbm.xml will look like:

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

    But it doesn't end here. In Hibernate we use session.load (entityClass, id_type_object) to find and load the entity using primary key. In case of composite keys, the ID object should be a separate ID class (in above case a PurchasedTestId class) which just declares the primary key attributes like below:

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

    Important point is that we also implement the two functions hashCode() and equals() as Hibernate relies on them.

    0 讨论(0)
  • 2020-11-22 10:46

    The primary key class must define equals and hashCode methods

    1. When implementing equals you should use instanceof to allow comparing with subclasses. If Hibernate lazy loads a one to one or many to one relation, you will have a proxy for the class instead of the plain class. A proxy is a subclass. Comparing the class names would fail.
      More technically: You should follow the Liskows Substitution Principle and ignore symmetricity.
    2. The next pitfall is using something like name.equals(that.name) instead of name.equals(that.getName()). The first will fail, if that is a proxy.

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

    0 讨论(0)
  • 2020-11-22 10:49

    To map a composite key, you can use the EmbeddedId or the IdClass annotations. I know this question is not strictly about JPA but the rules defined by the specification also applies. So here they are:

    2.1.4 Primary Keys and Entity Identity

    ...

    A composite primary key must correspond to either a single persistent field or property or to a set of such fields or properties as described below. A primary key class must be defined to represent a composite primary key. Composite primary keys typically arise when mapping from legacy databases when the database key is comprised of several columns. The EmbeddedId and IdClass annotations are used to denote composite primary keys. See sections 9.1.14 and 9.1.15.

    ...

    The following rules apply for composite primary keys:

    • The primary key class must be public and must have a public no-arg constructor.
    • If property-based access is used, the properties of the primary key class must be public or protected.
    • The primary key class must be serializable.
    • The primary key class must define equals and hashCode methods. The semantics of value equality for these methods must be consistent with the database equality for the database types to which the key is mapped.
    • A composite primary key must either be represented and mapped as an embeddable class (see Section 9.1.14, “EmbeddedId Annotation”) or must be represented and mapped to multiple fields or properties of the entity class (see Section 9.1.15, “IdClass Annotation”).
    • If the composite primary key class is mapped to multiple fields or properties of the entity class, the names of primary key fields or properties in the primary key class and those of the entity class must correspond and their types must be the same.

    With an IdClass

    The class for the composite primary key could look like (could be a static inner class):

    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
    }
    

    And the entity:

    @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 IdClass annotation maps multiple fields to the table PK.

    With EmbeddedId

    The class for the composite primary key could look like (could be a static inner class):

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

    And the entity:

    @Entity
    class Time implements Serializable {
        @EmbeddedId
        private TimePK timePK;
    
        private String src;
        private String dst;
        private Integer distance;
        private Integer price;
    
        //...
    }
    

    The @EmbeddedId annotation maps a PK class to table PK.

    Differences:

    • From the physical model point of view, there are no differences
    • @EmbeddedId somehow communicates more clearly that the key is a composite key and IMO makes sense when the combined pk is either a meaningful entity itself or it reused in your code.
    • @IdClass is useful to specify that some combination of fields is unique but these do not have a special meaning.

    They also affect the way you write queries (making them more or less verbose):

    • with IdClass

      select t.levelStation from Time t
      
    • with EmbeddedId

      select t.timePK.levelStation from Time t
      

    References

    • JPA 1.0 specification
      • Section 2.1.4 "Primary Keys and Entity Identity"
      • Section 9.1.14 "EmbeddedId Annotation"
      • Section 9.1.15 "IdClass Annotation"
    0 讨论(0)
  • 2020-11-22 10:53

    Looks like you are doing this from scratch. Try using available reverse engineering tools like Netbeans Entities from Database to at least get the basics automated (like embedded ids). This can become a huge headache if you have many tables. I suggest avoid reinventing the wheel and use as many tools available as possible to reduce coding to the minimum and most important part, what you intent to do.

    0 讨论(0)
  • 2020-11-22 10:59

    You need to use @EmbeddedId:

    @Entity
    class Time {
        @EmbeddedId
        TimeId id;
    
        String src;
        String dst;
        Integer distance;
        Integer price;
    }
    
    @Embeddable
    class TimeId implements Serializable {
        Integer levelStation;
        Integer confPathID;
    }
    
    0 讨论(0)
  • 2020-11-22 11:00

    As I explained in this article, assuming you have the following database tables:

    First, you need to create the @Embeddable holding the composite identifier:

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

    With this in place, we can map the Employee entity which uses the composite identifier by annotating it with @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 Phone entity which has a @ManyToOne association to Employee, needs to reference the composite identifier from the parent class via two @JoinColumnmappings:

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

    For more details, check out this article.

    0 讨论(0)
提交回复
热议问题