hibernate中維護“一對多”關係的兩種方式

目錄

 

關於數據庫中“一對多”關係的介紹

此篇博客所涉及到的數據庫

交給“一”的一方來維護“一對多”關係:

交給“多”的一方來維護“一對多”關係

“一對多”關係究竟交給誰維護?


關於數據庫中“一對多”關係的介紹

在數據庫設計的時候我們經常會遇到這樣的問題:一個表中的字段可以對應另一張表的很多個字段。這樣子的關係我們就稱之爲“一對多”的關係,而如果站在“多”的一方來看的話,就是“多對一”的關係。它們實質上是指的同一種關係。例子:

 以上部門表與員工表之間就存在着:一個部門可以對應多個員工,但是一個員工只能對應一個部門這種關係。這種關係就屬於典型的也是經典的“一對多”的關係。而“一”的一方就是部門,“多”的一方就是員工。

此篇博客所涉及到的數據庫

我們這篇博客使用的是mysql數據庫,而其數據庫中的表結構如下:

表dep(部門表):

    depid : 表示部門的id即編號  (主鍵,非空)

    depname : 表示部門的名字(非空)

表emp(員工表):

    eid : 表示員工的id即編號(主鍵,非空)

    ename : 表示員工的姓名(非空)

    did : 表示員工所在部門的編號(外鍵於dep表中的depid,非空)

其創建表時的sql語句如下(也可以使用Navicat這種可視化工具創建,效率相對較高):

create table dep(
    -> depid int(11) not null,
    -> depname varchar(255) not null,
    -> primary key(depid)
    -> );
 create table emp(
    -> eid int(11) not null,
    -> ename varchar(255) not null,
    -> did int(11),
    -> foreign key(did) references dep(depid)
    -> );

hibernate中用兩種維護“一對多”關係的方式,一種是交給“一”的一方來維護,另一種是交給“多”的一方來維護。不管這種關係是交給“一”的一方來維護還是交給“多”的一方來維護,其前提都是有以上說明的數據庫做支撐,也就是必須嚴格按照上面的數據庫進行設計主鍵和外鍵。

交給“一”的一方來維護“一對多”關係:

交給“一”的一方來維護“一對多”的關係的方式就是在“一”的一方的數據庫中的表所對應的pojo類中新增一個集合屬性用於存儲“多”的一方的數據庫中的表所對應的pojo類的對象。

首先,在dep表所對應的Dep類中定義了一個Set集合用於存儲Emp類的對象:

public class Dep {

	private int depid;
	private String depname;
	private Set<Emp> emps = new HashSet<Emp>();//“多”的一方的集合

	public Set<Emp> getEmps() {
		return emps;
	}
	public void setEmps(Set<Emp> emps) {
		this.emps = emps;
	}
	public int getDepid() {
		return depid;
	}
	public void setDepid(int depid) {
		this.depid = depid;
	}
	public String getDepname() {
		return depname;
	}
	public void setDepname(String depname) {
		this.depname = depname;
	}
}

其次,在Dep類所對應的dep.hbm.xml配置文件中使用<set>對其關係進行聲明:

<hibernate-mapping>
	<!-- 一張表對應一個類 orm  -->
	<class name="com.pojo.Dep" table="dep">
		<id column="depid" name="depid">
			<generator class="assigned"></generator>		
		</id>
		<property column="depname" name="depname"></property>
		<!-- 關係 -->
		<set name="emps">
			<key column="did"></key>
			<one-to-many class="com.pojo.Emp"/>
		</set>
	</class>
</hibernate-mapping>

關於<set>中的屬性和子標籤:

    name屬性:對應Dep類中set集合類型的屬性的屬性名;

    <key>子標籤:對應set集合中存儲的類對象所對應的類所對應的表(即emp表)中與dep表中depid所聯繫的外鍵列名;

    <one-to-many>子標籤中class屬性:set集合中存儲的類的位置。

以上就是我們在“一對多”關係出現後,由“一“的一方去維護此關係時所需要的配置。關於此關係”一“的一方如何來維護,或者說如何使用此配置,下面我們寫一個測試類來進行說明:

public class Test {
	public static void main(String[] args) {
		// 1.讀取總的配置文件
		Configuration configuration = new Configuration().configure();
		// 2.創建session工廠
		SessionFactory factory = configuration.buildSessionFactory();
		// 3.得到session
		Session session = factory.openSession();
		// 4.開啓事務
		Transaction transaction = session.beginTransaction();

		Dep dep = new Dep();
		dep.setDepid(7);
		dep.setDepname("中賣部");

		Emp emp = new Emp();
		emp.setEmpid(6);
		emp.setEmpname("鳥上飛");

		dep.getEmps().add(emp);
	
		session.save(dep);
		session.save(emp);
		transaction.commit();
		session.close();		
	}
}

