首页 文章

如何使用Hibernate映射复合键?

提问于
浏览
169

在这段代码中,如何为复合键生成Java类(如何在hibernate中复合键):

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;

8 回答

  • 9

    要映射复合键,可以使用 EmbeddedId or IdClass 注释 . 我知道这个问题并不严格关于JPA,但规范定义的规则也适用 . 所以他们在这里:

    2.1.4主键和实体标识...复合主键必须对应于单个持久字段或属性,或者对应于下面描述的一组此类字段或属性 . 必须定义主键类以表示复合主键 . 当数据库键由多个列组成时,从旧数据库映射时,通常会出现复合主键 . EmbeddedId和IdClass注释用于表示复合主键 . 见9.1.14和9.1.15 . ...以下规则适用于复合主键:主键类必须是公共的,并且必须具有公共的无参数构造函数 . 如果使用基于属性的访问,则主键类的属性必须是公共的或受保护的 . 主键类必须是可序列化的 . 主键类必须定义equals和hashCode方法 . 这些方法的值相等的语义必须与键映射到的数据库类型的数据库相等一致 . 复合主键必须表示并映射为可嵌入类(请参见第9.1.14节“EmbeddedId注释”),或者必须表示并映射到实体类的多个字段或属性(请参见第9.1.15节“IdClass”)注解”) . 如果复合主键类映射到实体类的多个字段或属性,则主键类中的主键字段或属性的名称与实体类的名称必须对应,并且它们的类型必须相同 .

    使用IdClass

    复合主键的类可能看起来像(可能是静态内部类):

    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
    }
    

    而实体:

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

    IdClass 注释将多个字段映射到表PK .

    使用EmbeddedId

    复合主键的类可能看起来像(可能是静态内部类):

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

    而实体:

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

    @EmbeddedId 注释将PK类映射到表PK .

    差异:

    • 从物理模型的角度来看,没有差异

    • @EmbeddedId 以某种方式更清楚地传达了密钥是复合密钥,当组合的pk本身是有意义的实体或在代码中重用时,IMO是有意义的 .

    • @IdClass 可用于指定某些字段组合是唯一的,但这些字段组合没有特殊含义 .

    它们也会影响您编写查询的方式(使它们或多或少冗长):

    • IdClass
    select t.levelStation from Time t
    
    • EmbeddedId
    select t.timePK.levelStation from Time t
    

    参考文献

    • JPA 1.0规范

    • 第2.1.4节"Primary Keys and Entity Identity"

    • 第9.1.14节"EmbeddedId Annotation"

    • 第9.1.15节"IdClass Annotation"

  • 5

    你需要使用@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

    正如我在this article中解释的那样,假设您有以下数据库表:

    enter image description here

    首先,您需要创建包含复合标识符的 @Embeddable

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

    有了这个,我们可以通过用 @EmbeddedId 注释来映射使用复合标识符的 Employee 实体:

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

    Employee 具有 @ManyToOne 关联的 Phone 实体需要通过两个 @JoinColumn 映射从父类引用复合标识符:

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

    有关更多详细信息,请查看this article .

  • 41

    看起来你是从头开始这样做的 . 尝试使用可用的逆向工程工具,如Netbeans Entities from Database,至少可以自动完成基础知识(如嵌入式ID) . 如果你有很多 table ,这可能会成为一个巨大的问题 . 我建议避免重新发明轮子并使用尽可能多的工具来将编码减少到最小和最重要的部分,你打算做什么 .

  • 5

    The primary key class must define equals and hashCode methods

    • 实现equals时,您应该 use instanceof 以允许与子类进行比较 . 如果Hibernate延迟加载一对一或多对一关系,您将拥有该类的代理而不是普通类 . 代理是子类 . 比较类名会失败 .
      更技术上:您应该遵循Liskows替换原则并忽略对称性 .

    • 下一个陷阱是使用 name.equals(that.name) 而不是 name.equals(that.getName()) 之类的东西 . 如果是代理,第一个将失败 .

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

  • 3

    让's take a simple example. Let'说两个名为 testcustomer 的表被描述为:

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

    还有一个表可以跟踪 testcustomer

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

    我们可以看到表 tests_purchased 中的主键是复合键,因此我们将使用 hbm.xml 映射文件中的 <composite-id ...>...</composite-id> 标记 . 所以 PurchasedTest.hbm.xml 看起来像:

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

    但它并没有在这里结束 . 在Hibernate中,我们使用session.load( entityClassid_type_object )来使用主键查找和加载实体 . 在复合键的情况下,ID对象应该是一个单独的ID类(在上面的例子中是一个 PurchasedTestId 类) 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;
      }
    }
    

    重要的是,我们还实现了两个函数 hashCode()equals() ,因为Hibernate依赖它们 .

  • 2

    另一个选项是映射为ConfPath表中的复合元素的Map .

    但是,此映射将受益于(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;
        }
    }
    

    制图:

    <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>
    
  • 371

    使用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>
    

    使用注释

    复合密钥类

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

    实体类

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

相关问题