如何映射复合键与休眠?
在这段代码中,如何为复合键生成一个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;
要映射组合键,可以使用EmbeddedId
或 IdClass
注释。 我知道这个问题不是严格的JPA,但规范定义的规则也适用。 所以在这里他们是:
2.1.4主键和实体标识
…
组合主键必须对应于单个持久性字段或属性,或者对应于一组这样的字段或属性,如下所述。 主键类必须定义为表示复合主键。 当数据库密钥由多个列组成时,通常会在从旧数据库映射时出现复合主键。
EmbeddedId
和IdClass
注释用于表示复合主键。 见9.1.14和9.1.15节。…
以下规则适用于复合主键:
- 主键类必须是公共的,并且必须有公共的无参数构造函数。
- 如果使用基于属性的访问,则主键类的属性必须是公共的或受保护的。
- 主键类必须是可
serializable
。- 主键类必须定义
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节“主键和实体标识”
- 第9.1.14节“EmbeddedId注释”
- 第9.1.15节“IdClass注释”
你需要使用@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)。 如果你有很多桌子,这可能会让你头痛不已。 我建议避免重新发明轮子,并尽可能多地使用可用的工具,以将编码减少到最小和最重要的部分,你打算做什么。
主键类必须定义equals和hashCode方法
- 当实现等于你应该使用instanceof允许与子类进行比较。 如果Hibernate懒惰加载一对一或多对一的关系,你将有一个类的代理,而不是普通的类。 代理是一个子类。 比较类名称将失败。
更具体地说:你应该遵循Liskows替代原则,忽略对称性。 - 下一个陷阱是使用类似name.equals(that.name)而不是name.equals(that.getName()) 。 第一个将会失败,如果这是一个代理。
我们举一个简单的例子。 假设有两个名为test
和customer
表格被描述为:
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));
还有一张桌子,它保持着test
和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( entityClass
, id_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; } }
重要的一点是,当Hibernate依赖它们时,我们也实现了两个函数hashCode()
和equals()
。
另一种选择是映射为ConfPath表中复合元素的映射。
不过,这个映射会受益于(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>