多對多關聯需透過關聯表實現。EJB 3 使用 @ManyToMany 搭配 @JoinTable 完成映射。本文以 Customer(客戶)與 Address(地址)為例,示範雙向多對多映射、關聯表配置與測試程式碼。
id (PK) | name | ...
id (PK) | address_line | country | post_code

customer_id (FK) | address_id (FK)

複合主鍵:(customer_id, address_id)
@Entity
@Table(name = "t_customers")
public class Customer {
private int id;
private String name;
private Referee referee;
private Collection<Order> orders;
private Collection<Address> addresses;
@ManyToMany(cascade = CascadeType.PERSIST, fetch = FetchType.LAZY)
@JoinTable(
name = "t_customers_addresses",
joinColumns = @JoinColumn(name = "customer_id", referencedColumnName = "id"),
inverseJoinColumns = @JoinColumn(name = "address_id", referencedColumnName = "id")
)
public Collection<Address> getAddresses() { return addresses; }
public void setAddresses(Collection<Address> addresses) { this.addresses = addresses; }
// ... 其他 getter/setter 略
}
@Entity
@Table(name = "t_addresses")
public class Address {
private int id;
private String addressLine;
private String country;
private String postCode;
private Collection<Customer> customers;
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
public int getId() { return id; }
// ... 基本屬性 getter/setter 略
@ManyToMany(mappedBy = "addresses")
public Collection<Customer> getCustomers() { return customers; }
public void setCustomers(Collection<Customer> customers) { this.customers = customers; }
}
| 註解 | 位置 | 說明 |
|---|---|---|
@ManyToMany | 雙方 getter | 聲明多對多關聯 |
@JoinTable | 關聯維護端 | 指定關聯表名稱、雙向外鍵欄位對應 |
joinColumns | @JoinTable 內 | 本方實體對應的外鍵(此處 customer_id → Customer.id) |
inverseJoinColumns | @JoinTable 內 | 對方實體對應的外鍵(此處 address_id → Address.id) |
mappedBy | 被關聯端 | 指向關聯維護端的屬性名("addresses"),表示不維護關聯表 |
cascade=PERSIST | 維護端 | 持久化 Customer 時級聯持久化關聯的 Address |
fetch=LAZY | 維護端 | 預設延遲載入,避免載入大量地址 |
Customer customer = new Customer();
customer.setName("微軟11");
List<Address> addresses = new ArrayList<Address>();
Address address1 = new Address();
address1.setAddressLine("address1");
address1.setCountry("中國");
address1.setPostCode("12345678");
addresses.add(address1);
Address address2 = new Address();
address2.setAddressLine("address2");
address2.setCountry("美國");
address2.setPostCode("4321");
addresses.add(address2);
customer.setAddresses(addresses);
em.persist(customer); // 級聯持久化 2 個 Address 並建立關聯表記錄
INSERT INTO t_customers (name) VALUES ('微軟11'); -- 客戶
INSERT INTO t_addresses (address_line, country, post_code) VALUES ('address1', '中國', '12345678');
INSERT INTO t_addresses (address_line, country, post_code) VALUES ('address2', '美國', '4321');
INSERT INTO t_customers_addresses (customer_id, address_id) VALUES (1, 1);
INSERT INTO t_customers_addresses (customer_id, address_id) VALUES (1, 2);
新增關聯時,建議雙向同步:
customer.getAddresses().add(address);
address.getCustomers().add(customer); // 保持記憶體中物件圖一致
JSON/XML 序列化時,雙向關聯會導致循環引用。解決方案:
@JsonIgnore (Jackson) 或 @XmlTransient (JAXB) 標記一方若關聯表需額外欄位(如 created_date、is_primary),無法直接用 @ManyToMany,需拆解為兩個 @OneToMany + 中間實體類別。
大量建立關聯時,考慮:
hibernate.jdbc.batch_sizepersist 並定期 flush/clear