JPA @ManyToMany» 看似優雅,實務中卻藏著嚴重設計缺陷:關聯控制權只能單向、級聯刪除要么失敗要么過度。本文剖析根因並給出替代方案。
@Entity @Table(name="rong_user")
public class User {
@ManyToMany(cascade={PERSIST, MERGE})
@JoinTable(name="rong_user_role",
joinColumns=@JoinColumn(name="user_id"),
inverseJoinColumns=@JoinColumn(name="role_id"))
@OrderBy("id")
private Set<Role> roles = new LinkedHashSet<>();
// getter/setter
}
@Entity @Table(name="rong_role")
public class Role {
@ManyToMany(cascade={PERSIST, MERGE}, mappedBy="roles", fetch=FetchType.LAZY)
private Set<User> users = new LinkedHashSet<>();
// getter/setter
}
生成三表:rong_user、rong_role、中間表 rong_user_role (user_id, role_id),雙向外鍵約束。
若有 User 持有該 Role,刪除 Role 拋出:
ConstraintViolationException: Cannot delete or update a parent row:
foreign key constraint fails (`rong_user_role`, FK_ROLE_ID REFERENCES `rong_role`)
原因:mappedBy="roles" 宣告 User 為關聯擁有者,Hibernate 只在 User 端維護中間表。刪除 Role 時,Hibernate 不會自動清理中間表對應行,資料庫外鍵直接阻擋。
@ManyToMany(cascade={PERSIST, MERGE, REMOVE}, mappedBy="roles")
private Set<User> users;
刪除 Role → 級聯刪除所有擁有該 Role 的 User → 連帶刪除這些 User 的所有其他 Role 關聯 → 雪球效應,業務災難。
移除 Role 端 users 集合變單向,仍受中間表外鍵約束,刪除 Role 依然失敗。
@ManyToMany 只允許單邊維護關聯 (mappedBy 指定擁有者)CASCADE.REMOVE 則從擁有者端級聯刪除關聯實體 → 語義錯誤@Entity @Table(name="rong_user_role")
public class UserRole {
@EmbeddedId
private UserRoleId id; // userId + roleId
@ManyToOne @MapsId("userId") @JoinColumn(name="user_id")
private User user;
@ManyToOne @MapsId("roleId") @JoinColumn(name="role_id")
private Role role;
private Date assignedAt; // 可擴充屬性
// getter/setter
}
@Embeddable
public class UserRoleId implements Serializable {
private Long userId;
private Long roleId;
// equals/hashCode
}
User / Role 端改為 @OneToMany(mappedBy="user/role")。
UserRole 關聯行,不觸及 User@BatchSize、@Fetch 精準調優@ManyToMany 適合「純關聯、無額外屬性、極少刪除主實體」的簡單場景。一旦涉及關聯屬性、刪除邏輯、雙向獨立維護,**請毫不猶豫實體化中間表**。這不是妥協,而是領域建模的正確姿勢。