一個人可住多個地址,一個地址可住多人,典型多對多。中間表 people_address 存儲關聯。本文給出基於 JPA 註解的雙向映射完整配置、表結構生成、CRUD 測試代碼與 SQL 日誌分析。
@Entity @Table(name="people_inf")
public class People implements Serializable {
@Id @GeneratedValue(strategy=GenerationType.IDENTITY)
@Column(name="people_id") private Integer id;
private String name; private int age;
@ManyToMany(targetEntity=Address.class) // 無 mappedBy → 維護端
@JoinTable(name="people_address",
joinColumns=@JoinColumn(name="peopleId", referencedColumnName="people_id"),
inverseJoinColumns=@JoinColumn(name="addressId", referencedColumnName="address_id"))
private Set<Address> address = new HashSet<>();
// getter/setter
}
@Entity @Table(name="Address_inf")
public class Address {
@Id @GeneratedValue(strategy=GenerationType.IDENTITY)
@Column(name="address_id") private int addressId;
private String message;
@ManyToMany(targetEntity=People.class)
@JoinTable(name="people_address", // 必須與維護端同名
joinColumns=@JoinColumn(name="addressId", referencedColumnName="address_id"),
inverseJoinColumns=@JoinColumn(name="peopleId", referencedColumnName="people_id"))
private Set<People> people = new HashSet<>();
// getter/setter
}
| 註解/屬性 | 作用 | 注意點 |
|---|---|---|
@ManyToMany | 聲明多對多 | 雙向雙方都要寫 |
targetEntity | 指定關聯實體 | 集合泛型可推導時可省略 |
@JoinTable | 定義中間表 | 雙方必須同名 |
joinColumns | 本端外鍵 | referencedColumnName 指向主鍵列 |
inverseJoinColumns | 對端外鍵 | 同上 |
mappedBy | 放棄維護權 | 僅一方寫,另一方寫 @JoinTable |
// test01.java
Transaction tx = session.beginTransaction();
People p1 = new People(); p1.setAge(22); p1.setName("林彪");
session.save(p1);
Address a1 = new Address(); a1.setMessage("廣州");
a1.getPeople().add(p1); // 從 Address 端建立關聯
session.persist(a1);
Address a2 = new Address(); a2.setMessage("香港");
a2.getPeople().add(p1);
session.persist(a2);
People p2 = new People(); p2.setAge(17); p2.setName("小芳");
p2.getAddress().add(a2); // 從 People 端建立關聯
session.save(p2);
tx.commit();
Hibernate: insert into people_inf (age, name) values (?, ?) -- p1 Hibernate: insert into Address_inf (message) values (?) -- a1 Hibernate: insert into Address_inf (message) values (?) -- a2 Hibernate: insert into people_inf (age, name) values (?, ?) -- p2 Hibernate: insert into people_address (addressId, peopleId) values (?, ?) -- a1-p1 Hibernate: insert into people_address (addressId, peopleId) values (?, ?) -- a2-p1 Hibernate: insert into people_address (peopleId, addressId) values (?, ?) -- p2-a2
觀察:雙向關聯會產生重複中間表插入(a1-p1、p1-a1 邏輯相同但 SQL 列順序不同),Hibernate 4.x 會去重,舊版可能報主鍵衝突。生產建議單向維護或在 Service 層統一入口。
-- people_inf
people_id (PK) | name | age
-- Address_inf
address_id (PK) | message
-- people_address
peopleId (FK) | addressId (FK) -- 聯合主鍵
@JoinTable,Address 端用 @ManyToMany(mappedBy="address"),避免重複 SQLcascade={PERSIST, MERGE} 通常足夠,避免 REMOVE 誤刪關聯數據@ManyToMany 默認 LAZY,Session 關閉後訪問拋 LazyInitializationException → DTO 轉換或 Open Session In View// People 端(維護端)
@ManyToMany
@JoinTable(name="people_address",
joinColumns=@JoinColumn(name="peopleId"),
inverseJoinColumns=@JoinColumn(name="addressId"))
private Set<Address> addresses;
// Address 端(僅查詢)
@ManyToMany(mappedBy="addresses")
private Set<People> peoples;
此時只有 People 端操作會同步中間表,Address 端僅可讀取,SQL 更乾淨。