【问题标题】:JPA 2.0 many-to-many with extra column带有额外列的 JPA 2.0 多对多
【发布时间】:2014-07-13 07:19:29
【问题描述】:

我正在尝试在 JPA 2.0 (JBoss 7.1.1) 中建立一个多对多关系,并在该关系中添加一个额外的列(下面用粗体表示),例如:

Employer           EmployerDeliveryAgent             DeliveryAgent
(id,...)   (employer_id, deliveryAgent_id, **ref**)  (id,...)

我不想有重复的属性,所以我想应用http://giannigar.wordpress.com/2009/09/04/mapping-a-many-to-many-join-table-with-extra-column-using-jpa/ 中提出的第二种解决方案。但我无法让它工作,我收到几个错误,例如:

  1. 嵌入式 ID 类不应包含关系映射(实际上规范是这样说的);
  2. 在属性“employerDeliveryAgent”中,“映射者”值“pk.deliveryAgent”无法解析为目标实体上的属性;
  3. 在属性“employerDeliveryAgent”中,“映射者”值“pk.employer”无法解析为目标实体上的属性;
  4. 无法解析持久类型的覆盖属性“pk.deliveryAgent”;
  5. 无法解析持久类型的覆盖属性“pk.employer”;

该链接上的许多人都说它工作正常,所以我想我的环境有些不同,可能是 JPA 或 Hibernate 版本。所以我的问题是:如何使用 JPA 2.0(Jboss 7.1.1 / 使用 Hibernate 作为 JPA 实现)实现这种场景?为了补充这个问题:我应该避免使用复合键,而是使用普通生成的 id 和唯一约束吗?

提前致谢。

Obs.:我没有在这里复制我的源代码,因为它本质上是上面链接中的源代码的副本,只是具有不同的类和属性名称,所以我想没有必要。

【问题讨论】:

标签: hibernate jpa jakarta-ee orm jpa-2.0


【解决方案1】:

Eric LucioRenan 的两个答案都有帮助,但他们在关联表中使用 id 是多余的。您在类中同时拥有关联的实体它们的 id。这不是必需的。您可以简单地将关联类中的关联实体与关联实体字段上的@Id 进行映射。

@Entity
public class Employer {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private int id;

    @OneToMany(mappedBy = "employer")
    private List<EmployerDeliveryAgent> deliveryAgentAssoc;

    // other properties and getters and setters
}

@Entity
public class DeliveryAgent {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private int id;

    @OneToMany(mappedBy = "deliveryAgent")
    private List<EmployerDeliveryAgent> employerAssoc;

    // other properties and getters and setters
}

关联类

@Entity
@Table(name = "employer_delivery_agent")
@IdClass(EmployerDeliveryAgentId.class)
public class EmployerDeliveryAgent {
    
    @Id
    @ManyToOne
    @JoinColumn(name = "employer_id", referencedColumnName = "id")
    private Employer employer;
    
    @Id
    @ManyToOne
    @JoinColumn(name = "delivery_agent_id", referencedColumnName = "id")
    private DeliveryAgent deliveryAgent;
    
    @Column(name = "is_project_lead")
    private boolean isProjectLead;
}

还需要关联PK类。请注意,字段名称应与关联类中的字段名称完全对应,但类型应为关联类型中 id 的类型。

public class EmployerDeliveryAgentId implements Serializable {
    
    private int employer;
    private int deliveryAgent;

    // getters/setters and most importantly equals() and hashCode()
}

【讨论】:

  • 这是你能得到的最干净的解决方案,干得好。
  • 任何人都可以提供插入或删除示例。
  • 非常酷,谢谢谢谢。问题... ...如果我还将生成的@id 列放入关联类(在您的示例中为 EmployerDeliveryAgent),我会得到一个 NPE。但是我想要一个 RDBMS 长 PK ......你说什么?有办法吗?拧我的代理键?
  • 如何通过额外的字段id而不是boolean isProjectLead绑定实体?
  • 为什么需要id类?
【解决方案2】:

首先你需要生成一个EmployerDeliveryAgentPK类,因为它有多个PK:

@Embeddable
public class EmployerDeliveryAgentPK implements Serializable {

    @Column(name = "EMPLOYER_ID")
    private Long employer_id;

    @Column(name = "DELIVERY_AGENT_ID")
    private Long deliveryAgent_id;
}

接下来,您需要创建一个EmployerDeliveryAgent 类。这个类代表EmployerDeliveryAgent之间的多对多关系:

@Entity
@Table(name = "EmployerDeliveryAgent")
public class EmployerDeliveryAgent implements Serializable {

    @EmbeddedId
    private EmployerDeliveryAgentPK id;

    @ManyToOne
    @MapsId("employer_id") //This is the name of attr in EmployerDeliveryAgentPK class
    @JoinColumn(name = "EMPLOYER_ID")
    private Employer employer;

    @ManyToOne
    @MapsId("deliveryAgent_id")
    @JoinColumn(name = "DELIVERY_AGENT_ID")
    private DeliveryAgent deliveryAgent;    
}

之后,在Employer类中你需要添加:

    @OneToMany(mappedBy = "deliveryAgent")
    private Set<EmployerDeliveryAgent> employerDeliveryAgent = new HashSet<EmployerDeliveryAgent>();

