近來,在做的一個NewsMS項目中,需要用到多對多關聯映射,以下是項目中用到的兩個實體類:用戶類User和角色類Role,它們之間是多對多的關系。
//用戶表 @Entity @Table(name="rong_user") public class User{ //省略其它內容 private Set<Role> roles = new LinkedHashSet<Role>(); //角色集合 @ManyToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE}) @JoinTable(name = "rong_user_role", joinColumns = { @JoinColumn(name ="user_id" )}, inverseJoinColumns = { @JoinColumn(name = "role_id") }) @OrderBy("id") public Set<Role> getRoles() { return roles; } public void setRoles(Set<Role> roles) { this.roles = roles; } } //角色表 @Entity @Table(name="rong_role") public class Role{ //省略其它內容 private Set<User> user = new LinkedHashSet<User>(); //用戶集合 @ManyToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE}, mappedBy = "roles", fetch = FetchType.LAZY) public Set<User> getUser() { return user; } public void setUser(Set<User> user) { this.user = user; } }
這兩個生成數據庫中的三個表,分別是rong_user, rong_role和一個中間表rong_user_role。
Hibernate和JPA控制關聯關系的,只能是一方,不能雙方控制的,上面的程序中,我通過在Role類中設置mappedBy="roles"來設置由User來控制關系,
這樣,問題就出現了:當我在要刪除角色Role時,如果沒有用戶擁有這個角色的話,就能成功刪除;如果有用戶擁有這個角色的時候,就不能刪除,會拋以下異常:
12:53:33,125 WARN JDBCExceptionReporter:100 - SQL Error: 1451, SQLState: 23000
12:53:33,125 ERROR JDBCExceptionReporter:101 - Cannot delete or update a parent row: a foreign key constraint fails (`newsms/rong_user_role`, CONSTRAINT `FKF1698421A337A5FA` FOREIGN KEY (`role_id`) REFERENCES `rong_role` (`id`))
12:53:33,171 ERROR AbstractFlushingEventListener:324 - Could not synchronize database state with session
org.hibernate.exception.ConstraintViolationException: Could not execute JDBC batch update
/****堆棧信息略****/
Caused by: java.sql.BatchUpdateException: Cannot delete or update a parent row: a foreign key constraint fails (`newsms/rong_user_role`, CONSTRAINT `FKF1698421A337A5FA` FOREIGN KEY (`role_id`) REFERENCES `rong_role` (`id`))
/******堆棧信息略*****/
當我設置成單向關系映射時,即把Role類中,Set<User>信息去掉,這樣,也不能刪,原因也是說有外鍵約束!怎麼辦?
苦惱了好幾天,最後,只能歸於Hibernate(JPA)的多對多關聯映射設計得有點不符實際!就像上面我說的例子,有人選了某角色,就不能刪掉該角色。還有一種做法是,在Role類中:
@ManyToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE,CascadeType.REMOVE}, mappedBy = "roles", fetch = FetchType.LAZY) public Set<User> getUser() { return user; }
即加多一個“CascadeType.REMOVE”,這樣能把角色Role給刪掉了,但連擁有該角色的所有用戶User也被級聯刪掉了。這樣來看,某個用戶擁有許多角色,就因為其中有這一個角色,就被級聯刪了整個自己,那不是很冤枉。這也不符合實際!
個人認為,Hibernate(JPA)在設置多對多關聯映射時,應該有做法能使得雙方都能控制關聯關系才好,才符合實際吧!但事實上,好像還沒有發現有Hibernate(JPA)這種能力!