1. 程式人生 > >六、Hibernate對映之一對多處理

六、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語句
在這裡插入圖片描述