目錄
關於數據庫中“一對多”關係的介紹
在數據庫設計的時候我們經常會遇到這樣的問題:一個表中的字段可以對應另一張表的很多個字段。這樣子的關係我們就稱之爲“一對多”的關係,而如果站在“多”的一方來看的話,就是“多對一”的關係。它們實質上是指的同一種關係。例子:
以上部門表與員工表之間就存在着:一個部門可以對應多個員工,但是一個員工只能對應一個部門這種關係。這種關係就屬於典型的也是經典的“一對多”的關係。而“一”的一方就是部門,“多”的一方就是員工。
此篇博客所涉及到的數據庫
我們這篇博客使用的是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( )時先保存“一”再保存“多”。