Java 如何使用 JPA 和 Hibernate 映射复合键?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/3585034/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How to map a composite key with JPA and Hibernate?
提问by kaaf
In this code, how to generate a Java class for the composite key (how to composite key in 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;
回答by Thierry-Dimitri Roy
You need to use @EmbeddedId
:
您需要使用@EmbeddedId
:
@Entity
class Time {
@EmbeddedId
TimeId id;
String src;
String dst;
Integer distance;
Integer price;
}
@Embeddable
class TimeId implements Serializable {
Integer levelStation;
Integer confPathID;
}
回答by javydreamercsw
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.
看起来你是从头开始做这件事。尝试使用可用的逆向工程工具,如数据库中的 Netbeans 实体,至少使基础知识自动化(如嵌入式 ID)。如果您有很多表,这可能会成为一个非常头痛的问题。我建议避免重新发明轮子,并使用尽可能多的可用工具将编码减少到最少和最重要的部分,即您打算做什么。
回答by Pascal Thivent
To map a composite key, you can use the EmbeddedId
orthe IdClass
annotations. I know this question is not strictly about JPA but the rules defined by the specification also applies. So here they are:
要映射组合键,你可以使用EmbeddedId
或在IdClass
注解。我知道这个问题严格来说并不是关于 JPA,但规范定义的规则也适用。所以他们在这里:
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
andIdClass
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
andhashCode
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.
2.1.4 主键和实体身份
...
复合主键必须对应于单个持久字段或属性,或者对应于如下所述的一组此类字段或属性。必须定义主键类来表示复合主键。当数据库键由多个列组成时,从旧数据库映射时通常会出现复合主键。的
EmbeddedId
和IdClass
注解用于表示复合主键。见 9.1.14 和 9.1.15 节。...
以下规则适用于复合主键:
- 主键类必须是公共的,并且必须有一个公共的无参数构造函数。
- 如果使用基于属性的访问,则主键类的属性必须是公共的或受保护的。
- 主键类必须是
serializable
.- 主键类必须定义
equals
和hashCode
方法。这些方法的值相等的语义必须与键映射到的数据库类型的数据库相等一致。- 复合主键必须表示和映射为可嵌入类(请参阅第 9.1.14 节,“EmbeddedId 注释”),或者必须表示并映射到实体类的多个字段或属性(请参阅第 9.1.15 节,“IdClass注解”)。
- 如果复合主键类映射到实体类的多个字段或属性,则主键类中的主键字段或属性的名称必须与实体类的主键字段或属性的名称相对应,并且它们的类型必须相同。
With an IdClass
带着 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.
该IdClass
注释映射多个字段的表PK。
With EmbeddedId
和 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.
该@EmbeddedId
注解映射一个PK类表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.
- 从物理模型来看,没有区别
@EmbeddedId
以某种方式更清楚地传达该密钥是一个复合密钥,并且当组合 pk 本身是一个有意义的实体或它在您的代码中重用时,IMO 才有意义。@IdClass
可用于指定某些字段组合是唯一的,但这些组合没有特殊含义。
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
和
IdClass
select t.levelStation from Time t
和
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"
- JPA 1.0 规范
- 第 2.1.4 节“主键和实体身份”
- 第 9.1.14 节“EmbeddedId 注释”
- 第 9.1.15 节“IdClass 注释”
回答by Maurice Perry
Another option is to map is as a Map of composite elements in the ConfPath table.
另一种选择是映射为 ConfPath 表中复合元素的 Map。
This mapping would benefit from an index on (ConfPathID,levelStation) though.
但是,此映射将受益于 (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;
}
}
Mapping:
映射:
<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>
回答by Mykhaylo Adamovych
The primary key class must define equals and hashCode methods
主键类必须定义equals和hashCode方法
- When implementing equals you should use instanceofto 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. - 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.
- 在实现 equals 时,您应该使用 instanceof来允许与子类进行比较。如果 Hibernate 延迟加载一对一或多对一关系,您将拥有该类的代理而不是普通类。代理是一个子类。比较类名会失败。
从技术上讲:您应该遵循 Liskows 替换原则并忽略对称性。 - 下一个陷阱是使用类似name.equals(that.name) 的东西而不是name.equals(that.getName())。如果是代理,第一个将失败。
回答by dinesh kandpal
Let's take a simple example. Let's say two tables named test
and customer
are there described as:
我们举一个简单的例子。假设有两个表命名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));
One more table is there which keeps the track of test
s and customer
:
还有一张表记录了test
s 和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:
我们可以看到表中tests_purchased
的主键是一个复合键,所以我们将使用映射文件中的<composite-id ...>...</composite-id>
标签hbm.xml
。所以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>
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:
但这并没有结束。在 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;
}
}
Important point is that we also implement the two functions hashCode()
and equals()
as Hibernate relies on them.
重要的一点是我们也实现了这两个函数hashCode()
,equals()
并且 Hibernate 依赖于它们。
回答by Mazen Embaby
Using hbm.xml
使用 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>
Using Annotation
使用注解
Composite Key Class
复合密钥类
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 Class
实体类
@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
);
}
}
回答by Vlad Mihalcea
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
持有复合标识符:
@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
:
有了这个,我们可以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;
}
}
The Phone
entity which has a @ManyToOne
association to Employee
, needs to reference the composite identifier from the parent class via two @JoinColumn
mappings:
Phone
与 有@ManyToOne
关联的实体Employee
需要通过两个@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;
}
}
For more details, check out this article.
有关更多详细信息,请查看这篇文章。