---
# System prepended metadata

title: 多對多
tags: [jointable, manytomany, jpa]

---

# 多對多

###### tags: `jpa` `manytomany` `jointable`

一張圖勝過千言萬語，先來張 entity relation diagram：

![ER-Model](https://i.imgur.com/uDwMO1e.png)

這個示例中，一個帳號可以有多種權限，每種權限也可以有多個帳號，這就是所謂的**多對多**關係；`authorities`稱為 **join table** (**中介表**或是**聯接表**)。

## 模化

兩個 entities 中各加一個`Collection`<sup>[![JavaDoc](https://i.imgur.com/7EkdyXl.png)](https://docs.oracle.com/javase/8/docs/api/java/util/Collection.html "JavaDoc")</sup>的 field 並以`@ManyToMany`<sup>[![JavaDoc](https://i.imgur.com/7EkdyXl.png)](https://javaee.github.io/javaee-spec/javadocs/javax/persistence/ManyToMany.html "JavaDoc")</sup>註釋，其中一個(此例選擇從`Account.java`下手)以`@JoinTable`<sup>[![JavaDoc](https://i.imgur.com/7EkdyXl.png)](https://javaee.github.io/javaee-spec/javadocs/javax/persistence/JoinTable.html "JavaDoc")</sup>註釋。

```java
package manytomany.simple.entity;

import java.util.Collection;
import javax.persistence.*;

/**
 * 帳號
 */
@Entity
@Table(name = "account")
public class Account implements java.io.Serializable {

	private static final long serialVersionUID = -2147483647L;

	@Id
	@Basic(optional = false)
	@Column(nullable = false)
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	private Long id;

	@Column(name = "login", nullable = false)
	private String login;

	@JoinTable(
		name = "authorities",
		joinColumns = @JoinColumn(name = "account_id"),
		inverseJoinColumns = @JoinColumn(name = "privilege_id")
	)
	@ManyToMany(cascade = CascadeType.ALL)
	private Collection<Privilege> privileges;

	public Account() {
	}

	protected Account(Long id) {
		this.id = id;
	}

	@Override
	public int hashCode() {
		int hash = 0;
		hash += (id != null ? id.hashCode() : 0);
		return hash;
	}

	@Override
	public boolean equals(Object object) {
		if (!(object instanceof Account)) {
			return false;
		}
		Account other = (Account) object;
		return !((this.id == null && other.id != null) || (this.id != null && !this.id.equals(other.id)));
	}

	@Override
	public String toString() {
		return "manytomany.simple.entity.Account[ id=" + id + " ]";
	}

	public Long getId() {
		return id;
	}

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

	public String getLogin() {
		return login;
	}

	public void setLogin(String login) {
		this.login = login;
	}

	public Collection<Privilege> getPrivileges() {
		return privileges;
	}

	public void setPrivileges(Collection<Privilege> privileges) {
		this.privileges = privileges;
	}
}
```

```java
package manytomany.simple.entity;

import com.fasterxml.jackson.annotation.JsonIgnore;
import java.util.Collection;
import javax.persistence.Basic;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.ManyToMany;
import javax.persistence.Table;
import org.hibernate.annotations.Cascade;

/**
 * 權限
 */
@Entity
@Table(name = "privilege")
public class Privilege implements java.io.Serializable {

	private static final long serialVersionUID = -2147483647L;

	@Id
	@Basic(optional = false)
	@Column(nullable = false)
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	private Long id;

	@Column(name = "display_name")
	private String displayName;

	//@Cascade(org.hibernate.annotations.CascadeType.ALL)
	@ManyToMany(mappedBy = "courses")
	private Collection<Account> accounts;

	public Privilege() {
	}

	protected Privilege(Long id) {
		this.id = id;
	}

	@Override
	public int hashCode() {
		int hash = 0;
		hash += (id != null ? id.hashCode() : 0);
		return hash;
	}

	@Override
	public boolean equals(Object object) {
		if (!(object instanceof Privilege)) {
			return false;
		}
		Privilege other = (Privilege) object;
		return !((this.id == null && other.id != null) || (this.id != null && !this.id.equals(other.id)));
	}

	@Override
	public String toString() {
		return "manytomany.simple.entity.Privilege[ id=" + id + " ]";
	}

	public Long getId() {
		return id;
	}

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

	public Collection<Account> getAccounts() {
		return students;
	}

	public void setAccounts(Collection<Account> accounts) {
		this.accounts = accounts;
	}
}

```
Specifies the mapping of associations. It is applied to the owning side of an association.
A join table is typically used in the mapping of many-to-many and unidirectional one-to-many associations. It may also be used to map bidirectional many-to-one/one-to-many associations, unidirectional many-to-one relationships, and one-to-one associations (both bidirectional and unidirectional).

When a join table is used in mapping a relationship with an embeddable class on the owning side of the relationship, the containing entity rather than the embeddable class is considered the owner of the relationship.

If the JoinTable annotation is missing, the default values of the annotation elements apply. The name of the join table is assumed to be the table names of the associated primary tables concatenated together (owning side first) using an underscore.

```
spring.jpa.properties.hibernate.enable_lazy_load_no_trans=true
```
https://www.baeldung.com/hibernate-lazy-loading-workaround

https://www.baeldung.com/jpa-cascade-types

## 帶欄位的關聯

### 模化

#### 複合鍵類

1. 為 composite key 創建一個 class。
2. 此 class 須以`@Embeddable`<sup>[![JavaDoc](https://i.imgur.com/7EkdyXl.png)](https://javaee.github.io/javaee-spec/javadocs/javax/persistence/ManyToMany.html "JavaDoc")</sup>註釋。
3. 此 class 須`implements java.io.Serializable`。
4. 此 class 須`@Override`諸如`hashCode()`、`equals(Object object)`等 methods。
5. 此 class 不能有任何 entity field。

#### 

```

```