亚洲在线久爱草,狠狠天天香蕉网,天天搞日日干久草,伊人亚洲日本欧美

Hibernate 一對多雙向關聯映射

1. 前言

本節課程和大家一起聊聊一對多關聯映射。通過本節課程,你將了解到:

  • 如何實現一對多關聯映射;

  • 如何實現雙向一對多關聯映射;

  • 關聯映射中的級聯操作。

2. 一對多關聯映射

關系型數據庫中表與表中的數據存在一對多(或多對一)關系。

如學生表、班級表。一個班級有多個學生,多個學生可以在同一個班級。

一對多或多對一本質上是一樣的,如同一塊硬幣的正面和反面,只是看待事物的角度不同而已。

數據庫中有學生表、班級表。使用 Hibernate 進行數據操作時, 程序中就應該有學生類、班級類。

同時學生類、班級類應該使用 OOP 語法描述出如同學生表和班級表一樣的關系。并且還要讓 Hibernate 看得懂。

有了前面的基礎,直接上代碼:

創建班級類:

@Entity
public class ClassRoom {
	private Integer classRoomId;
	private String classRoomName;
	private String classRoomDesc;
	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	public Integer getClassRoomId() {
		return classRoomId;
	}

需求:查詢學生時,得到學生所在班級信息。

進入學生類,添加如下代碼,描述學生類班級類的關系:

private ClassRoom classRoom;

除此之外,還需要讓 Hibernate 知道,這個對象屬性的值來自于班級表中的對應數據,進一步修改代碼:

private ClassRoom classRoom;
@ManyToOne(targetEntity=ClassRoom.class)
@JoinColumn(name="classRoomId")
public ClassRoom getClassRoom() {
	return classRoom;
}
  • @ManyToOne 告訴 Hibernate,從學生的角度來看,學生是多的一邊,查詢班級表可以得到學生所在班級信息。
  • @JoinColumn 告訴 Hibernate 需要帶著指定的字段值到班級表中匹配數據。

修改 Hibernate 主配置文件中內容:

<property name="hbm2ddl.auto">create</property>
<mapping class="com.mk.po.Student" />
<mapping class="com.mk.po.ClassRoom" />

為了讓事情變得簡單明了,在主配置文件中只保留學生類班級類的映射關系。

學生類中的所有屬性描述:

private Integer stuId;
	private String stuName;
	private String stuSex;
	private String stuPassword;
	private Blob stuPic;
	private ClassRoom classRoom;
    @ManyToOne(targetEntity=ClassRoom.class)
    @JoinColumn(name="classRoomId")
	public ClassRoom getClassRoom() {
		return classRoom;
	}

使用上一節課的模板對象跑一個空測試實例:

@Test
public void testGetByTemplate() {
	HibernateTemplate<Student> hibernateTemplate=new HibernateTemplate<Student>();	
}

目的是讓 Hibernate 重新創建學生表、班級表。別忘記啦,自動創建表后,修改回:

<property name="hbm2ddl.auto">update</property>

進入 MySql,在學生表、班級表中手工添加幾條測試數據:

圖片描述

到了完成需求的時候,測試下面實例:

HibernateTemplate<Student> hibernateTemplate = new HibernateTemplate<Student>();
		hibernateTemplate.template(new Notify<Student>() {
			@Override
			public Student action(Session session) {
				Student stu=(Student)session.get(Student.class, new Integer(1));
				System.out.println("學生姓名:"+stu.getStuName());
				System.out.println("學生所在班級:"+stu.getClassRoom().getClassRoomName());
				return stu_;
			}
		});

控制臺輸出結果:

Hibernate: 
    select
        student0_.stuId as stuId1_1_1_,
        student0_.classRoomId as classRoo6_1_1_,
        student0_.stuName as stuName2_1_1_,
        student0_.stuPassword as stuPassw3_1_1_,
        student0_.stuPic as stuPic4_1_1_,
        student0_.stuSex as stuSex5_1_1_,
        classroom1_.classRoomId as classRoo1_0_0_,
        classroom1_.classRoomDesc as classRoo2_0_0_,
        classroom1_.classRoomName as classRoo3_0_0_ 
    from
        Student student0_ 
    left outer join
        ClassRoom classroom1_ 
            on student0_.classRoomId=classroom1_.classRoomId 
    where
        student0_.stuId=?
學生姓名:Hibernate
學生所在班級:c1911

Hibernate 使用 left outer join 構建了一條多表查詢語句!

3. 雙向一對多關聯映射

需求:查詢班級時,想知道班上有多少名學生,又應該如何映射?

進入班級類,添加如下屬性:

private Set<Student> students;

使用集合屬性 students,描述了一個班有多名學生。

為什么使用 Set 集合?

因為一個班級內不可能出現兩個完全相同的學生對象。

這還僅僅只是 OOP 層面上的關系,還需要告訴 Hibernate 應該如何填充數據。

添加下面代碼:

private Set<Student> students;
@OneToMany(targetEntity=Student.class,mappedBy="classRoom")
public Set<Student> getStudents() {
	return students;
}
  • @OneToMany:很直白的說明了一個班級會有多名學生,指引 Hibernate 在填充數據時,要找到所有學生,別遺漏了;
  • 屬性 mappedBy=“classRoom”: 告訴 Hibernate,班級和學生之間的關系在學生類中已經說的夠明白了,應該不需要再廢話了吧。

OK!把前面的測試實例改為查詢班級信息:

HibernateTemplate<ClassRoom> hibernateTemplate = new HibernateTemplate<ClassRoom>();
		 hibernateTemplate.template(new Notify<ClassRoom>() {
			@Override
			public ClassRoom action(Session session) {
				ClassRoom classRoom=(ClassRoom)session.get(ClassRoom.class, new Integer(1));
				System.out.println("班級名稱:"+classRoom.getClassRoomName());
System.out.println("------我是分隔線------------------------");
				System.out.println("班級學生人數:"+classRoom.getStudents().size());
				return classRoom;
			}
		});

查看控制臺輸出結果:

Hibernate: 
    select
        classroom0_.classRoomId as classRoo1_0_0_,
        classroom0_.classRoomDesc as classRoo2_0_0_,
        classroom0_.classRoomName as classRoo3_0_0_ 
    from
        ClassRoom classroom0_ 
    where
        classroom0_.classRoomId=?
班級名稱:c1911
------我是分隔線------------------------
Hibernate: 
    select
        students0_.classRoomId as classRoo6_0_1_,
        students0_.stuId as stuId1_1_1_,
        students0_.stuId as stuId1_1_0_,
        students0_.classRoomId as classRoo6_1_0_,
        students0_.stuName as stuName2_1_0_,
        students0_.stuPassword as stuPassw3_1_0_,
        students0_.stuPic as stuPic4_1_0_,
        students0_.stuSex as stuSex5_1_0_ 
    from
        Student students0_ 
    where
        students0_.classRoomId=?
班級學生人數:2

會發現一個很有意思的地方。Hibernate 查詢班級時,構建了兩條 SQL。

先查詢班級,當需要學生信息時,才構建查詢學生的 SQL。

大家應該也猜出來了,當從學生表查詢班級表時,Hibernate 采用的是立即策略。

當查詢從班級表查詢到學生表時,Hibernate 采用的是延遲加載策略。

采用延遲加載都只有一個目的,需要時加載,提高響應速度。

現在,學生類和班級類的映射配置信息,能讓 Hibernate 自動從學生表查詢到班級表,也能從班級表查詢到學生表。

這種 2 個實體類中的映射關系就稱為雙向一對多關聯映射

無論是 @ManyToOne 還是 @OneToMany 注解都有 fetch 屬性,可以設置的值有 2 個選擇:

