我应该使用哪个注释:@IdClass 或@EmbeddedId

JPA(Java持久化API)规范有两种不同的方法来指定实体组合键: @IdClass@EmbeddedId

我在我的映射实体上同时使用了这两个注释,但是对于不太熟悉 JPA的人来说,它实在是一团糟。

我只想采用一种方法来指定复合键。哪一个真的是最好的? 为什么?

112640 次浏览

我认为 @EmbeddedId可能更加冗长,因为使用 @IdClass时,您不能使用任何字段访问操作符访问整个主键对象。使用 @EmbeddedId你可以这样做:

@Embeddable class EmployeeId { name, dataOfBirth }
@Entity class Employee {
@EmbeddedId EmployeeId employeeId;
...
}

这给出了组成组合键的字段的清晰概念,因为它们都聚合在通过字段访问操作符访问的类中。

@IdClass@EmbeddedId的另一个不同之处在于编写 HQL 时:

@IdClass中,你写道:

select e.name from Employee e

and with @EmbeddedId you have to write:

select e.employeeId.name from Employee e

对于同一个查询,您必须编写更多的文本。有些人可能会争辩说,这不同于像 IdClass提倡的那种更自然的语言。但是大多数情况下,从查询中理解给定字段是组合键的一部分是非常有帮助的。

我发现了一个不得不使用 EmbeddedId 而不是 IdClass 的实例。在此场景中,有一个联接表,其中定义了其他列。我尝试使用 IdClass 来表示显式表示联接表中的行的实体的键,从而解决这个问题。我不能让它这样工作。值得庆幸的是,“使用 Hibernate 的 Java 持久性”有一节专门讨论这个主题。一个提议的解决方案与我的非常相似,但它使用了 EmbeddedId。我根据书中的对象建模了我的对象,它现在的行为正确。

我认为最主要的优点是我们可以在使用 @IdClass的时候使用 @GeneratedValue作为 id?我肯定我们不能用 @GeneratedValue@EmbeddedId

据我所知,如果您的复合 PK 包含 FK,那么使用 @IdClass会更简单、更直接

对于 @EmbeddedId,你必须为 FK 列定义两次映射,一次在 @Embeddedable,一次在 @ManyToOne,其中 @ManyToOne必须是只读的(@PrimaryKeyJoinColumn) ,因为你不能在两个变量中设置一个列(可能发生冲突)。
所以你必须在 @Embeddedable中使用一个简单的类型来设置你的 FK。

在使用 @IdClass的其他站点上,这种情况可以更容易地处理,如 通过一对一和多对一关系的主键所示:

示例 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;
}
}

使用 @EmbeddedId时,复合密钥不能具有 @Id属性。

使用复合主键有三种策略:

  • 将其标记为 @Embeddable,并将其标记为 @Id的普通属性添加到实体类中。
  • 为实体类添加一个标记为 @EmbeddedId的普通属性。
  • 为实体类的所有字段添加属性,用 @Id标记它们,并用 @IdClass标记实体类,提供主键类的类。

使用标记为 @Embeddable的类的 @Id是最自然的方法。无论如何,@Embeddable标记可用于非主键可嵌入值。它允许您将复合主键视为单个属性,并允许在其他表中重用 @Embeddable类。

下一个最自然的方法是使用 @EmbeddedId标记。在这里,主键类不能在其他表中使用,因为它不是 @Embeddable实体,但是它允许我们将该键视为 某类的单一属性。

最后,使用 @IdClass@Id注释允许我们使用与主键类中的属性名相对应的实体本身的属性来映射复合主键类。名字必须一致(没有覆盖它的机制) ,主键类必须承担与其他两种技术相同的义务。这种方法的唯一优点是它能够在封闭实体的接口中“隐藏”主键类的使用。@IdClass注释接受 Class 类型的值参数,该值参数必须是用作复合主键的类。与要使用的主键类的属性对应的字段都必须使用 @Id进行注释。

参考资料: http://www.apress.com/us/book/9781430228509

使用 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的事情