Hibernate 單向多對一、單向一對多、雙向一對多關聯關係詳解
一對多關係是最普遍的對映關係。比如部門和職工
一對多:從部門的角度,一個部門對應多個職工
多對一:從職工的角度,多個職工對應一個部門
資料庫表中結構:
表 department:did departname
表 Employee:eid ename esex did
工具類:SessionFactoryUtils .java,用來建立session工廠,後面的測試類的程式碼中會呼叫這個工具類的方法
public class SessionFactoryUtils {
private SessionFactory factory;
private static SessionFactoryUtils factoryUtils;
// 單例模式:把構造方法設定為私有的,說明不可以new這個類的例項。
private SessionFactoryUtils() {
}
// 通過定義這個類的靜態方法,並且返回型別與這個類的型別一樣,來實現對這個類的訪問
public static SessionFactoryUtils getInstance() {
if (factoryUtils == null) {
factoryUtils = new SessionFactoryUtils();
}
return factoryUtils;
}
public SessionFactory openSessionFactory() {
if (factory == null) {
//載入主配置檔案
Configuration configuration = new Configuration().configure();
//建立工廠
factory = configuration.buildSessionFactory();
}
return factory;
}
}
單向多對一:
首先兩個實體類,一個部門類,一個職工類:
public class Department {
private Integer did;
private String departname;
//get和set方法,這裡就不貼出來了,自己引入即可
}
public class Employee {
private Integer eid;
private String ename;
private String esex;
private Department department;
}
Employee類的對映檔案:Employee.hbm.xml:多的一方,需要維護雙方關係,所以裡面配置有一的一方的引用。
<hibernate-mapping>
<class name="org.danni.model.entity.Employee">
<!-- 設定主鍵 -->
<id name="eid" column="eid">
<!-- 設定為native之後,主鍵會按照本來的順序進行增長(比如之前23但是刪除了,現在就從24開始) -->
<generator class="native"></generator>
</id>
<!-- 設定屬性 -->
<property name="ename" column="ename"></property>
<property name="esex" column="esex"></property>
<many-to-one name="department" cascade="all" column="did"
class="org.danni.model.entity.Department"></many-to-one>
</class>
</hibernate-mapping>
many-to-one的常用屬性:
name:對映類屬性的名稱(必須)
class:關聯類的完全限定名
column:關聯的欄位
not-null:設定關聯的欄位的值是否可以為空。預設值false
lazy:指定關聯物件是否使用延遲載入以及延遲載入的策略。lazy屬性有三個取值:false、proxy、no-proxy。預設值proxy
fetch:設定抓取資料的策略。預設值是select
many-to-one沒有inverse屬性,因為關係的維護是多的一方,不可能放棄對關係的維護。
Department.hbm.xml對映檔案:一的一方,不需要維護關係,所以和普通的配置一樣既可以了。
<hibernate-mapping>
<class name="org.danni.model.entity.Department">
<id name="did" column="did">
<!-- 設定成increment之後,會從資料庫中查詢主鍵id最大的值,然後+1進行儲存 -->
<generator class="increment"></generator>
</id>
<property name="departname" column="departname"></property>
</class>
</hibernate-mapping>
多對一測試類:Junit Test Case:ManyToOneTest .java
public class ManyToOneTest {
private SessionFactory factory;
@Before
public void init() {
//呼叫前面的工具類來建立並開啟session工廠
factory = SessionFactoryUtils.getInstance().openSessionFactory();
}
@Test
public void add() {
// 所有的公用的都是一個session,它的內容也不會清空。事務是提交之後就關閉了。
Session session = factory.openSession();
Transaction ts = session.beginTransaction();
Department department = new Department();
department.setDepartname("售後部");
Employee e1 = new Employee();
e1.setEname("呵呵");
e1.setEsex("男");
e1.setDepartment(department);
Employee e2 = new Employee();
e2.setEname("嘻嘻");
e2.setEsex("女");
e2.setDepartment(department);
try {
// 單向:也就是說也可通過save員工之前把部門save到資料庫,但是不能通過save部門達到save員工的作用
session.save(e1); //因為設定了級聯cascade為all,所以在儲存Employee的時候會同時儲存Department
session.save(e2);
ts.commit(); //增、刪、改都徐亞用到事務,通過事務來commit,查詢則不需要用到事務
} catch (Exception e) {
ts.rollback();
}
}
}
控制檯會輸出:
Hibernate: select max(did) from Department
Hibernate: insert into Department (departname, did) values (?, ?)
Hibernate: insert into Employee (ename, esex, did) values (?, ?, ?)
Hibernate: insert into Employee (ename, esex, did) values (?, ?, ?)
之所以叫它多對一,是因為他們之間的關係是由多的一方來維護的。我們根據職工Employee的資訊就能夠知道它對應的Department的資訊。
單向多對一:我們可以通過一個員工知道它屬於那個部門,而不需要知道一個部門裡 有哪些員工,這就是所謂的單向的。
可以通過儲存員工的資料的同時達到儲存部門資訊的效果,但是不能通過儲存部門資訊而儲存員工資料,這也是單向的。
多對一對映原理:在多的一端加入一個外來鍵指向一的一端,它維護的關係多指向一。
一的一方,不需要維護關係,所以和普通的配置一樣既可以了。
多的一方,需要維護雙方關係,所以裡面配置有一的一方的引用。
單向一對多
首先兩個實體類,一個部門類,一個職工類:
一個部門會對應多個員工物件,因此設定了一個員工物件的集合。這個集合為什麼要用Set型別而不用List型別的呢?因為List集合是可儲存重複的元素的,而Set中元素具有唯一性,不會去儲存重複的元素,因此這個地方我們採用Set集合
Department實體類
public class Department {
private Integer did;
private String departname;
private Set<Employee> employeeSet = new HashSet<Employee>();
//get和set方法,這裡就不貼出來了,自己引入即可
}
Employee實體類
public class Employee {
private Integer eid;
private String ename;
private String esex;
}
Employee類的對映檔案:Employee.hbm.xml
<hibernate-mapping>
<class name="org.danni.model.entity.Employee">
<!-- 設定主鍵 -->
<id name="eid" column="eid">
<!-- 設定為native之後,主鍵會按照本來的順序進行增長(比如之前23但是刪除了,現在就從24開始) -->
<generator class="native"></generator>
</id>
<!-- 設定屬性 -->
<property name="ename" column="ename"></property>
<property name="esex" column="esex"></property>
</class>
</hibernate-mapping>
Department.hbm.xml對映檔案
<hibernate-mapping>
<class name="org.danni.model.entity.Department">
<id name="did" column="did">
<!-- 設定成increment之後,會從資料庫中查詢主鍵id最大的值,然後+1進行儲存 -->
<generator class="increment"></generator>
</id>
<property name="departname" column="departname"></property>
<!--
inverse:預設為false, 指關聯關係的控制權由自己來維護
inverse設定為true的時候,就說明關聯在多的那一方被維護 (一般設定在多的一方去維護,效率會跟高一點)
-->
<set name="employeeSet" cascade="all" lazy="false">
<key column="did"></key>
<one-to-many class="org.danni.model.entity.Employee"/>
</set>
</class>
</hibernate-mapping>
One-To-Many測試類(會有問題,我們這裡先看看是什麼問題以及出現這種問題的原因是什麼,後面會說明解決辦法)
public class OneToManyTest {
private SessionFactory factory;
@Before
public void init() {
factory = SessionFactoryUtils.getInstance().openSessionFactory();
}
@Test
public void add() {
Session session = factory.openSession();
Transaction ts = session.beginTransaction();
Department d = new Department();
d.setDepartname("財務部");
Employee e1 = new Employee();
e1.setEname("呵呵");
e1.setEsex("男");
Employee e2 = new Employee();
e2.setEname("嘻嘻");
e2.setEsex("女");
d.getEmployeeSet().add(e1);
d.getEmployeeSet().add(e2);
try {
session.save(d);
ts.commit();
} catch (Exception e) {
ts.rollback();
}
}
}
控制檯輸出:
Hibernate: select max(did) from Department
Hibernate: insert into Department (departname, did) values (?, ?)
Hibernate: insert into Employee (ename, esex) values (?, ?)
Hibernate: insert into Employee (ename, esex) values (?, ?)
Hibernate: update Employee set did=? where eid=?
Hibernate: update Employee set did=? where eid=?
單向一對多:實體之間關係由”一”的一端載入”多”的一端,關係由”一”的一端來維護。也就是說在”一”的一端持有”多”的一端的集合。
我們在載入”一”的時候把”多”也載入進來了,也就是在部門中維護職工的集合。我們可以根據部門找到屬於這些部門的所有職工。但是不能根據職工找到它所屬的部門。
為什麼會有5條sql語句,進行insert之後為什麼還要update呢?
因為單向一對多關係是在”一”的一方維護資料,多的一方Employee並不知道有Department的存在。所以在儲存Employee的時候,關係欄位did是為null的。如果把did欄位設定為非空,則無法儲存資料。
因為Employee不維護關係,而是由Department來維護關係,則在對Department進行操作的時候,就會發出多餘的update語句,去維持Department與Employee的關係,這樣載入Department的時候才會把該Department對應的職工載入進來。所以會多兩條udate語句。這樣的效率是非常低的。
為了提高效率,我們一般會採用雙向一對多關聯的方式。
雙向一對多
所謂的雙向一對多關聯,同時配置單向多對一和單向一對多就可以構成雙向一對多關聯關係。它在解決單向一對多關係存在的缺陷起到了一定的修補作用。
Department實體類
public class Department {
private Integer did;
private String departname;
private Set<Employee> employeeSet = new HashSet<Employee>();
//get和set方法,這裡就不貼出來了,自己引入即可
}
Employee實體類
public class Employee {
private Integer eid;
private String ename;
private String esex;
private Department department;
}
Employee類的對映檔案:Employee.hbm.xml
<hibernate-mapping>
<class name="org.danni.model.entity.Employee">
<!-- 設定主鍵 -->
<id name="eid" column="eid">
<!-- 設定為native之後,主鍵會按照本來的順序進行增長(比如之前23但是刪除了,現在就從24開始) -->
<generator class="native"></generator>
</id>
<!-- 設定屬性 -->
<property name="ename" column="ename"></property>
<property name="esex" column="esex"></property>
<many-to-one name="department" cascade="all" column="did"
class="org.danni.model.entity.Department"></many-to-one>
</class>
</hibernate-mapping>
Department.hbm.xml對映檔案
<hibernate-mapping>
<class name="org.danni.model.entity.Department">
<id name="did" column="did">
<!-- 設定成increment之後,會從資料庫中查詢主鍵id最大的值,然後+1進行儲存 -->
<generator class="increment"></generator>
</id>
<property name="departname" column="departname"></property>
<!--
inverse:預設為false, 指關聯關係的控制權由自己來維護
inverse設定為true的時候,就說明關聯在多的那一方被維護 (一般設定在多的一方去維護,效率會跟高一點)
-->
<set name="employeeSet" cascade="all" inverse="true" lazy="false">
<key column="did"></key>
<one-to-many class="org.danni.model.entity.Employee"/>
</set>
</class>
</hibernate-mapping>
One-To-ManyDouble測試類(會有問題,我們這裡先看看是什麼問題以及出現這種問題的原因是什麼,後面會說明解決辦法)
public class OneToManyTestDouble {
private SessionFactory factory;
@Before
public void init() {
factory = SessionFactoryUtils.getInstance().openSessionFactory();
}
@Test
public void add() {
Session session = factory.openSession();
Transaction ts = session.beginTransaction();
Department d = new Department();
d.setDepartname("財務部");
Employee e1 = new Employee();
e1.setEname("呵呵");
e1.setEsex("男");
Employee e2 = new Employee();
e2.setEname("嘻嘻");
e2.setEsex("女");
d.getEmployeeSet().add(e1);
d.getEmployeeSet().add(e2);
try {
session.save(d);
ts.commit();
} catch (Exception e) {
ts.rollback();
}
}
}
控制檯輸出:可以發現只有三條sql語句,沒有那2條update語句了
Hibernate: select max(did) from Department
Hibernate: insert into Department (departname, did) values (?, ?)
Hibernate: insert into Employee (ename, esex, did) values (?, ?, ?)
Hibernate: insert into Employee (ename, esex, did) values (?, ?, ?)
只能在set上面設定inverse屬性值。不能在many-to-one上面設定。設定set標籤的inverser屬性值為true,即表示轉交控制權,把控制權轉交給多的一方,即控制權就在Employee的那一方。inverse屬性預設值是false。
因為是雙向操作,所以儲存任何一方都能成功,但是推薦儲存”一”的一方,並在”一”的一方交出控制權,這樣會提高效率
hibernate僅僅會格局主控方物件的狀態來同步更新資料庫。
只能在set上面設定inverse屬性值。many-to-one上沒有這個屬性。