  • FetchType.EAGER
  • FetchType.LAZY

所以,在雙向一對多關聯映射可以選擇是否啟用延遲加載,這和一對一關聯映射中是一樣的,就不在此重復復述。

是否采用延遲加載,由項目邏輯決定。

4. 一對多關聯映射中的級聯操作

什么是級聯操作?

關系型數據庫中由主外鍵維系的兩張表,具有主從關系。

如學生表和班級表,班級班是主表,學生表是從表。

類似于刪除某一個班級的信息,則需要先刪除所在班的學生信息,再刪除班級信息,這個操作就是級聯操作。

所謂級聯操作,指操作一張表時,是否會牽連到與之有關聯的其它表。

現在,咱們是使用 Hibernate 進行數據操作,不可能還要勞駕自己親力親為吧。只需要做些簡單配置,就可以讓 Hibernate 自動做級聯操作。

進入班級類,修改代碼如下:

@OneToMany(targetEntity=Student.class,mappedBy="classRoom",cascade=CascadeType.REMOVE)
	public Set<Student> getStudents() {
		return students;
	}

很簡單,只需要使用 @OneToManycascade 屬性,就能讓 Hibernate 明白如何做級聯操作。默認情況下,沒有級聯效應。

cascade 是一個枚舉類型:

public enum CascadeType {
    ALL,
    PERSIST,
    MERGE,
    REMOVE,
	REFRESH,
    DETACH
}
  • ALL: 級聯所有操作;
  • PERSIST: 級聯新增;
  • MERGE: 級聯更新或者新增;
  • REMOVE: 級聯刪除;
  • REFRESH: 級聯刷新;
  • DETACH: 級聯分離。

測試刪除班級實例:

HibernateTemplate<ClassRoom> hibernateTemplate = new HibernateTemplate<ClassRoom>();
		 hibernateTemplate.template(new Notify<ClassRoom>() {
			@Override
			public ClassRoom action(Session session) {
				ClassRoom classRoom=(ClassRoom)session.get(ClassRoom.class, new Integer(1));	
				 session.delete(classRoom);
				return null;
			}
		});

如果不添加 cascade 相關說明,因為有學生引用班級信息,班級信息是不能被刪除的。

添加后再測試,查看表中內容:班級以及班級所在學生信息全部刪除!
圖片描述

刪除班級時能級聯刪除學生,反過來,刪除學生能刪除班級嗎?

想法很好,實踐是檢驗真理的唯一手段,學生類中修改成如下代碼:

@ManyToOne(targetEntity=ClassRoom.class,cascade=CascadeType.REMOVE)
    @JoinColumn(name="classRoomId")
	public ClassRoom getClassRoom() {
		return classRoom;
	}

測試實例:

HibernateTemplate<Student> hibernateTemplate = new HibernateTemplate<Student>();
		hibernateTemplate.template(new Notify<Student>() {
			@Override
			public Student action(Session session) {
				Student stu=(Student)session.get(Student.class, new Integer(2));
				session.delete(stu);
				return stu;
			}
		});

結果很殘酷!學生被刪除了,班級也被刪除了!

級聯級聯,只要設置了級聯,不管刪除學生還是班級,只要在對應表中有引用關系的數據就會被刪除。

現在,學生類、班級類中的級聯刪除都打開了。如果對下面情形的數據(編號 1、2 的學生的班級編號都為 1)進行刪除操作,則會發生什么事情?

圖片描述

數據庫中的數據如下:

圖片描述

測試刪除編號為 1 的學生:

HibernateTemplate<Student> hibernateTemplate = new HibernateTemplate<Student>();
		hibernateTemplate.template(new Notify<Student>() {
			@Override
			public Student action(Session session) {
				Student stu=(Student)session.get(Student.class, new Integer(1));
				session.delete(stu);
				return stu;
			}
		});

進入 MySql,查看一下:
圖片描述

天呀!這是級聯還是株連呀,太讓人后怕,數據都沒有了。

刪除學生時,會級聯刪除和學生有關的班級,班級刪除時,又會查看學生表中是否還存在與班級有關聯的學生,有,則一刀下去,連根拔起。

Hibernate 有點剎不住車,產生了級聯連鎖反應。

針對上面的測試,如果班級表的級聯關閉,執行測試代碼,請問結果又會怎樣?

本節課程,講解了級聯刪除,級聯添加的內容留到下節課繼續展開。

5. 小結

本節課和大家聊了雙向一對多關聯映射。

無論是一對一雙向關聯映射,還是一對多雙向關聯映射。都可以根據需要隨時設置是否延遲加載、級聯等操作。

在使用級聯操作時,一定要小心,避免產生連鎖反應,刪除了不應該刪除的數據。