java.lang.IllegalArgumentException: 期望 IdClass 映射

26

我已经为我的实体Employee配置了复合主键,具体如下:

Employee.java:

@Entity
@Table(name="employee")
@Proxy(lazy=false)
@IdClass(EmployeeId.class)
public class Employee implements Serializable {
    private static final long serialVersionUID = 1L;

    private EmployeeId employeeId;
    private Person person;
    private Branch branch;
    private boolean isActive;

    public Employee() {

    }    

    @EmbeddedId
    @AttributeOverrides({
        @AttributeOverride(name="person", column = @Column(name="person_id")),
        @AttributeOverride(name="branch", column = @Column(name="branch_id"))})

    public EmployeeId getEmployeeId() {
        return employeeId;
    }

    public void setEmployeeId(EmployeeId employeeId) {
        this.employeeId = employeeId;
    }

    @ManyToOne(fetch=FetchType.LAZY)
    @JoinColumn(name="person_id")
    public Person getPerson() {
        return person;
    }

    public void setPerson(Person person) {
        this.person = person;
    }


    @ManyToOne(fetch=FetchType.LAZY)
    @JoinColumn(name="branch_id")
    public Branch getBranch() {
        return branch;
    }

    public void setBranch(Branch branch) {
        this.branch = branch;
    }

    @Column(name="is_active")
    public boolean getIsActive() {
        return isActive;
    }

    public void setIsActive(boolean isActive) {
        this.isActive = isActive;
    }

}

EmployeeId.java:

@Embeddable
public class EmployeeId implements Serializable {
    private static final long serialVersionUID = 1L;

    private Person person;
    private Branch branch;

    public EmployeeId() {

    }

    public EmployeeId(Person argPerson, Branch argbranch) {
        this.person = argPerson;
        this.branch = argbranch;
    }


    @ManyToOne(fetch=FetchType.LAZY)
    @JoinColumn(name="person_id", insertable=false, updatable=false)
    public Person getPerson() {
        return person;
    }
    public void setPerson(Person person) {
        this.person = person;
    }

    @ManyToOne(fetch=FetchType.LAZY)
    @JoinColumn(name="branch_id", insertable=false, updatable=false)
    public Branch getBranch() {
        return branch;
    }
    public void setBranch(Branch branch) {
        this.branch = branch;
    }
}

我使用org.springframework.orm.hibernate5.LocalSessionFactoryBean类创建了一个SessionFactory bean,并将所有的hbm.xml文件映射为一个MappingLocations

我的代码抛出以下错误:

Caused by: java.lang.IllegalArgumentException: expecting IdClass mapping
at org.hibernate.metamodel.internal.AttributeFactory$3.resolveMember(AttributeFactory.java:971)
at org.hibernate.metamodel.internal.AttributeFactory$5.resolveMember(AttributeFactory.java:1029)
at org.hibernate.metamodel.internal.AttributeFactory.determineAttributeMetadata(AttributeFactory.java:451)
at org.hibernate.metamodel.internal.AttributeFactory.buildIdAttribute(AttributeFactory.java:128)
at org.hibernate.metamodel.internal.MetadataContext.buildIdClassAttributes(MetadataContext.java:337)
at org.hibernate.metamodel.internal.MetadataContext.applyIdMetadata(MetadataContext.java:269)
at org.hibernate.metamodel.internal.MetadataContext.wrapUp(MetadataContext.java:190)
at org.hibernate.metamodel.internal.MetamodelImpl.initialize(MetamodelImpl.java:219)
at org.hibernate.internal.SessionFactoryImpl.<init>(SessionFactoryImpl.java:296)
at org.hibernate.boot.internal.SessionFactoryBuilderImpl.build(SessionFactoryBuilderImpl.java:476)
at org.hibernate.cfg.Configuration.buildSessionFactory(Configuration.java:707)
at org.hibernate.cfg.Configuration.buildSessionFactory(Configuration.java:723)
at org.springframework.orm.hibernate5.LocalSessionFactoryBean.buildSessionFactory(LocalSessionFactoryBean.java:504)
at org.springframework.orm.hibernate5.LocalSessionFactoryBean.afterPropertiesSet(LocalSessionFactoryBean.java:488)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFac

我该如何避免这个错误?我正在使用spring-orm-4.3.1-RELEASEhibernate-core-5.2.0.Final

更新

我创建了一个示例项目并在运行时得到以下错误...

Caused by: org.hibernate.AnnotationException: Property of @IdClass not found in entity sample.domain.Employee: employee