在上邊的main方法執行後,數據庫中dep表中就會加入一條數據:

emp表中也會加上一條數據:

 接下來我們考慮一個問題:session.save( )的執行順序是否會影響到最後的結果?在上面我們的測試方法中是下面這個順序:

session.save(dep);
session.save(emp);

即先保存dep對象,再保存emp對象。而控制檯通過log4j打印出來的sql語句如下:

    Hibernate: insert into dep (depname, depid) values (?, ?)
    Hibernate: insert into emp (ename, did, eid) values (?, ?, ?)
    Hibernate: update emp set did=? where eid=?

通過這個可以看出,hibernate框架對其的執行過程是:先加入dep對象所對應的屬性值進入數據庫,再加入emp對象所對應的屬性值進入數據庫而此時did字段的值是多少呢?我們暫時還不知道,但是不妨猜想一下是null,最後再根據eid找到emp表中剛剛加入的數據對did進行修改。

上面設計數據庫的時候我們知道,emp表中的did字段(即外鍵字段)並沒有設置not null 。那我們現在把它設置成not null,看一下main方法的執行結果是什麼。 將剛剛添加在數據庫中的數據刪除並且把did字段修改成not null之後再去執行一下main方法我們發現控制檯輸出結果如下:

並且數據庫中兩張表中都沒有相關數據的加入。也就是說在執行到“insert into emp(ename,did,eid) values(?,?,?)”這條sql語句的時候,出錯了,它說did字段不可以爲空值。這就可以證實了我們之前的猜想,那就是關於數據庫中數據的變化過程是:先加入dep對象所對應的屬性值進入數據庫,再加入emp對象所對應的屬性值進入數據庫而此時did字段的值爲null,最後再根據eid找到emp表中剛剛加入的數據對did進行修改。

但是是在執行到“insert into emp(ename,did,eid) values(?,?,?)”的時候報的錯,按說“insert into dep(depname,depid) values (?,?)”已經執行完了,dep表中應該有相關數據的加入呀,事實並沒有。是因爲我們此次操作開啓了事務,一旦全部流程中任何一處報了錯,就會進行回滾。之前執行的sql語句所產生的結果都會被打回原形。

現在我們把session.save( )的順序進行顛倒:

session.save(emp);
session.save(dep);

此次 運行控制檯打印輸出的結果如下:

可以看出還是在執行到“insert into emp(ename,did,eid) values (?,?,?)”的時候出了did字段爲null的錯誤。但是這次與上次不同的是這次並沒有打印輸出“insert into dep(depname,depid) values (?,?)”這句話。可見是因爲還沒有來得及執行這句話就已經出現了錯誤。而不難推斷:出現這個錯誤的原因與上邊所提到的sql語句的執行有關。那session.save( )的順序不變,我們把數據庫中emp表所對應的did字段的not null取消掉,之後再執行一邊main方法。

控制檯完美輸出且數據庫中有相關數據加入:

Hibernate: insert into dep (depname, depid) values (?, ?)
Hibernate: insert into emp (ename, did, eid) values (?, ?, ?)
Hibernate: update emp set did=? where eid=?

由以上分析可見:

    1. session.save( )的順序只會影響到insert語句的執行順序,而不會對最終結果造成影響。因爲每次加入數據的時候,兩個表都是獨立的加入的,並不是dep不加入emp就不可以加入,也不是emp不加入dep就不可以加入。也就是:雖然在emp表中did字段依賴於dep表中的depid字段,但是在emp表中加入數據的原理是did一開始加入null,然後再去修改did。這也就是session.save( )的書寫順序不會對結果造成影響的原因;

    2. emp表即“多”的一方的表中的外鍵字段,不能設置爲非空。否則這種方式是不可用的。

交給“多”的一方來維護“一對多”關係

上邊我們演示的是如何使用“一”的一方來維護數據庫中“一對多”的關係。下面我們演示一下如何使用“多”的一方來維護此關係。

首先,在“多”的一方的pojo類中加入“一”的一方所對應的Pojo類所對應的類型的屬性。例如:

public class Emp {
	private int empid;
	private String empname;
	private Dep dep;//“一”的一方

	public int getEmpid() {
		return empid;
	}
	public Dep getDep() {
		return dep;
	}
	public void setDep(Dep dep) {
		this.dep = dep;
	}
	public void setEmpid(int empid) {
		this.empid = empid;
	}
	public String getEmpname() {
		return empname;
	}
	public void setEmpname(String empname) {
		this.empname = empname;
	}
}

其次,在emp.hbm.xml文件中配置<many-to-one>標籤:

