跳轉到內容

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/

華夏公益教科書