如何映射一个组合键与JPA和Hibernate?

在这段代码中,如何为组合键生成一个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;
420580 次浏览

你需要使用@EmbeddedId:

@Entity
class Time {
@EmbeddedId
TimeId id;


String src;
String dst;
Integer distance;
Integer price;
}


@Embeddable
class TimeId implements Serializable {
Integer levelStation;
Integer confPathID;
}

看来你是从零开始的。尝试使用可用的逆向工程工具,如Netbeans Entities from Database,至少实现基本的自动化(如嵌入式id)。如果你有很多桌子,这可能会成为一个非常令人头痛的问题。我建议避免重新发明轮子,并使用尽可能多的可用工具,将编码减少到最小和最重要的部分,即您想要做的部分。

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

2.1.4主键和实体标识

...

复合主键必须 对应于单个中的任意一个 属性的持久化字段或属性 字段或属性的集合 下面描述。主键类 必须定义为表示 复合主键。复合 主键通常出现在 从遗留数据库进行映射时 数据库键由几个键组成 列。EmbeddedIdIdClass注释用于 表示复合主键。< / >强 章节9.1.14和9.1.15.

...

以下规则适用于 复合主键:

    主键类必须是公共的,必须有一个公共的no-arg 李构造函数。< /强> < / >
  • 如果使用基于属性的访问,则主键的属性 类必须为public或protected
  • 主键类必须是serializable
  • 主键类 必须定义equalshashCode 方法。 value的语义 这些方法的相等性必须是 与数据库的相等性一致 属性指向的数据库类型
  • . Key被映射 复合主键必须被表示和映射为 可嵌入类(见9.1.14节, " EmbeddedId Annotation ")或必须为 表示并映射到多个 实体的字段或属性 IdClass(见9.1.15节“IdClass . class”) 李注释”)。< / >
  • 如果复合主键类映射到多个字段或 实体类的属性 主键字段的名称或 主键类中的属性 而实体类的则必须 对应的,它们的类型必须是 李一样。< / >

使用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以某种方式更清楚地传达了键是复合键,IMO是有意义的当组合的pk本身是有意义的实体或在代码中重用时
  • @IdClass 指定某些字段的组合是唯一的,但这些字段没有特殊含义,这有用吗

它们还会影响你编写查询的方式(使它们变得更详细或更少):

  • IdClass < p >

    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"

另一种选择是将is映射为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>

主键类必须定义equals和hashCode方法

    当实现equals时,你应该使用运算符以允许与子类进行比较。如果Hibernate惰性加载一个一对一或多对一关系,您将获得类的代理,而不是普通类。代理是一个子类。比较类名将失败 更严格地说:你应该遵循Liskows替换原则,忽略对称性。李< / >
  1. 下一个陷阱是使用类似name.equals (that.name)的东西而不是name.equals (that.getName ())。如果这是一个代理的话,第一个将会失败。

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

让我们举一个简单的例子。让我们说两个名为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));

还有一个表用来跟踪__abc0和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));

我们可以看到,在表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类)它只是像下面那样声明主键属性:

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依赖它们。

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

假设你有以下数据库表:

Database tables

首先,你需要创建包含复合标识符的@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());
}
}

这样,我们就可以映射使用复合标识符的Employee实体,方法是用@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;
}
}

Phone实体与Employee@ManyToOne关联,需要通过两个__abc3映射从父类引用复合标识符:

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