<hibernate-mapping>
	<!-- 一張表對應一個類 orm -->
	<class name="com.pojo.Emp" table="emp">
		<id column="eid" name="empid">
			<generator class="assigned"></generator>
		</id>
		<property column="ename" name="empname"></property>
	
		<!-- 多對一  -->
		<many-to-one name="dep" class="com.pojo.Dep" column="did"></many-to-one>
	</class>
</hibernate-mapping>

關於<many-to-one>標籤:

    name屬性:Emp類中對應emp表中的外鍵did字段的屬性的名字;

    class屬性:指明“一”所對應的類;

    column屬性:emp表中外鍵名字。

以上就是使用“多”的一方去維護“一對多”關係時的準備工作。關於此關係”多“的一方如何來維護,或者說如何使用此配置,下面我們寫一個測試類來進行說明:

public class Test3 {
	public static void main(String[] args) {
		Configuration configuration = new Configuration().configure();
		SessionFactory factory = configuration.buildSessionFactory();
		Session session = factory.openSession();
		Transaction transaction = session.beginTransaction();

		Dep dep = new Dep();
		dep.setDepid(8);
		dep.setDepname("信箋部");

		Emp emp = new Emp();
		emp.setEmpid(11);
		emp.setEmpname("王九");

		emp.setDep(dep);

		session.save(dep);
		session.save(emp);

		transaction.commit();

		session.close();
	}
}

其運行結束後數據庫中存在相關數據:

 

並且控制檯打印輸出兩條sql語句:

    Hibernate: insert into dep (depname, depid) values (?, ?)
    Hibernate: insert into emp (ename, did, eid) values (?, ?, ?)

可見,其執行過程是:加入相對應的dep表中的數據,然後再加入emp表中的數據。(由其打印輸出的sql語句可知,這時候如果將emp表中的did字段設置成not null之後不會再像由“一”的一方維護關係時那樣再去報錯)

那麼我們把數據庫中剛剛加入的數據刪除並將session.save( )換一下順序:

session.save(emp);
session.save(dep);

之後執行main方法,控制檯打印輸出結果如下並且數據庫中存在相關數據: 

    Hibernate: select dep_.depid, dep_.depname as depname0_ from dep dep_ where dep_.depid=?
    Hibernate: insert into emp (ename, did, eid) values (?, ?, ?)
    Hibernate: insert into dep (depname, depid) values (?, ?)
    Hibernate: update emp set ename=?, did=? where eid=? 

可以看出有update語句,所以可以猜測出,如果再把emp表中的did字段設置成not null,之後它還會報錯。我們測試了一下,果不其然,結果如下:

 由上邊的sql語句可以看出,"insert into emp(ename,did,eid) values(?,?,?)"語句執行的時候報了錯,由此可知這條語句執行的時候did爲null。故其執行過程也就可以看的出了。

但是依照“人之常情”session.save( )的執行過程就應該是:

session.save(dep);
session.save(emp);

因爲如果沒有部門,加個卵子的員工。肯定是現有部門後有員工啊,並且此時的“一對多”關係是由“多”的一方來維護的,是在Emp類中使用了一個Dep類型的屬性。用腳趾頭想想,沒有dep之前,就先有emp也不符合思維習慣不是?! 

“一對多”關係究竟交給誰維護?

上邊我們知道了:“一對多”關係可以交給“一”來維護,也可以交給“多”來維護。那麼究竟交給誰來維護更加好些呢?下面我們根據它們的執行過程進行分析。

由”一“的一方來維護此關係的話。在session.save( )中不管是先保存”一“還是先保存”多“,其sql語句都是相同的順序:insert...+insert...+update...也就是都會執行三條sql語句。並且在”多“的一方的表中的外鍵字段不能設置成not null。

由”多“的一方來維護此關係的話。在session.save( )中先保存”一“和先保存”多"其區別較大:先保存“一”的話,執行兩條insert...語句,並沒有執行update語句,所以此時在"多"的一方的表中的外鍵字段可以設置成not null ;如果先保存“多”的一方的話,執行的sql語句較多,共有四條:select...+insert..+insert...+update...並且"多"的一方的表中的外鍵字段不可以設置成not null(只要是有update...語句執行那麼外鍵字段都不能設置成not null,因爲在前面的insert...語句中會先將外鍵字段賦值爲null)。

總結:由“一”的一方來維護的話,需要三條sql語句,其效率會變低。如果由“多”的一方來維護的話,一定要記得先保存“一”的一方,這樣只會執行兩條sql語句,效率較高,並且不需要考慮外鍵字段是否爲not null;不建議先保存“多”的一方,第一是不符合人之常情,第二就是所執行的sql語句較多,效率較低,並且還需要考慮外鍵字段是否爲not null這個問題。

所以:建議使用“多”的一方來維護“一對多”的關係,並且在使用session.save( )時先保存“一”再保存“多”。

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章