1. 程式人生 > >五、hibernate表與表之間的關係(一對多關係)

五、hibernate表與表之間的關係(一對多關係)

資料庫表與表之間的關係

一對多:一個學校可以有多個學生,一個學生只能有一個學校

多對多:一個學生可以有多個老師,一個老師可以教多個學生

一對一:一個人只能有一個身份證號,一個身份證號只能找到一個人

一對多關係

建立學生和學校表

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集合
11 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() { 21
return 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 }
View Code

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>
  • 測試程式碼
    • 測試方法
       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>
  • 測試程式碼
    • 測試方法 
      @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主要作用是指定哪一方來維護關係

  1. 取值是boolean,預設inverse="false"
  2. 如果一方的對映檔案中設定為true,說明在對映關係中讓另一方來維護關係;如果為false,就自己來維護關係
  3. 只能在一的一方設定

測試方法

  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