参考代码:https://www.dropbox.com/s/axr8l01iqh0qr29/idclass-using-hibernate5.tar.gz?dl=0

我做错了什么?请在这里提供您的意见。


如果您删除@IdClass会发生什么?它应该仍然可以正常工作。 - valepu
移除@IdClass注释后,我仍然收到与上述相同的错误。 sessionFactory bean创建中是否有任何映射缺失? - Achaius
我尝试了以下5个答案。但是我仍然得到相同的错误。我正在使用spring-4.3.2.RELEASE和hibernate-5.2.0.Final。 - Achaius
@Achaius - 在你的测试中,你试图让Employee没有Company或Team。但是Team和Company是Employee的ID - 它们不能为null。除了公司和团队之外,你的Employee还有一个id字段 - 你希望使用哪个作为ID - company+team还是id字段?你需要更多地告诉我们关于你的设计/要求,然后我们可能能够帮助你。目前这个模型在很多地方都是错误的。尽管我已经成功解决了你提到的错误。 - Adam Michalik
@Achaius,您能否告诉我您是如何解决它的? - clorzs
7个回答

7
你的情况对应于JPA 2.1规范的第2.4.1节派生标识符对应的主键Employee的身份是从PersonBranch的身份派生出来的。由于你没有展示它们中的任何一个的代码,所以我会假设它们都有简单的主键。在这种关系中,PersonBranch是“父实体”,而Employee是“从属”实体。 Employee的ID可以使用IdClassEmbeddedId进行映射,但不能同时使用两者。
请参阅第2.4.1.1节派生标识符的规范
如果你想使用IdClass,那么:
"id"类的属性名称和依赖实体类的"id"属性必须对应如下:
  • 实体类中的"id"属性和"id"类中的对应属性必须具有相同的名称。

...

  • 如果实体中的"id"属性是到父实体的多对一或一对一关系,则"id"类中的对应属性必须是(...)父实体的"id"属性类型。

因此,您的类将如下所示(省略getter、setter、冗余注释等)

@Entity
@IdClass(EmployeeId.class)
public class Employee {
   @Id
   @ManyToOne
   private Person person;
   @Id
   @ManyToOne
   private Branch branch;
}

public class EmployeeId {
    private Long person; // Corresponds to the type of Person ID, name matches the name of Employee.person
    private Long branch; // Corresponds to the type of Branch ID, name matches the name of Employee.branch
}

如果您使用EmbeddedId,则:

如果依赖实体使用嵌入式id表示其主键,则嵌入式id中对应于关系属性的属性必须与父实体的主键类型相同,并且必须由应用于关系属性的MapsId注释指定。 MapsId注释的value元素必须用于指定关系属性对应的嵌入式id中的属性名称。

代码如下:

@Entity
public class Employee {
   @EmbeddedId
   private EmployeeId id;
   @ManyToOne
   @MapsId("personId") // Corresponds to the name of EmployeeId.personId
   private Person person;
   @ManyToOne
   @MapsId("branchId") // Corresponds to the name of EmployeeId.branchId
   private Branch branch;
}

@Embeddable
public class EmployeeId {
    private Long personId; // Corresponds to the type of Person ID
    private Long branchId; // Corresponds to the type of Branch ID
}

1
谢谢@Adam Michalik,我尝试了你的答案,但仍然遇到相同的错误。还有其他建议吗? - Achaius

3

使用复合键映射可以使用IdClass或Embeddable。如果要使用IdClass,则必须在Employee中对字段进行注释@Id。

@IdClass(EmployeeId.class)
    class Person{
    @Id
     private Person person;
    @Id   
     private Branch branch;
    }

如果您想将嵌入式对象用作复合键,请从Person中删除@IdClass(EmployeeId.class)注释。您的Person类中也不需要person和branch字段,因为这些在您的嵌入式类中已经定义了。

我尝试了你的建议,但仍然遇到了同样的问题。 - Achaius

1

转换为:

@Entity
@Table(name = "employee")
@Proxy(lazy = false)
@IdClass(EmployeeId.class)
public class Employee implements Serializable {
private static final long serialVersionUID = 1L;

private EmployeeId id;
private Person person;
private Branch branch;
private boolean isActive;

public Employee() {

}

@EmbeddedId
@AttributeOverrides({@AttributeOverride(name = "person", column = @Column(name = "person_id") ),
    @AttributeOverride(name = "branch", column = @Column(name = "branch_id") )})

public EmployeeId getId() {
return id;
}

public void setId(EmployeeId id) {
this.id = id;
}

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "person_id")
public Person getPerson() {
return person;
}

