我应该使用哪个注解:@IdClass 还是 @EmbeddedId?

162

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

我在映射实体上同时使用这两个注解,但这对于不太熟悉JPA的人来说是一件大麻烦。

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

7个回答

117

我认为@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提倡的更自然的语言不同。但大多数情况下,从查询中正确理解给定字段是复合键的一部分是非常有价值的帮助。


14
虽然我同意上面给出的解释,但我还想补充一个关于@IdClass的独特用例,尽管在大多数情况下我更喜欢使用@EmbeddedId(从Antonio Goncalves的一次会议中了解到)。他建议,在组合键类不可访问或来自另一个模块或遗留代码的情况下,我们可以使用@IdClass。在这些情况下,@IdClass将为我们提供一种方法。 - Gaurav Rawat
1
我认为@Gaurav提供的使用@IdClass注释的情况可能正是JPA规范列出创建复合键的两种方法的原因之一,即@IdClass和@EmbeddidId。 - kapad

28
有三种方法可以使用复合主键:
1. 将它标记为 @Embeddable 并在实体类中添加一个普通属性,用 @Id 标记。 2. 在实体类中添加一个普通属性,用 @EmbeddedId 标记。 3. 为实体类的所有字段添加属性,用 @Id 标记,并将实体类标记为 @IdClass 并提供您的主键类的类。
在被标记为 @Embeddable 的类上使用 @Id 是最自然的方法。@Embeddable 标记也可以用于非主键嵌入式值。它允许您将复合主键视为单个属性,并允许在其他表中重用 @Embeddable 类。
其次,最自然的方法是使用 @EmbeddedId 标记。在此情况下,由于它不是 @Embeddable 实体,因此无法在其他表中使用主键类,但它允许我们将主键视为某个类的单个属性。
最后,使用 @IdClass 和 @Id 注释可让我们使用实体本身属性来映射复合主键类,这些属性对应于主键类中的属性名称。名称必须相应(没有覆盖此机制的机制),并且主键类必须遵守与其他两种技术相同的义务。此方法的唯一优点是它能够“隐藏”封闭实体接口中使用主键类的方式。@IdClass 注释采用 Class 类型的值参数,必须是要用作复合主键的类。对应于主键类属性的字段必须全部用 @Id 注释标记。

参考文献: http://www.apress.com/us/book/9781430228509


19

我发现一个情况,在这种情况下,我必须使用EmbeddedId而不是IdClass。在这种情况下,有一个连接表定义了额外的列。我尝试使用IdClass来解决这个问题,以表示实体的键,该实体明确表示连接表中的行。但我无法通过这种方式解决这个问题。幸运的是,《Java Persistence With Hibernate》有一节专门讲解这个主题。其中一种提出的解决方案与我的非常相似,但它使用了EmbeddedId。我按照书中的对象模型进行建模,现在它的行为正确。


16
据我所知,如果您的复合主键包含外键,使用@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;
    }
}

1
只是不要忘记在您的PK类中添加getter方法。 - Sonata
@Sonata,为什么我们需要getter方法?我尝试过没有使用getter/setter方法,它也可以正常工作。 - xagaffar
感谢提供id类的示例!虽然我最终还需要实现Serializable接口。另外,最好添加getter和setter方法,特别是如果你的IDE可以自动生成它们。 - Starwarswii

8

我认为主要的优势在于,当使用@IdClass时,我们可以使用@GeneratedValue来生成id。但是我确定我们不能在@EmbeddedId中使用@GeneratedValue


1
在Embeddedid中无法使用@GeneratedValue吗? - Kayser
1
我已经成功地在EmbeddedId中使用它,但显然它的支持因数据库而异。这也适用于使用IdClass。规范说明:“GeneratedValue注释只能用于简单(即非复合)主键。” - BPS

6
当使用@EmbeddedId时,组合键不应该拥有@Id属性。

4

使用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的操作。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接