例:一個班級能夠有多個學生,而一個學生只能屬於一個班級。java
package com.zze.bean; import java.util.HashSet; import java.util.Set; public class Class { private Integer id; private String name; private Set<Student> students = new HashSet<>(); public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Set<Student> getStudents() { return students; } public void setStudents(Set<Student> students) { this.students = students; } }
package com.zze.bean; import java.util.Date; public class Student { private Integer id; private String name; private Integer age; private Date birthday; private String address; private Class clazz; public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } public Date getBirthday() { return birthday; } public void setBirthday(Date birthday) { this.birthday = birthday; } public String getAddress() { return address; } public void setAddress(String address) { this.address = address; } public Class getClazz() { return clazz; } public void setClazz(Class clazz) { this.clazz = clazz; } }
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd"> <hibernate-mapping> <class name="com.zze.bean.Class" table="class"> <id name="id"> <generator class="native"/> </id> <property name="name" length="32"/> <!-- set : name : ‘多’的一方在當前類中的屬性名 --> <set name="students"> <!-- key : column : 外鍵表中的關聯當前類對應表的外鍵名稱 --> <key column="cid"/> <!-- one-to-many : 標識一對多關係 class : ‘多’的一方全限定類名 --> <one-to-many class="com.zze.bean.Student"/> </set> </class> </hibernate-mapping>
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd"> <hibernate-mapping> <class name="com.zze.bean.Student" table="student"> <id name="id"> <generator class="native"/> </id> <property name="name" length="32"/> <property name="age"/> <property name="birthday"/> <property name="address"/> <!-- many-to-one : 標識多對一關係 name : ‘一’的一方在當前類中屬性名 column : 外鍵名稱 class : ‘一’的一方的全限定類名 --> <many-to-one name="clazz" column="cid" class="com.zze.bean.Class"/> </class> </hibernate-mapping>
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE hibernate-configuration PUBLIC "-//Hibernate/Hibernate Configuration DTD 3.0//EN" "http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd"> <hibernate-configuration> <session-factory> <property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property> <property name="hibernate.connection.url">jdbc:mysql://192.168.208.153:3306/test</property> <property name="hibernate.connection.username">root</property> <property name="hibernate.connection.password">root</property> <property name="hibernate.dialect">org.hibernate.dialect.MySQLDialect</property> <property name="hibernate.show_sql">true</property> <property name="hibernate.format_sql">true</property> <property name="current_session_context_class">thread</property> <property name="hibernate.hbm2ddl.auto">create</property> <mapping resource="com/zze/bean/Class.hbm.xml"></mapping> <mapping resource="com/zze/bean/Student.hbm.xml"></mapping> </session-factory> </hibernate-configuration>
@Test public void test1() { Session session = HibernateUtil.getCurrentSession(); Transaction transaction = session.beginTransaction(); // 建立一個班級 Class clazz = new Class(); clazz.setName("1班"); // 建立兩個學生 Student student1 = new Student(); student1.setName("張三"); Student student2 = new Student(); student2.setName("李四"); // 讓班級關聯學生 clazz.getStudents().add(student1); clazz.getStudents().add(student2); // 讓學生關聯班級 student1.setClazz(clazz); student2.setClazz(clazz); // 保存班級 session.save(clazz); // 保存學生 session.save(student1); session.save(student2); transaction.commit(); }
@Test public void test2() { Session session = HibernateUtil.getCurrentSession(); Transaction transaction = session.beginTransaction(); // 建立一個班級 Class clazz = new Class(); clazz.setName("1班"); // 建立兩個學生 Student student1 = new Student(); student1.setName("張三"); Student student2 = new Student(); student2.setName("李四"); // 讓班級關聯學生 clazz.getStudents().add(student1); clazz.getStudents().add(student2); // 讓學生關聯班級 student1.setClazz(clazz); student2.setClazz(clazz); // 保存班級 session.save(clazz); transaction.commit(); /* 拋異常:org.hibernate.TransientObjectException: object references an unsaved transient instance - save the transient instance before flushing: com.zze.bean.Student 緣由是:保存班級時,班級關聯的學生爲瞬時態對象,不能與瞬時態對象創建關係。 */ }
級聯指的是:操做一個對象的時候,是否會同時操做其關聯的對象。mysql
操做一的一方的時候,是否會同時操做到多的一方。sql
操做多的一方的時候,是否會同時操做到一的一方。數據庫
保存‘一’級聯保存‘多’:session
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd"> <hibernate-mapping> <class name="com.zze.bean.Class" table="class"> <id name="id"> <generator class="native"/> </id> <property name="name" length="32"/> <set name="students" cascade="save-update"> <key column="cid"/> <one-to-many class="com.zze.bean.Student"/> </set> </class> </hibernate-mapping>
@Test public void test3() { Session session = HibernateUtil.getCurrentSession(); Transaction transaction = session.beginTransaction(); // 建立一個班級 Class clazz = new Class(); clazz.setName("1班"); // 建立兩個學生 Student student1 = new Student(); student1.setName("張三"); Student student2 = new Student(); student2.setName("李四"); // 讓班級關聯學生 clazz.getStudents().add(student1); clazz.getStudents().add(student2); // 保存班級級聯保存學生 session.save(clazz); transaction.commit(); }
保存‘多’級聯保存‘一’:app
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd"> <hibernate-mapping> <class name="com.zze.bean.Student" table="student"> <id name="id"> <generator class="native"/> </id> <property name="name" length="32"/> <property name="age"/> <property name="birthday"/> <property name="address"/> <many-to-one name="clazz" column="cid" class="com.zze.bean.Class" cascade="save-update"/> </class> </hibernate-mapping>
@Test public void test4(){ Session session = HibernateUtil.getCurrentSession(); Transaction transaction = session.beginTransaction(); // 建立一個班級 Class clazz = new Class(); clazz.setName("1班"); // 建立兩個學生 Student student1 = new Student(); student1.setName("張三"); Student student2 = new Student(); student2.setName("李四"); // 讓學生關聯班級 student1.setClazz(clazz); student2.setClazz(clazz); // 保存學生級聯保存班級 session.save(student1); session.save(student2); transaction.commit(); }
假如表中已有以下數據:ide
刪除‘一’級聯刪除‘多’:this
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd"> <hibernate-mapping> <class name="com.zze.bean.Class" table="class"> <id name="id"> <generator class="native"/> </id> <property name="name" length="32"/> <set name="students" cascade="save-update,delete"> <key column="cid"/> <one-to-many class="com.zze.bean.Student" /> </set> </class> </hibernate-mapping>
@Test public void test5() { Session session = HibernateUtil.getCurrentSession(); Transaction transaction = session.beginTransaction(); Class clazz = session.get(Class.class, 1); session.delete(clazz); transaction.commit(); }
刪除‘多’級聯刪除‘一’:url
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd"> <hibernate-mapping> <class name="com.zze.bean.Student" table="student"> <id name="id"> <generator class="native"/> </id> <property name="name" length="32"/> <property name="age"/> <property name="birthday"/> <property name="address"/> <many-to-one name="clazz" column="cid" class="com.zze.bean.Class" cascade="save-update,delete"/> </class> </hibernate-mapping>
@Test public void test6() { Session session = HibernateUtil.getCurrentSession(); Transaction transaction = session.beginTransaction(); Student student = session.get(Student.class, 1); session.delete(student); transaction.commit(); // 級聯刪除‘一’的一方,將其它關聯‘一’的一方的數據的外鍵設爲 null。 // 若是‘一’的一方也設置了級聯刪除,那麼全部關聯‘一’的一方的數據都會被刪除。 }
假入表中已有以下數據:spa
先要把張三從 1 班轉到 2 班:
@Test public void test7(){ Session session = HibernateUtil.getCurrentSession(); Transaction transaction = session.beginTransaction(); Student student = session.get(Student.class, 1); // 獲取張三 Class clazz = session.get(Class.class, 1); // 獲取 2 班 clazz.getStudents().add(student); student.setClazz(clazz); transaction.commit(); /* 此時會執行一下兩條 SQL: Hibernate: update student set name=?, age=?, birthday=?, address=?, cid=? where id=? Hibernate: update student set cid=? where id=? */ }
執行兩條 SQL 的緣由是:在上述代碼中獲取了兩個持久態對象,而且在事務提交時兩個持久態對象的屬性都發生了變化,因此 Hibernate 發出了兩條 SQL。
但顯然這個操做其實一條 SQL 就能搞定的,這個時候就須要控制一方不維護外鍵關係了。
‘一’的一方放棄維護外鍵關係:
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd"> <hibernate-mapping> <class name="com.zze.bean.Class" table="class"> <id name="id"> <generator class="native"/> </id> <property name="name" length="32"/> <set name="students" cascade="save-update" inverse="true"> <key column="cid"/> <one-to-many class="com.zze.bean.Student" /> </set> </class> </hibernate-mapping>
依舊是執行上述「例 7」代碼,會發現只會由‘多’(學生)的一方發出一條 update 語句。
在 Hibernate 中,‘多’的一方不能夠放棄維護外鍵關係。
緣由是當‘一’的一方維護關係時,Hibernate 會額外發出一條 select 語句查出它所關聯的全部的多的一方,顯然不必。而多的一方維護關係就比較簡單了,直接 update 它自己的外鍵便可。
看到過一個有趣應景的說法:十三億人民(‘多’)記住(維護)國家領導人的名字很簡單,但要想國家領導人(‘一’)記住(維護)十三億就不可能了。
@Test public void test8() { // com/zze/bean/Class.hbm.xml 的 set 中設置了 cascade="save-update"、inverse="true" Session session = HibernateUtil.getCurrentSession(); Transaction transaction = session.beginTransaction(); Student student = new Student(); student.setName("王五"); Class clazz = session.get(Class.class, 2); // 獲取 1 班 clazz.getStudents().add(student); session.save(clazz); transaction.commit(); /* 上述代碼執行結果會是怎麼樣? 先明確 cascade 和 inverse 的做用: cascade:爲 save-update,即 Class 會級聯保存和更新 Student。 inverse:爲 true,即 Class 放棄維護外鍵關係。 首先 session 保存的是 clazz,clazz 是持久態對象。 在事務提交時 clazz 的外鍵字段 students 發生了變化,添加了 student, 班級有級聯操做學生的能力,因此「王五」這個 student 會被保存到數據庫。 而班級放棄了維護外鍵關係,因此「王五」這個 student 添加到數據庫後對應的外鍵字段爲 null。 結果以下: mysql> select * from student; +----+--------+------+----------+---------+------+ | id | name | age | birthday | address | cid | +----+--------+------+----------+---------+------+ | 1 | 張三 | NULL | NULL | NULL | 1 | | 2 | 王五 | NULL | NULL | NULL | NULL | +----+--------+------+----------+---------+------+ 2 rows in set (0.00 sec) */ }
例:一名學生能夠選多門課程,一門課程也能夠被多名學生選擇:
package com.zze.bean; import java.util.Date; import java.util.HashSet; import java.util.Set; public class Student { private Integer id; private String name; private Integer age; private Date birthday; private String address; private Class clazz; private Set<Course> courses = new HashSet<>(); public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } public Date getBirthday() { return birthday; } public void setBirthday(Date birthday) { this.birthday = birthday; } public String getAddress() { return address; } public void setAddress(String address) { this.address = address; } public Class getClazz() { return clazz; } public void setClazz(Class clazz) { this.clazz = clazz; } public Set<Course> getCourses() { return courses; } public void setCourses(Set<Course> courses) { this.courses = courses; } }
package com.zze.bean; import java.util.HashSet; import java.util.Set; public class Course { private Integer id; private String name; private Set<Student> students = new HashSet<>(); public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Set<Student> getStudents() { return students; } public void setStudents(Set<Student> students) { this.students = students; } }
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd"> <hibernate-mapping> <class name="com.zze.bean.Student" table="student"> <id name="id"> <generator class="native"/> </id> <property name="name" length="32"/> <property name="age"/> <property name="birthday"/> <property name="address"/> <many-to-one name="clazz" column="cid" class="com.zze.bean.Class" cascade="save-update,delete"/> <set name="courses" table="student_course"> <!-- key : column : 當前對象類對應中間表的外鍵名稱 --> <key column="studentId"/> <!-- many-to-many : 標識多對多關係 class : 對方類全路徑 column : 對方對應中間表的外鍵名稱 --> <many-to-many class="com.zze.bean.Course" column="courseId"/> </set> </class> </hibernate-mapping>
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd"> <hibernate-mapping> <class name="com.zze.bean.Course" table="course"> <id name="id"> <generator class="native"/> </id> <property name="name" length="32"/> <set name="students" table="student_course"> <!-- key : column : 當前對象類對應中間表的外鍵名稱 --> <key column="courseId"/> <!-- many-to-many : 標識多對多關係 class : 對方類全路徑 column : 對方對應中間表的外鍵名稱 --> <many-to-many class="com.zze.bean.Student" column="studentId"/> </set> </class> </hibernate-mapping>
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE hibernate-configuration PUBLIC "-//Hibernate/Hibernate Configuration DTD 3.0//EN" "http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd"> <hibernate-configuration> <session-factory> <property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property> <property name="hibernate.connection.url">jdbc:mysql://192.168.208.153:3306/test</property> <property name="hibernate.connection.username">root</property> <property name="hibernate.connection.password">root</property> <property name="hibernate.dialect">org.hibernate.dialect.MySQLDialect</property> <property name="hibernate.show_sql">true</property> <property name="hibernate.format_sql">true</property> <property name="current_session_context_class">thread</property> <property name="hibernate.hbm2ddl.auto">create</property> <mapping resource="com/zze/bean/Class.hbm.xml"></mapping> <mapping resource="com/zze/bean/Student.hbm.xml"></mapping> <mapping resource="com/zze/bean/Course.hbm.xml"></mapping> </session-factory> </hibernate-configuration>
@Test public void test9() { Session session = HibernateUtil.getCurrentSession(); Transaction transaction = session.beginTransaction(); Student student = new Student(); student.setName("張三"); Course course = new Course(); course.setName("數學"); student.getCourses().add(course); course.getStudents().add(student); session.save(student); session.save(course); transaction.commit(); /* 拋異常:org.hibernate.exception.ConstraintViolationException: could not execute statement 緣由:由於多對多關係的維護是依賴第三張中間表,而中間表的字段是兩個分別關聯多對多這兩張表的主鍵的外鍵, 且這兩個外鍵在中間表中組成了聯合主鍵,而上述代碼實際上進行了兩次 insert 操做,這兩次操做的內容違 反了聯合主鍵約束,因此會拋出異常。 */ }
分析緣由後,能夠得出結論,在多對多中兩方關聯時,必須有一方得放棄維護外鍵關係(通常是被動方放棄,即 Course 放棄),下面配置二選一:
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd"> <hibernate-mapping> <class name="com.zze.bean.Student" table="student"> <id name="id"> <generator class="native"/> </id> <property name="name" length="32"/> <property name="age"/> <property name="birthday"/> <property name="address"/> <many-to-one name="clazz" column="cid" class="com.zze.bean.Class" cascade="save-update,delete"/> <set name="courses" table="student_course" inverse="true"> <!-- key : column : 當前對象類對應中間表的外鍵名稱 --> <key column="studentId"/> <!-- many-to-many : 標識多對多關係 class : 對方類全路徑 column : 對方對應中間表的外鍵名稱 --> <many-to-many class="com.zze.bean.Course" column="courseId"/> </set> </class> </hibernate-mapping>
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd"> <hibernate-mapping> <class name="com.zze.bean.Course" table="course"> <id name="id"> <generator class="native"/> </id> <property name="name" length="32"/> <set name="students" table="student_course" inverse="true"> <!-- key : column : 當前對象類對應中間表的外鍵名稱 --> <key column="courseId"/> <!-- many-to-many : 標識多對多關係 class : 對方類全路徑 column : 對方對應中間表的外鍵名稱 --> <many-to-many class="com.zze.bean.Student" column="studentId"/> </set> </class> </hibernate-mapping>
修改配置後,再次執行 「例 9」 中代碼,成功。
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd"> <hibernate-mapping> <class name="com.zze.bean.Student" table="student"> <id name="id"> <generator class="native"/> </id> <property name="name" length="32"/> <property name="age"/> <property name="birthday"/> <property name="address"/> <many-to-one name="clazz" column="cid" class="com.zze.bean.Class" cascade="save-update,delete"/> <set name="courses" table="student_course" cascade="save-update"> <!-- key : column : 當前對象類對應中間表的外鍵名稱 --> <key column="studentId"/> <!-- many-to-many : 標識多對多關係 class : 對方類全路徑 column : 對方對應中間表的外鍵名稱 --> <many-to-many class="com.zze.bean.Course" column="courseId"/> </set> </class> </hibernate-mapping>
@Test public void test10() { Session session = HibernateUtil.getCurrentSession(); Transaction transaction = session.beginTransaction(); Student student = new Student(); student.setName("張三"); Course course = new Course(); course.setName("數學"); student.getCourses().add(course); session.save(student); transaction.commit(); }
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd"> <hibernate-mapping> <class name="com.zze.bean.Student" table="student"> <id name="id"> <generator class="native"/> </id> <property name="name" length="32"/> <property name="age"/> <property name="birthday"/> <property name="address"/> <many-to-one name="clazz" column="cid" class="com.zze.bean.Class" cascade="save-update,delete"/> <set name="courses" table="student_course" cascade="save-update,delete"> <!-- key : column : 當前對象類對應中間表的外鍵名稱 --> <key column="studentId"/> <!-- many-to-many : 標識多對多關係 class : 對方類全路徑 column : 對方對應中間表的外鍵名稱 --> <many-to-many class="com.zze.bean.Course" column="courseId"/> </set> </class> </hibernate-mapping>
@Test public void test11() { Session session = HibernateUtil.getCurrentSession(); Transaction transaction = session.beginTransaction(); Student student = session.get(Student.class, 1); session.delete(student); transaction.commit(); }