跳转到内容

Java 持久化/多对多

来自 Wikibooks,开放世界中的开放书籍

多对多

[编辑 | 编辑源代码]

Java 中的 ManyToMany 关系是指源对象具有一个存储目标对象集合的属性,并且如果这些目标对象具有反向关系回到源对象,它也将是一个 ManyToMany 关系。Java 和 JPA 中的所有关系都是单向的,这意味着如果源对象引用目标对象,则不能保证目标对象也与源对象具有关系。这与关系数据库不同,在关系数据库中,关系通过外键定义,并进行查询,从而始终存在反向查询。

JPA 还定义了 一对多 关系,它类似于 ManyToMany 关系,除了反向关系(如果定义的话)是一个 ManyToOne 关系。JPA 中 OneToManyManyToMany 关系的主要区别在于 ManyToMany 始终使用中间关系联接表来存储关系,而 OneToMany 既可以使用联接表,也可以使用目标对象表中的外键引用源对象表的主键。

在 JPA 中,ManyToMany 关系是通过 @ManyToMany 注解或 <many-to-many> 元素定义的。

所有 ManyToMany 关系都需要一个 JoinTableJoinTable 是使用 @JoinTable 注解和 <join-table> XML 元素定义的。JoinTable 定义了到源对象主键的外键(joinColumns)和到目标对象主键的外键(inverseJoinColumns)。通常,JoinTable 的主键是两个外键的组合。

多对多关系数据库示例

[编辑 | 编辑源代码]

EMPLOYEE (表)

ID FIRSTNAME LASTNAME
1 Bob Way
2 Sarah Smith

EMP_PROJ (表)

EMP_ID PROJ_ID
1 1
1 2
2 1

PROJECT (表)

ID NAME
1 GIS
2 SIG

多对多关系注解示例

[编辑 | 编辑源代码]
@Entity
public class Employee {
 @Id
 @Column(name="ID")
 private long id;
 ...
 @ManyToMany
 @JoinTable(
   name="EMP_PROJ",
   joinColumns=@JoinColumn(name="EMP_ID", referencedColumnName="ID"),
   inverseJoinColumns=@JoinColumn(name="PROJ_ID", referencedColumnName="ID"))
 private List<Project> projects;
 .....
}

多对多关系 XML 示例

[编辑 | 编辑源代码]
<entity name="Employee" class="org.acme.Employee" access="FIELD">
	<attributes>
		<id name="id">
			<column name="EMP_ID"/>
		</id>
		<set name="projects" table="EMP_PROJ" lazy="true" cascade="none" sort="natural" optimistic-lock="false">
			<key column="EMP_ID" not-null="true" />
			<many-to-many class="com.flipswap.domain.Project" column="PROJ_ID" />
		</set>
	</attributes>
</entity>

双向多对多

[编辑 | 编辑源代码]

尽管 ManyToMany 关系在数据库中始终是双向的,但对象模型可以选择是否在两个方向上进行映射,以及在哪个方向上进行映射。如果您选择在两个方向上映射关系,则必须将一个方向定义为所有者,而另一个方向必须使用 mappedBy 属性来定义其映射。这也避免了在两个地方重复 JoinTable 信息。

如果未使用 mappedBy,则持久化提供程序将假定存在两个独立的关系,并且您最终将获得插入到联接表中的重复行。如果您具有概念上的双向关系,但在数据库中具有两个不同的联接表,则您不能使用 mappedBy,因为您需要维护两个独立的表。

与所有双向关系一样,您的对象模型和应用程序负责维护两个方向上的关系。JPA 中没有魔法,如果您在集合的一侧添加或删除,则还必须在另一侧添加或删除,请参阅 对象损坏。从技术上讲,如果您只在关系的所有者一侧添加/删除,则数据库将被正确更新,但您的对象模型将不同步,这可能会导致问题。

反向多对多关系注解示例

[编辑 | 编辑源代码]
@Entity
public class Project {
 @Id
 @Column(name="ID")
 private long id;
 ...
 @ManyToMany(mappedBy="projects")
 private List<Employee> employees;
 ...
}

另请参见

[编辑 | 编辑源代码]

常见问题

