五、hibernate表與表之間的關係(一對多關係)
阿新 • • 發佈:2018-12-24
資料庫表與表之間的關係
一對多:一個學校可以有多個學生,一個學生只能有一個學校
多對多:一個學生可以有多個老師,一個老師可以教多個學生
一對一:一個人只能有一個身份證號,一個身份證號只能找到一個人
一對多關係
建立學生和學校表
create table school( sch_id int PRIMARY KEY auto_increment , sch_name VARCHAR(30), sch_address VARCHAR(200) ); create table student( sid int PRIMARY KEY auto_increment, s_sch int , sname VARCHAR(30), FOREIGN KEY(s_sch) REFERENCES school(sch_id) );
根據表建立實體類和對映檔案
一的一方
School.java
1 package com.qf.entity; 2 3 import java.util.HashSet; 4 import java.util.Set; 5 6 public class School { 7 private long sch_id; 8 private String sch_name; 9 private String sch_address; 10 //一個學校有多個學生,應該是一對多關係中多的一方的集合,hibernate預設使用set集合View Code11 private Set<Student> stus = new HashSet<Student>(); 12 13 //get、set方法 14 public long getSch_id() { 15 return sch_id; 16 } 17 public void setSch_id(long sch_id) { 18 this.sch_id = sch_id; 19 } 20 public String getSch_name() { 21return sch_name; 22 } 23 public void setSch_name(String sch_name) { 24 this.sch_name = sch_name; 25 } 26 public String getSch_address() { 27 return sch_address; 28 } 29 public void setSch_address(String sch_address) { 30 this.sch_address = sch_address; 31 } 32 public Set<Student> getStus() { 33 return stus; 34 } 35 public void setStus(Set<Student> stus) { 36 this.stus = stus; 37 } 38 39 @Override 40 public String toString() { 41 return "School [sch_id=" + sch_id + ", sch_name=" + sch_name + ", sch_address=" + sch_address + ", stus=" + stus 42 + "]"; 43 } 44 public School() { 45 super(); 46 } 47 48 }
School.hbm.xml
<?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.qf.entity.School" table="school"> <id name="sch_id" column="sch_id"> <!-- 主鍵生成策略 --> <generator class="native"></generator> </id> <property name="sch_name" column="sch_name"/> <property name="sch_address" column="sch_address"/> <!-- name:多的一方的集合屬性的名稱 --> <set name="stus" > <!-- column:多的一方表的外來鍵名稱--> <key column="s_sch"></key> <!-- class:多的一方的類的全路徑 --> <one-to-many class="com.qf.entity.Student"/> </set> </class> </hibernate-mapping>
多的一方
Student.java
1 package com.qf.entity; 2 3 public class Student { 4 5 private Long sid; 6 private int s_sch; 7 private String sname; 8 //一個學生只能有一個學校 9 private School sch; 10 11 public Student() { 12 super(); 13 } 14 15 public Long getSid() { 16 return sid; 17 } 18 public void setSid(Long sid) { 19 this.sid = sid; 20 } 21 public int getS_sch() { 22 return s_sch; 23 } 24 public void setS_sch(int s_sch) { 25 this.s_sch = s_sch; 26 } 27 public String getSname() { 28 return sname; 29 } 30 public void setSname(String sname) { 31 this.sname = sname; 32 } 33 public School getSch() { 34 return sch; 35 } 36 public void setSch(School sch) { 37 this.sch = sch; 38 } 39 @Override 40 public String toString() { 41 return "Student [sid=" + sid + ", s_sch=" + s_sch + ", sname=" + sname + ", sch=" + sch + "]"; 42 } 43 44 }View Code
Student.hbm.xml
<?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.qf.entity.Student" table="student"> <id name="sid" column="sid"> <!-- 主鍵生成策略 --> <generator class="native"></generator> </id> <property name="sname" column="sname"/> <!-- name:一的一方的物件的屬性 class:一的一方的物件全路徑 column:多的一方表的外來鍵名稱 --> <many-to-one name="sch" class="com.qf.entity.School" column="s_sch"></many-to-one> </class> </hibernate-mapping>
配置核心配置檔案hibernate.cfg.xml
<?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:///test02</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.hbm2ddl.auto" >update</property> <property name="hibernate.show_sql" >true</property> <property name="hibernate.format_sql" >true</property> <mapping resource="com/qf/entity/School.hbm.xml"/> <mapping resource="com/qf/entity/Student.hbm.xml"/> </session-factory> </hibernate-configuration>
測試類
TestDemo.java
1 package com.qf.test; 2 3 import org.hibernate.Session; 4 import org.hibernate.Transaction; 5 import org.junit.Test; 6 7 import com.qf.entity.School; 8 import com.qf.entity.Student; 9 import com.qf.util.SessionFactoryUtil; 10 11 public class TestDemo { 12 @Test 13 public void test() { 14 Session session = SessionFactoryUtil.getSession(); 15 Transaction tx = session.beginTransaction(); 16 17 School sch1 = new School(); 18 sch1.setSch_name("懷遠一中"); 19 School sch2 = new School(); 20 sch2.setSch_name("包集中學"); 21 22 Student stu1 = new Student(); 23 stu1.setSname("張三"); 24 Student stu2 = new Student(); 25 stu2.setSname("李四"); 26 Student stu3 = new Student(); 27 stu3.setSname("王五"); 28 29 stu1.setSch(sch1); 30 stu2.setSch(sch2); 31 stu3.setSch(sch1); 32 sch1.getStus().add(stu1); 33 sch1.getStus().add(stu3); 34 sch2.getStus().add(stu2); 35 36 session.save(stu1); 37 session.save(stu2); 38 session.save(stu3); 39 session.save(sch1); 40 session.save(sch2); 41 42 tx.commit(); 43 } 44 }
檢視資料庫結果
student表
sid | sname | s_sch |
1 | 張三 | 1 |
2 | 李四 | 2 |
3 | 王五 | 1 |
school表
sch_id | sch_name | sch_address |
1 | 懷遠一中 | |
2 | 包集中學 |
級聯操作
級聯,指的是操作一個物件的同時操作該物件相關聯的物件
級聯分類
- 級聯儲存或者更新
- 級聯刪除
級聯儲存或者更新
1. 操作一的一方
- 修改一的一方的對映檔案
- School.hbm.xml
<?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.qf.entity.School" table="school"> <id name="sch_id" column="sch_id"> <!-- 主鍵生成策略 --> <generator class="native"></generator> </id> <property name="sch_name" column="sch_name"/> <property name="sch_address" column="sch_address"/> <!-- name:多的一方的集合屬性的名稱 --> <set name="stus" cascade="save-update"> <!-- column:多的一方表的外來鍵名稱--> <key column="s_sch"></key> <!-- class:多的一方的類的全路徑 --> <one-to-many class="com.qf.entity.Student"/> </set> </class> </hibernate-mapping>
- School.hbm.xml
- 測試程式碼
- 測試方法
1 @Test 2 /** 3 *儲存學校是否會儲存學生資訊 4 *儲存的主體是學校,所以在學校的對映檔案中新增級聯配置 5 *<set name="stus" cascade="save-update"> 6 */ 7 public void test() { 8 Session session = SessionFactoryUtil.getSession(); 9 Transaction tx = session.beginTransaction(); 10 11 School sch1 = new School(); 12 sch1.setSch_name("包集中學"); 13 14 Student stu1 = new Student(); 15 stu1.setSname("李四"); 16 17 stu1.setSch(sch1); 18 sch1.getStus().add(stu1); 19 20 session.save(sch1); 21 22 tx.commit(); 23 }
- 測試方法
- 檢視資料庫中結果
-
student表
sid sname s_sch 1 李四 1 school表
sch_id sch_name sch_address 1 包集中學
-
2. 操作多的一方
- 操作多的一方的對映檔案
- Student.hbm.xml
<?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.qf.entity.Student" table="student"> <id name="sid" column="sid"> <!-- 主鍵生成策略 --> <generator class="native"></generator> </id> <property name="sname" column="sname"/> <!-- name:一的一方的物件的屬性 class:一的一方的物件全路徑 column:多的一方表的外來鍵名稱 --> <many-to-one cascade="save-update" name="sch" class="com.qf.entity.School" column="s_sch"></many-to-one> </class> </hibernate-mapping>
- Student.hbm.xml
- 測試程式碼
- 測試方法
@Test /** *儲存學生是否會儲存學校資訊 *儲存的主體是學生,所以在學生的對映檔案中新增級聯配置 *<many-to-one cascade="save-update" name="sch" class="com.qf.entity.School" column="s_sch"/> */ public void test() { Session session = SessionFactoryUtil.getSession(); Transaction tx = session.beginTransaction(); School sch1 = new School(); sch1.setSch_name("懷遠一中"); Student stu1 = new Student(); stu1.setSname("張三"); stu1.setSch(sch1); sch1.getStus().add(stu1); session.save(stu1); tx.commit(); }
- 測試方法
- 檢視資料庫中結果
-
student表
sid sname s_sch 1 張三 1 school表
sch_id sch_name sch_address 1 懷遠一中
-
3. 可以在多的一方和一的一方分別配置級聯,這樣操作一的一方、多的一方,同時都會操作另一方
1 @Test 2 /** 3 *儲存學校是否會儲存學生資訊 4 *儲存的主體是學校,所以在學校的對映檔案中新增級聯配置 5 *<set name="stus" cascade="save-update"> 6 */ 7 public void test() { 8 Session session = SessionFactoryUtil.getSession(); 9 Transaction tx = session.beginTransaction(); 10 11 School sch1 = new School(); 12 sch1.setSch_name("包集中學"); 13 14 Student stu1 = new Student(); 15 stu1.setSname("李四"); 16 Student stu2 = new Student(); 17 stu2.setSname("王五"); 18 Student stu3 = new Student(); 19 stu3.setSname("張三"); 20 21 stu1.setSch(sch1); 22 sch1.getStus().add(stu2); 23 sch1.getStus().add(stu3); 24 25 /* 26 * 學校插入一條記錄,學生插入三條記錄 27 * 因為儲存stu1,級聯儲存sch1,儲存sch1,級聯儲存stu2、stu3 28 */ 29 session.save(stu1); 30 /* 31 * 學生插入一條記錄 32 * 因為儲存stu2,stu2並沒有其它關聯的,所以只儲存stu2 33 */ 34 //session.save(stu2); 35 /* 36 * 學校插入一條記錄,學生插入兩條記錄 37 * 因為儲存sch1,級聯儲存stu2、stu3 38 */ 39 //session.save(sch1); 40 41 tx.commit(); 42 }
級聯刪除
通常jdbc操作資料庫情況下,如果要刪除表中學校資訊,直接刪除學校是無法成功的,必須先將該學校下所有的學生資訊刪除完,才能刪除學校資訊
級聯刪除可以讓我們在刪除學校的同時級聯刪除相關的學生資訊
1. 配置級聯刪除之前測試
1 @Test 2 public void delete() { 3 Session session = SessionFactoryUtil.getSession(); 4 Transaction tx = session.beginTransaction(); 5 6 /* 7 * 先查詢再刪除 8 */ 9 School school = session.get(School.class, 1L); 10 session.delete(school); 11 12 tx.commit(); 13 }
console輸出
Hibernate: select school0_.sch_id as sch_id1_0_0_, school0_.sch_name as sch_name2_0_0_, school0_.sch_address as sch_addr3_0_0_ from school school0_ where school0_.sch_id=? Hibernate: update student set s_sch=null where s_sch=? Hibernate: delete from school where sch_id=?
結論
- 刪除學校資訊時,預設先修改學生資訊的外來鍵(置為null),再刪除學校資訊
- hibernate刪除一對多關係中一的一方某記錄資訊時,預設先將多的一方引用一的一方相關資訊的外來鍵置為null,再刪除一的一方記錄資訊
2. 配置刪除學校時級聯刪除學生
- 因為刪除主體是學校,所以需要在學校的對映檔案中做級聯刪除配置<set name="stus" cascade="delete">
<?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.qf.entity.School" table="school"> <id name="sch_id" column="sch_id"> <!-- 主鍵生成策略 --> <generator class="native"></generator> </id> <property name="sch_name" column="sch_name"/> <property name="sch_address" column="sch_address"/> <!-- name:多的一方的集合屬性的名稱 --> <set name="stus" cascade="delete"> <!-- column:多的一方表的外來鍵名稱--> <key column="s_sch"></key> <!-- class:多的一方的類的全路徑 --> <one-to-many class="com.qf.entity.Student"/> </set> </class> </hibernate-mapping>
-
測試方法(必須先查詢再刪除,如果自己建立物件刪除的話,自己建立的物件裡資訊可能不全,例如school中可能沒有學生集合資訊)
@Test public void delete() { Session session = SessionFactoryUtil.getSession(); Transaction tx = session.beginTransaction(); /* * 先查詢再刪除 */ School school = session.get(School.class, 1L); session.delete(school); tx.commit(); }
-
console輸出
Hibernate: select school0_.sch_id as sch_id1_0_0_, school0_.sch_name as sch_name2_0_0_, school0_.sch_address as sch_addr3_0_0_ from school school0_ where school0_.sch_id=? Hibernate: select stus0_.s_sch as s_sch3_1_0_, stus0_.sid as sid1_1_0_, stus0_.sid as sid1_1_1_, stus0_.sname as sname2_1_1_, stus0_.s_sch as s_sch3_1_1_ from student stus0_ where stus0_.s_sch=? Hibernate: update student set s_sch=null where s_sch=? Hibernate: delete from student where sid=? Hibernate: delete from student where sid=? Hibernate: delete from student where sid=? Hibernate: delete from school where sch_id=?
-
結論
- 刪除學校資訊時級聯刪除了相關學生資訊
3. 刪除學生資訊級聯刪除學校資訊(並不符合常理,一般不會使用)
- 配置學生對映檔案
- <many-to-one name="sch" cascade="delete" class="com.qf.entity.School" column="s_sch"/>
維護關係inverse
inverse主要作用是指定哪一方來維護關係
- 取值是boolean,預設inverse="false"
- 如果一方的對映檔案中設定為true,說明在對映關係中讓另一方來維護關係;如果為false,就自己來維護關係
- 只能在一的一方設定
測試方法
2號學生本來是2號學校的,改變成1號學校
@Test public void test() { Session session = SessionFactoryUtil.getSession(); Transaction tx = session.beginTransaction(); Student stu = session.get(Student.class, 2L); School sch = session.get(School.class, 1L); stu.setS_sch(2); sch.getStus().add(stu); tx.commit(); }
1.不設定,使用預設值
School.hbm.xml
<set name="stus" cascade="save-update delete" inverse="false" >
<!-- column:多的一方表的外來鍵名稱-->
<key column="s_sch"></key>
<!-- class:多的一方的類的全路徑 -->
<one-to-many class="com.qf.entity.Student"/>
</set>
Hibernate: select student0_.sid as sid1_1_0_, student0_.sname as sname2_1_0_, student0_.s_sch as s_sch3_1_0_ from student student0_ where student0_.sid=? Hibernate: select school0_.sch_id as sch_id1_0_0_, school0_.sch_name as sch_name2_0_0_, school0_.sch_address as sch_addr3_0_0_ from school school0_ where school0_.sch_id=? Hibernate: select stus0_.s_sch as s_sch3_1_0_, stus0_.sid as sid1_1_0_, stus0_.sid as sid1_1_1_, stus0_.sname as sname2_1_1_, stus0_.s_sch as s_sch3_1_1_ from student stus0_ where stus0_.s_sch=? Hibernate: update student set sname=?, s_sch=? where sid=? Hibernate: update student set s_sch=? where sid=?
2.設定另一方維護關係
School.hbm.xml
<set name="stus" cascade="save-update delete" inverse="true" >
<!-- column:多的一方表的外來鍵名稱-->
<key column="s_sch"></key>
<!-- class:多的一方的類的全路徑 -->
<one-to-many class="com.qf.entity.Student"/>
</set>
Hibernate: select student0_.sid as sid1_1_0_, student0_.sname as sname2_1_0_, student0_.s_sch as s_sch3_1_0_ from student student0_ where student0_.sid=? Hibernate: select school0_.sch_id as sch_id1_0_0_, school0_.sch_name as sch_name2_0_0_, school0_.sch_address as sch_addr3_0_0_ from school school0_ where school0_.sch_id=? Hibernate: select stus0_.s_sch as s_sch3_1_0_, stus0_.sid as sid1_1_0_, stus0_.sid as sid1_1_1_, stus0_.sname as sname2_1_1_, stus0_.s_sch as s_sch3_1_1_ from student stus0_ where stus0_.s_sch=? Hibernate: update student set sname=?, s_sch=? where sid=?
結論:
- 設定另一方維護關係時,少傳送一次更新語句
- inverse="true"指的是由雙向關聯另一方維護該關聯,己方不維護該關聯
- Inverse預設為false,雙向關係的兩端都能控制,會造成一些問題(更新的時候會因為兩端都控制關係,於是重複更新),可以在一端將inverse值設為true