public void setPerson(Person person) {
this.person = person;
}

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "branch_id")
public Branch getBranch() {
return branch;
}

public void setBranch(Branch branch) {
this.branch = branch;
}

@Column(name = "is_active")
public boolean getIsActive() {
return isActive;
}

public void setIsActive(boolean isActive) {
this.isActive = isActive;
}

}

我尝试了这个答案。但观察到与上面提到的相同的错误。 - Achaius

1
IdClass不应该被定义为可嵌入的 -
@Entity
@Table(name="employee")
@IdClass(EmployeeId.class)
public class Employee implements Serializable {
   private static final long serialVersionUID = 1L;

   @Id   
   @ManyToOne
   private Person person;
   @Id
   @ManyToOne
   private Branch branch;

   private boolean isActive;

   public Employee() { }
   //....
}

And -
public class EmployeeId implements Serializable {
    private static final long serialVersionUID = 1L;

    private Person person;
    private Branch branch;

    public EmployeeId() {}

    public EmployeeId(Person argPerson, Branch argbranch) {
        this.person = argPerson;
        this.branch = argbranch;
    }
}

我已经阅读了您的评论 - 我能否建议您将Employee映射到person_id和branch_id,而不是JPA对象Person和Branch?这将让我们测试您的hbm配置是否正确。我还建议您发布您的hbm配置,因为我认为此问题缺少信息。
因此,表将类似于 -
@Entity
@Table(name="employee")
@IdClass(EmployeeId.class)
public class Employee implements Serializable {
   private static final long serialVersionUID = 1L;

   @Id
   private Long personId;
   @Id
   private Long branchId;

   private boolean isActive;

   public Employee() { }
   //....
}

"And -"
"And -"
public class EmployeeId implements Serializable {
    private static final long serialVersionUID = 1L;

    private Long personId;
    private Long branchId;

    public EmployeeId() {}

    public EmployeeId(Person argPerson, Branch argbranch) {
        this.person = argPerson;
        this.branch = argbranch;
    }
}

我尝试了您的回答,但仍然遇到了与上述相同的错误。 - Achaius
你能否尝试上述方法来缩小问题范围,看看是OneToMany映射、IdClass还是配置的问题。 - farrellmr
感谢您的回复@farrellmr。我们需要在人员和分支对象中进行ManyToOne映射,而不是长整型/整型字段。请告知是否有任何可能性。我在我的应用程序中也遇到了同样的问题... - SST
说实话,我会切换到EmbeddedId - 原始类的问题在于它将IdClass与EmbeddedId混合在一起,这就造成了问题。根据我的经验,使用EmbeddedId更容易。 - farrellmr

0
提及使用 @IdClass 注解的类,该类持有 ID。 查看 this 帖子中的答案。

0

这个链接可能会对你有所帮助 JPA - EmbeddedId with @ManytoOne

在嵌入式id类中定义的关系映射是不被支持的。因此,你需要像这样更改嵌入式id类

@Embeddable
public class EmployeeId implements Serializable {
    private static final long serialVersionUID = 1L;

    private Long personId;
    private Long branchId;

    public EmployeeId() {

    }

    public EmployeeId(Long argPerson, Long argbranch) {
        this.personId = argPerson;
        this.branchId = argbranch;
    }


     @Column(name = "person_id")
    public Long getPersonId() {
        return personId;
    }
    public void setPersonId(Long personId) {
        this.personId = personId;
    }

    @Column(name = "branch_id")
    public Long getBranchId() {
        return branchId;
    }
    public void setBranchId(Long branchId) {
        this.branchId = branchId;
    }
}

0

JPA复合主键

指定映射到实体的多个字段或属性的复合主键类。

主键类中的字段或属性名称和实体的主键字段或属性必须对应,并且它们的类型必须相同。

答案在这里。阅读描述 输入链接描述

(示例代码)

@Entity
@Table(name = "EMP_PROJECT")
@IdClass(ProjectAssignmentId.class)
public class ProjectAssignment {
   @Id
   @Column(name = "EMP_ID", insertable = false, updatable = false)
   private int empId;

   @Id
   @Column(name = "PROJECT_ID", insertable = false, updatable = false)
   private int projectId;

   @ManyToOne
   @JoinColumn(name = "EMP_ID")
   Professor employee;

   @ManyToOne
   @JoinColumn(name = "PROJECT_ID")
   Project project;
   ....
}

public class ProjectAssignmentId implements Serializable {
   private int empId;
   private int projectId;
  ...
}

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