[编辑 | 编辑源代码]
刷新后对象不在集合中。
[编辑 | 编辑源代码]
如果您有双向 ManyToMany 关系,请确保您在关系的双方都添加了对象。
请参阅 对象损坏
联接表中存在其他列。
[编辑 | 编辑源代码]
请参阅 映射具有其他列的联接表
联接表中插入了重复行。
[编辑 | 编辑源代码]
如果您有双向 ManyToMany 关系,您必须在关系的一侧使用 mappedBy,否则它将被假定为两个不同的关系,并且您将获得插入到联接表中的重复行。

映射具有其他列的联接表

[编辑 | 编辑源代码]

一个常见问题是,两个类具有 ManyToMany 关系,但关系联接表具有其他数据。例如,如果 EmployeeProject 具有 ManyToMany 关系,但 PROJ_EMP 联接表还具有 IS_PROJECT_LEAD 列。在这种情况下,最佳解决方案是创建一个类来模拟联接表。因此,将创建一个 ProjectAssociation 类。它将具有 ManyToOneEmployeeProject,以及其他数据的属性。EmployeeProject 将具有 OneToManyProjectAssociation。一些 JPA 提供程序还提供对映射到具有其他数据的联接表的其他支持。

不幸的是,在 JPA 中,这种类型的模型映射变得更加复杂,因为它需要一个复合主键。关联对象的 IdEmployeeProject 的 id 组成。JPA 1.0 规范不允许在 ManyToOne 上使用 Id,因此关联类必须具有两个重复属性来存储 id,并使用 IdClass,这些重复属性必须与 ManyToOne 属性保持同步。一些 JPA 提供程序可能允许 ManyToOne 成为 Id 的一部分,因此这在某些 JPA 提供程序中可能更简单。为了简化您的操作,建议您向关联类添加一个生成的 Id 属性。这将使对象拥有一个更简单的 Id,并且不需要复制 EmployeeProject 的 id。

无论连接表中的额外数据是什么,都可以使用相同的模式。另一种用法是,如果您在两个对象之间具有 Map 关系,并且第三个无关对象或数据代表 Map 密钥。JPA 规范要求 Map 密钥是 Map 值的属性,因此可以使用“关联对象”模式来建模关系。

如果连接表中的额外数据仅在数据库中需要,而在 Java 中未使用,例如审计信息,那么也可以使用数据库触发器来自动设置数据。

示例连接表关联对象数据库

[编辑 | 编辑源代码]

EMPLOYEE (表)

ID FIRSTNAME LASTNAME
1 Bob Way
2 Sarah Smith

PROJ_EMP (表)

EMPLOYEEID PROJECTID IS_PROJECT_LEAD
1 1 true
1 2 false
2 1 false

PROJECT (表)

ID NAME
1 GIS
2 SIG

示例连接表关联对象注释

[编辑 | 编辑源代码]
@Entity
public class Employee {
 @Id
 private long id;
 ...
 @OneToMany(mappedBy="employee")
 private List<ProjectAssociation> projects;
 ...
}
@Entity
public class Project {
 @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);
  if(this.employees == null)
    this.employees = new ArrayList<>();

  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
 @PrimaryKeyJoinColumn(name="EMPLOYEEID", referencedColumnName="ID")
 /* if this JPA model doesn't create a table for the "PROJ_EMP" entity,
 * please comment out the @PrimaryKeyJoinColumn, and use the ff:
 * @JoinColumn(name = "employeeId", updatable = false, insertable = false)
 * or @JoinColumn(name = "employeeId", updatable = false, insertable = false, referencedColumnName = "id")
 */
 private Employee employee;
 @ManyToOne
 @PrimaryKeyJoinColumn(name="PROJECTID", referencedColumnName="ID")
 /* the same goes here:
 * if this JPA model doesn't create a table for the "PROJ_EMP" entity,
 * please comment out the @PrimaryKeyJoinColumn, and use the ff:
 * @JoinColumn(name = "projectId", updatable = false, insertable = false)
 * or @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;
 }

}
  • 如果给定的示例不适合您的期望,请尝试此链接中指示的解决方案

http://giannigar.wordpress.com/2009/09/04/mapping-a-many-to-many-join-table-with-extra-column-using-jpa/

华夏公益教科书