并且在DeliveryAgent类中你需要添加:

    @OneToMany(mappedBy = "employer")
    private Set<EmployerDeliveryAgent> employer = new HashSet<EmployerDeliveryAgent>();

这就是全部!祝你好运!!

【讨论】:

  • 非常感谢,我浪费了两天时间试图弄清楚这个问题。
  • 我认为在DeliveryAgent 类中我们应该映射EmployerDeliveryAgent 类而不是Employer 类。因此,DeliveryAgent 类中的代码应类似于 private Set&lt;EmployerDeliveryAgent&gt; employerDeliveryAgent = new HashSet&lt;EmployerDeliveryAgent&gt;();,与 Employer 类中的代码相同。
  • 是否需要覆盖equals()hashcode()? (假设我不会intend to put instances of persistent classes in a Set AND intend to use reattachment of detached instances
  • 不需要重写equals()和hashCode()。还要确保实例化 @EmbeddedId 属性,否则您将获得 NPE:private EmployerDeliveryAgentPK id = new EmployerDeliveryAgentPK();
【解决方案3】:

好的,我根据提供的解决方案让它工作了

http://en.wikibooks.org/wiki/Java_Persistence/ManyToMany#Mapping_a_Join_Table_with_Additional_Columns.

此解决方案不会在数据库上生成重复的属性,但它会在我的 JPA 实体中生成重复的属性(这是非常可接受的,因为您可以将额外的工作传递给构造函数或方法 - 它最终是透明的)。数据库中生成的主键和外键100%正确。

如链接所述,我不能使用 @PrimaryKeyJoinColumn 而是使用 @JoinColumn(name = "projectId", updatable = false, insertable = false, referencedColumnName = "id")。另一件值得一提的事情:我必须使用 EntityManager.persist(association),这在链接的示例中是缺失的。

所以我的最终解决方案是:

@Entity
public class Employee {
  @Id
  private long id;
  ...
  @OneToMany(mappedBy="employee")
  private List<ProjectAssociation> projects;
  ...
}
@Entity
public class Project {

  @PersistenceContext
  EntityManager em;

  @Id
  private long id;
  ...
  @OneToMany(mappedBy="project")
  private List<ProjectAssociation> employees;
  ...
  // Add an employee to the project.
  // Create an association object for the relationship and set its data.
  public void addEmployee(Employee employee, boolean teamLead) {
    ProjectAssociation association = new ProjectAssociation();
    association.setEmployee(employee);
    association.setProject(this);
    association.setEmployeeId(employee.getId());
    association.setProjectId(this.getId());
    association.setIsTeamLead(teamLead);
    em.persist(association);

    this.employees.add(association);
    // Also add the association object to the employee.
    employee.getProjects().add(association);
  }
}
@Entity
@Table(name="PROJ_EMP")
@IdClass(ProjectAssociationId.class)
public class ProjectAssociation {
  @Id
  private long employeeId;
  @Id
  private long projectId;
  @Column(name="IS_PROJECT_LEAD")
  private boolean isProjectLead;
  @ManyToOne
  @JoinColumn(name = "employeeId", updatable = false, insertable = false,
          referencedColumnName = "id")

  private Employee employee;
  @ManyToOne
  @JoinColumn(name = "projectId", updatable = false, insertable = false,
          referencedColumnName = "id")

  private Project project;
  ...
}
public class ProjectAssociationId implements Serializable {

  private long employeeId;

  private long projectId;
  ...

  public int hashCode() {
    return (int)(employeeId + projectId);
  }

  public boolean equals(Object object) {
    if (object instanceof ProjectAssociationId) {
      ProjectAssociationId otherId = (ProjectAssociationId) object;
      return (otherId.employeeId == this.employeeId) 
              && (otherId.projectId == this.projectId);
    }
    return false;
  }

}

【讨论】:

  • 您好,关于您的实用程序,addEmployee(以及 removeEmployee,为简洁起见可能省略)。创建另一个实体或服务层是一个实体的责任吗?如果连接表有多个额外列怎么办?
  • 阿拉什,多出一列也是一样的,加个isProjectLead之类的字段就行了。一个实体可以创建另一个实体,这取决于您的架构这是否是一个好主意,但这有点超出了这个问题的范围,如果是这样,我建议您开始一个新问题。
  • 谢谢 Renan,很抱歉我的问题,不清楚;我不擅长英语。我想说我认为最好在ProjectAssociationService 中创建ProjectAssociation 实例并将其传递给实用程序方法。你认为呢?我不知道这是否适合新问题。
  • @Arash 这样的实用方法旨在维护关联的 JPA/Hibernate 实体之间的数据一致性,因此将它们保存在实体类中似乎是一种完美的做法。
  • 是的,你的权利@Shahin。这些方法应该在实体中定义但我说的是中间实体(与连接表相关联),它应该在相应的服务对象中创建,然后传递给实用程序方法,因为中间实体可能有 5 个或更多实例变量当您想要传递超过 4 或 5 个参数时,最好将它们包装在一个对象中并传递该对象。那么最好在服务对象中创建中间实体。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-02-06
  • 2018-02-19
  • 1970-01-01
相关资源
最近更新 更多