JPA
(Java持久化API)规范有两种不同的方法来指定实体复合键:@IdClass
和@EmbeddedId
。
我在映射实体上同时使用这两个注解,但这对于不太熟悉JPA
的人来说是一件大麻烦。
我想采用只有一种指定复合键的方法。哪种方法才是最好的?为什么?
JPA
(Java持久化API)规范有两种不同的方法来指定实体复合键:@IdClass
和@EmbeddedId
。
我在映射实体上同时使用这两个注解,但这对于不太熟悉JPA
的人来说是一件大麻烦。
我想采用只有一种指定复合键的方法。哪种方法才是最好的?为什么?
我认为@EmbeddedId
可能更冗长,因为使用@IdClass
时,你不能使用任何字段访问运算符来访问整个主键对象。而使用@EmbeddedId
,你可以这样做:
I think @EmbeddedId
may be more verbose because with @IdClass
, you cannot access the entire primary key object using any field access operator. However, with @EmbeddedId
, you can access it like this:
@Embeddable class EmployeeId { name, dataOfBirth }
@Entity class Employee {
@EmbeddedId EmployeeId employeeId;
...
}
这清晰地说明了构成复合键的字段,因为它们都聚合在一个类中,并通过字段访问运算符访问。
@IdClass
和@EmbeddedId
的另一个区别在于,在编写HQL时:
使用@IdClass
编写如下:
select e.name from Employee e
而使用@EmbeddedId
则需要编写如下:
select e.employeeId.name from Employee e
同样的查询需要编写更多的文本。有些人可能会认为这与IdClass
提倡的更自然的语言不同。但大多数情况下,从查询中正确理解给定字段是复合键的一部分是非常有价值的帮助。
我发现一个情况,在这种情况下,我必须使用EmbeddedId而不是IdClass。在这种情况下,有一个连接表定义了额外的列。我尝试使用IdClass来解决这个问题,以表示实体的键,该实体明确表示连接表中的行。但我无法通过这种方式解决这个问题。幸运的是,《Java Persistence With Hibernate》有一节专门讲解这个主题。其中一种提出的解决方案与我的非常相似,但它使用了EmbeddedId。我按照书中的对象模型进行建模,现在它的行为正确。
@IdClass
会更容易且更直接。使用@EmbeddedId
时,您必须在@Embeddedable
中定义一次FK列的映射,然后在@ManyToOne
中再次定义一次(@PrimaryKeyJoinColumn
),因为您不能将一个列设置在两个变量中(可能会冲突)。 因此,您必须使用@Embeddedable
中的简单类型来设置您的FK。
而使用@IdClass
,可以更轻松地处理这种情况,如Primary Keys through OneToOne and ManyToOne Relationships所示:
JPA 2.0 中的 ManyToOne id 注解示例...
@Entity
@IdClass(PhonePK.class)
public class Phone {
@Id
private String type;
@ManyToOne
@Id
@JoinColumn(name="OWNER_ID", referencedColumnName="EMP_ID")
private Employee owner;
...
}
示例 JPA 2.0 id 类
...
public class PhonePK {
private String type;
private long owner;
public PhonePK() {}
public PhonePK(String type, long owner) {
this.type = type;
this.owner = owner;
}
public boolean equals(Object object) {
if (object instanceof PhonePK) {
PhonePK pk = (PhonePK)object;
return type.equals(pk.type) && owner == pk.owner;
} else {
return false;
}
}
public int hashCode() {
return type.hashCode() + owner;
}
}
我认为主要的优势在于,当使用@IdClass
时,我们可以使用@GeneratedValue
来生成id。但是我确定我们不能在@EmbeddedId
中使用@GeneratedValue
。
@EmbeddedId
时,组合键不应该拥有@Id
属性。使用EmbeddedId,您可以在HQL中使用IN子句,例如:FROM Entity WHERE id IN :ids
,其中id是一个EmbeddedId,而使用IdClass实现相同的结果则很痛苦,您需要做一些类似于FROM Entity WHERE idPartA = :idPartA0 AND idPartB = :idPartB0 .... OR idPartA = :idPartAN AND idPartB = :idPartBN
的操作。
@IdClass
的独特用例,尽管在大多数情况下我更喜欢使用@EmbeddedId
(从Antonio Goncalves的一次会议中了解到)。他建议,在组合键类不可访问或来自另一个模块或遗留代码的情况下,我们可以使用@IdClass
。在这些情况下,@IdClass
将为我们提供一种方法。 - Gaurav Rawat