六、Hibernate對映之一對多處理
環境準備
1.建立資料庫
create database hibernate_day03;
2.建立web工程,匯入相關jar包,具體jar包有哪些看這裡
3.建立實體類
以客戶和聯絡人關係為例,客戶(Customer.java)為一方,聯絡人(Linkman.java)為多方.
Customer.java
/**
* 客戶實體 (一方)
* @author mChenys
*
*/
public class Customer {
private Long cust_id;
private String cust_name;
// Hibernate框架預設的儲存多方的集合是set集合,集合必須要自己手動的初始化
private Set<Linkman> linkmans = new HashSet<>();
//get set方法...
}
Linkman.java
/**
* 聯絡人實體(多方)
* @author mChenys
*
*/
public class Linkman {
private Long lkm_id;
private String lkm_name;
//持有一方的引用(對應sql中的外來鍵),該物件不需要手動初始化
private Customer customer;
//get set方法...
}
4.編寫客戶和聯絡人的對映配置檔案
Customer.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="blog.csdn.net.mchenys.domain.Customer" table="cst_customer" >
<id name="cust_id" column="cust_id">
<generator class="native" />
</id>
<property name="cust_name" column="cust_name" />
<!-- 配置一對多對映關係 -->
<!-- set標籤name屬性:表示集合的名稱 -->
<set name="linkmans">
<!-- 外來鍵的欄位名 -->
<key column="lkm_cust_id" />
<!-- 多方的全路徑 -->
<one-to-many class="blog.csdn.net.mchenys.domain.Linkman" />
</set>
</class>
</hibernate-mapping>
Linkman.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="blog.csdn.net.mchenys.domain.Linkman" table="cst_linkman">
<id name="lkm_id" column="lkm_id">
<generator class="native" />
</id>
<property name="lkm_name" column="lkm_name" />
<!-- 配置多對一對映關係 -->
<!-- name: 多方javabean中持有一方引用的屬性名
class:一方全路徑名
colum:外來鍵名,需要和一方的對映檔案中配置的保持一致 -->
<many-to-one name="customer" class="blog.csdn.net.mchenys.domain.Customer"
column="lkm_cust_id" />
</class>
</hibernate-mapping>
5.編寫核心配置檔案
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>
<!-- 記住:先配置SessionFactory標籤,一個數據庫對應一個SessionFactory標籤 -->
<session-factory>
<!-- 必須要配置的引數有5個,4大引數,資料庫的方言 -->
<property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property>
<property name="hibernate.connection.url">jdbc:mysql:///hibernate_day03</property>
<property name="hibernate.connection.username">root</property>
<property name="hibernate.connection.password">1234</property>
<!-- 資料庫的方言 -->
<property name="hibernate.dialect">org.hibernate.dialect.MySQLDialect</property>
<!-- 可選配置 -->
<!-- 顯示SQL語句,在控制檯顯示 -->
<property name="hibernate.show_sql">true</property>
<!-- 格式化SQL語句 -->
<property name="hibernate.format_sql">true</property>
<!-- 生成資料庫的表結構
update:如果沒有表結構,建立表結構。如果存在,不會建立,可動態新增欄位(新增屬性和對映即可),不能刪除欄位
-->
<property name="hibernate.hbm2ddl.auto">update</property>
<!-- 開啟繫結本地的session -->
<property name="hibernate.current_session_context_class">thread</property>
<!-- 對映配置檔案,需要引入對映的配置檔案 -->
<mapping resource="blog/csdn/net/mchenys/domain/Customer.hbm.xml" />
<mapping resource="blog/csdn/net/mchenys/domain/Linkman.hbm.xml" />
</session-factory>
</hibernate-configuration>
6.Hibernate的工具類
/**
* Hibernate框架的工具類
* @author mChenys
*
*/
public class HibernateUtils {
private static final SessionFactory FACTORY;
private static final Configuration CONFIG;
//保證僅初始化一次
static {
// 載入XML的配置檔案
CONFIG = new Configuration().configure();
// 構造工廠
FACTORY = CONFIG.buildSessionFactory();
}
/**
* 從工廠中獲取Session物件
* @return
*/
public static Session getSession() {
return FACTORY.openSession();
}
/**
* 獲取當前執行緒的session
* @return
*/
public static Session getCurrentSession() {
return FACTORY.getCurrentSession();
}
public static void main(String[] args) {
getSession();
}
}
資料儲存處理
預設情況下,如果想只儲存其中一方的資料,程式是會拋異常的,如果想完成只儲存一方的資料,並且把相關聯的資料都儲存到資料庫中,那麼需要配置級聯,否則只能進行雙向關聯儲存(程式碼相對繁瑣)
1.雙向關聯儲存
以客戶和聯絡人關係為例,客戶和聯絡人的javabean建立完後,需要彼此關聯,也就說要擁有彼此的物件引用,通過set方法賦值,同時呼叫hibernate的save方法儲存的時候,所有建立的物件都需要通過save來進行儲存,因此這種方式其實是最繁瑣的(預設的處理方式)。
下面貼出示例程式碼:
/**
* 雙向關聯(最麻煩的儲存資料方式)
*/
@Test
public void test1() {
Session session = HibernateUtils.getCurrentSession();
Transaction tr = session.beginTransaction();
//建立客戶和聯絡人
Customer cc = new Customer();
cc.setCust_name("小陳");
Linkman tom = new Linkman();
tom.setLkm_name("tom");
Linkman jack = new Linkman();
jack.setLkm_name("jack");
//雙向關聯
cc.getLinkmans().add(tom);
cc.getLinkmans().add(jack);
tom.setCustomer(cc);
jack.setCustomer(cc);
//儲存資料
session.save(cc);
session.save(tom);
session.save(jack);
//提交
tr.commit();
}
2.級聯儲存
級聯儲存是有方向性,可以根據操作物件的頻繁性來設定哪一方開啟級聯操作,這樣儲存其中一方的同時可以把關聯的物件也自動儲存到資料庫中。當然也可以同時雙方都開啟級聯操作。
開啟級聯儲存操作,需要修改對映檔案,新增cascade="save-update
****。
例1 儲存客戶,級聯儲存聯絡人
1.修改Customer.hbm.xml檔案,在set節點上新增cascade屬性
...
<!-- set標籤cascade屬性:設定級聯操作,save-update表示允許級聯儲存-->
<set name="linkmans" cascade="save-update">
...
</set>
...
2.Hibernate儲存資料的操作
/**
* 級聯儲存之:儲存客戶,級聯儲存聯絡人
*/
@Test
public void test2() {
Session session = HibernateUtils.getCurrentSession();
Transaction tr = session.beginTransaction();
// 建立客戶和聯絡人
Customer cc = new Customer();
cc.setCust_name("小陳");
Linkman tom = new Linkman();
tom.setLkm_name("tom");
Linkman jack = new Linkman();
jack.setLkm_name("jack");
//單項關聯
cc.getLinkmans().add(tom);
cc.getLinkmans().add(jack);
//只儲存客戶資料就會自動儲存聯絡人資料
session.save(cc);
tr.commit();
}
例2 儲存聯絡人,級聯儲存客戶
1.修改Linkman.bhm.xml,在<many-to-one/>
標籤上新增cascade屬性.
...
<many-to-one name="customer" class="blog.csdn.net.mchenys.domain.Customer"
column="lkm_cust_id" cascade="save-update"/>
...
2.Hibernate儲存資料的操作
/**
* 級聯儲存之:儲存聯絡人,級聯儲存客戶
*/
@Test
public void test3() {
Session session = HibernateUtils.getCurrentSession();
Transaction tr = session.beginTransaction();
// 建立客戶和聯絡人
Customer cc = new Customer();
cc.setCust_name("小陳");
Linkman tom = new Linkman();
tom.setLkm_name("tom");
Linkman jack = new Linkman();
jack.setLkm_name("jack");
//單項關聯
tom.setCustomer(cc);
jack.setCustomer(cc);
//儲存聯絡人資料
session.save(tom);
//由於儲存tom的時候已經級聯儲存了客戶cc,因此儲存jack的時候不會在執行cc的insert操作
session.save(jack);
tr.commit();
}
例3 儲存聯絡人1,級聯儲存客戶,然後再級聯儲存聯絡人2
這個例子需要雙方表都開啟級聯操作
修改Customer.hbm.xml和Linkman.hbm.xml檔案,都新增cascade屬性,參考上面的例1和例2.
下面是程式碼實現部分.
/**
* 級聯儲存之:儲存聯絡人1級聯儲存客戶然後再級聯儲存聯絡人2
*/
@Test
public void test4() {
Session session = HibernateUtils.getCurrentSession();
Transaction tr = session.beginTransaction();
// 建立客戶和聯絡人
Customer cc = new Customer();
cc.setCust_name("小陳");
Linkman tom = new Linkman();
tom.setLkm_name("tom");
Linkman jack = new Linkman();
jack.setLkm_name("jack");
//串聯
tom.setCustomer(cc);
cc.getLinkmans().add(jack);
//儲存tom即可級聯儲存cc, 儲存cc又可以級聯儲存jack
session.save(tom);
tr.commit();
}
級聯操作的分類
cascade的取值一共有6種:
- none:不使用級聯
- save-update:級聯儲存或更新
- delete :級聯刪除
- delete-orphan:孤兒刪除.(注意:只能應用在一對多關係)
- all:除了delete-orphan的所有情況.(包含save-update delete)
- all-delete-orphan:包含了delete-orphan的所有情況.(包含save-update delete delete-orphan)
什麼是孤兒刪除?
孤兒刪除也叫孤子刪除,它只能應用在一對多的關係中,以一的一方認為是父方,以多的一方認為是子方,當刪除子方的時候除了會解除父子關係(即將外來鍵置為null),還會將子方的該條記錄直接刪除。
資料刪處理
如果直接使用sql語句操作刪除資料的話,在存在外來鍵約束的條件下,主表即一方表中的資料是刪除不成功,因為其主鍵被其他表的外來鍵所依賴,會看到類似的錯誤。
那麼Hibernate是怎麼來解決這個問題的呢?
hibernate操作delete方法的時候會先將該條資料解除從表和主表的依賴關係,通過修改從表的外來鍵值為null來實現,然後再來刪除主表中的資料。
刪除程式碼示例:
/**
* 刪除客戶,客戶下有2個聯絡人
*/
@Test
public void test5() {
Session session = HibernateUtils.getCurrentSession();
Transaction tr = session.beginTransaction();
//刪除客戶,先通過主鍵查詢
Customer cc = session.get(Customer.class, 10L);
if(null !=cc) {
//刪除
session.delete(cc);
}
tr.commit();
}
從控制檯輸出的sql語句也可以看出他的操作步驟是先修改外來鍵為null,然後再刪除Customer資料
級聯刪除
同樣需要修改hibernate的對映檔案,新增cascade的屬性,其值設定為delete或者all,通常會在一方的對映檔案中新增,因為一方一旦刪除,多方中的外來鍵也就沒啥意義了;而如果僅僅是刪除多方中的某條資料的話,一方在多方表中任然還有其他資料與之關聯,所以此時刪除一方有點不妥。
下面演示級聯刪除操作,刪除客戶級聯刪除與之關聯的聯絡人
修改Customer.hbm.xml檔案,在set節點上新增cascade屬性,其值取all
...
<!-- set標籤cascade屬性:設定級聯操作,all表示允許級聯儲存,更新和刪除-->
<set name="linkmans" cascade="all">
...
</set>
...
程式碼實現:
/**
* 級聯刪除:刪除客戶,同時刪除與之關聯的 聯絡人
*/
@Test
public void test6() {
Session session = HibernateUtils.getCurrentSession();
Transaction tr = session.beginTransaction();
//刪除客戶,先通過主鍵查詢
Customer cc = session.get(Customer.class, 11L);
if(null !=cc) {
//刪除
session.delete(cc);
}
tr.commit();
}
從控制檯輸入的sql語句,可以知道hibernate是先將外來鍵設定為null,然後再刪除從表和主表中的資料,一共執行了3條delete語句,即一個Customer和2個Linkman的資料.
注意:通常情況下,不建議直接刪除資料,而是會通過一個標記來標識該資料是否生效。
手動解除關係
解除關係並不是刪除資料,而是將從表的某條資料的外來鍵設定為null。
以Customer解除與tom聯絡人的關係為例:
/**
* 解除關係:從集合中刪除聯絡人
*/
@Test
public void test7(){
Session session = HibernateUtils.getCurrentSession();
Transaction tr = session.beginTransaction();
// 獲取到客戶
Customer cc = session.get(Customer.class, 13L);
// 獲取tom聯絡人
Linkman tom = session.get(Linkman.class, 22L);
// 解除客戶與tom聯絡人的關係,對應到資料庫就是操作外來鍵,將tom的外來鍵設定為null
cc.getLinkmans().remove(tom);
//提交
tr.commit();
}
檢視控制檯的log也能看出,並沒有執行delete語句,而是執行update語句