JPA
(Java Persistence API)规范有 2 种不同的方式来指定实体复合键:@IdClass
和 @EmbeddedId
。
我在我的映射实体上同时使用了这两个注释,但对于不太熟悉 JPA
的人来说,这真是一团糟。
我只想采用一种方式来指定复合键。哪一个真的是最好的?为什么?
我认为 @EmbeddedId
可能更冗长,因为使用 @IdClass
您无法使用任何字段访问运算符访问整个主键对象。使用 @EmbeddedId
您可以这样做:
@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
所提倡的更自然的语言不同。但大多数情况下,从查询中了解给定字段是组合键的一部分是非常有用的。
使用复合主键的三种策略:
将其标记为@Embeddable 并向您的实体类添加一个普通属性,并用@Id 标记。
为您的实体类添加一个普通属性,用@EmbeddedId 标记。
将属性添加到您的实体类的所有字段,用@Id 标记它们,并用@IdClass 标记您的实体类,提供您的主键类的类。
将 @Id
与标记为 @Embeddable
的类一起使用是最自然的方法。 @Embeddable
标记无论如何都可用于非主键可嵌入值。它允许您将复合主键视为单个属性,并允许在其他表中重用 @Embeddable
类。
下一个最自然的方法是使用 @EmbeddedId
标记。在这里,主键类不能用于其他表,因为它不是 @Embeddable
实体,但它确实允许我们将键视为某个类的单个属性。
最后,使用 @IdClass
和 @Id
注释允许我们使用实体本身的属性映射复合主键类,这些属性对应于主键类中的属性名称。 名称必须一致(没有覆盖它的机制),并且主键类必须履行与其他两种技术相同的义务。这种方法的唯一优点是它能够从封闭实体的接口“隐藏”主键类的使用。 @IdClass
注解采用 Class 类型的 value 参数,该参数必须是用作复合主键的类。 使用的主键类属性对应的字段都必须用@Id
注解。
参考:http://www.apress.com/us/book/9781430228509
我发现了一个实例,我必须使用 EmbeddedId 而不是 IdClass。在这种情况下,有一个定义了附加列的连接表。我尝试使用 IdClass 来表示明确表示连接表中的行的实体的键来解决这个问题。我无法让它以这种方式工作。值得庆幸的是,“Java Persistence With Hibernate”有一个专门讨论这个主题的部分。一个提议的解决方案与我的非常相似,但它使用 EmbeddedId 代替。我按照书中的对象建模了我的对象,它现在可以正常运行。
据我所知,如果您的复合 PK 包含 FK,则使用起来会更容易、更直接@IdClass
使用 @EmbeddedId
,您必须为 FK 列定义映射两次,一次在 @Embeddedable
中,一次在 @ManyToOne
中,其中 @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?我确定我们不能将 @GeneratedValue
用于 @EmbeddedId
。
使用 @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
将为我们提供一种 .@IdClass
注释的情况可能正是 JPA 规范列出了创建复合键的两种方法的原因。@IdClass
和@EmbeddidId