程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> JAVA綜合教程 >> Hibernate3 第二天,hibernate3第二天

Hibernate3 第二天,hibernate3第二天

編輯:JAVA綜合教程

Hibernate3 第二天,hibernate3第二天


Hibernate3 第二天

第一天回顧:

  • 創建數據庫
  • 准備po和hbm文件
  • 准備靈魂文件hibernate.cfg.xml
  • 1 加載配置文件Configuration
  • 2 創建會話工廠SessionFactory
  • 3 獲取連接Session
  • 4 開啟事務Transaction
  • 5 各種操作
  • 6 提交事務commit
  • 7 關閉連接close

     

     

    今天內容安排:

  • Hibernate持久化對象的狀態(3個)和轉換。
  • Session的一級緩存。
  • Session一級緩存的快照(snapshot)。
  • 一對多關聯映射配置和操作、以及級聯配置、外鍵維護配置。
  • 多對多關聯映射配置和操作。

     

    學習目標:

 

官方描述:

Hibernate 將操作的PO對象分為三種狀態:

  • 瞬時 (Transient )/ 臨時: 通常new 創建對象(持久化類),未與Session關聯,沒有OID
  • 持久 (Persistent) : 在數據庫存在對應實例,擁有持久化標識OID, 與Session關聯(受session管理)
  • 脫管 (Detached)/游離:當Session關閉後,持久狀態對象與Session斷開關聯,稱為脫管對象,此時也持有OID

 

搭建測試環境:創建項目Hibernate3_day02

<?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>

    <!-- 配置java類與數據表的對應關系

     name:java類名

         table:表名

     -->

    <class name="cn.itcast.a_postate.Customer" table="t_customer">

        <!-- 配置主鍵 -->

        <id name="id">

            <generator class="native"></generator>

        </id>

        <!-- 配置其他屬性 -->

        <property name="name"></property>

        <property name="age"></property>

    </class>

</hibernate-mapping>

 

5 創建TestPOState類,描述對象的三狀態。

在類中編寫測試testSave方法:代碼如下:

@Test

    public void testSave(){

        //瞬時態:

        //特點:沒有OID,new出來的一個對象,不與session關聯,不受session管理,數據庫中沒有對應的記錄

        Customer customer = new Customer();

        customer.setName("rose");

        customer.setAge(18);

        System.out.println(customer);

        

        Session session = HibernateUtils.openSession();

        session.beginTransaction();

        

        //在save執行之前,customer都只是瞬時態

        //當save執行之後,customer就處於持久態

        session.save(customer);

        //持久態

//        /特點:有OID,數據庫存在對應的記錄,與session關聯,受session管理

        System.out.println(customer);

        session.getTransaction().commit();

        session.close();

        

        //只要session已關閉,那麼久處於脫管態

        //有OID,數據庫中存在對應的記錄,但是不與session關聯,不受session管理

        System.out.println(customer);

        

    }

 

 

三種狀態的區別分析:

  • 持久態和瞬時態、脫管態:容易區別,持久態主要特點是與Session關聯,而且數據庫中有對應記錄、擁有OID,因此,只要與session關聯的就是持久態。

瞬時態和脫管態:相同點是都和Session沒關聯,不同點是瞬時態沒有OID,而脫管態有OID。

 

【思考】

通過上述的分析,發現,瞬時態和脫管態對象就差一個OID,那麼瞬時態的對象中給主鍵ID屬性賦值後就是脫管態了麼?

未必!

首先,需要區分持久化標識OID和對象中主鍵ID屬性的關系:

  • 在持久化之前,雖然有ID屬性,但數據庫中沒有對應的數據,那麼此時OID是null;
  • 在持久化之後,這個ID屬性值被插入數據庫中當主鍵了,數據庫中有對應的數據了,此時OID就有值了,而且與主鍵值保持一致性,比如類型、長度等。

因此:OID和PO對象中主鍵ID屬性的區別就是:數據庫存在不存在,如果存在就是OID,如果不存在,那就是個ID屬性而已。

 

瞬時態和脫管態的區別總結:

  • 脫管態對象:有持久化的標識oid,並且在數據庫中存在。
  • 瞬時態對象:無持久化標識oid,或者有id但在數據庫中不存在的。

 

例如:

Customer對象具有Id屬性值,如果數據庫中不存在,則該對象還是瞬時態對象,如果數據庫中存在,則認為是脫管態的。

 

【三者的區別最終總結】:

對於三者:在session中存在的,就是持久化對象,不存在的就是瞬時或脫管對象。

對於瞬時和脫管對象:有oid(持久化標識)的就脫管對象,沒有的就是瞬時對象。

OID一定是與數據庫主鍵一一對應的

 

是否有持久化標識OID

session是否存在

數據庫中是否有

瞬時態對象-臨時狀態

n

n

n

持久態對象

y

y

y/(n:沒有提交)

脫管態對象-游離

y

n

y

 

持久化對象狀態轉換圖(官方規范):

 

 

 

【分析】:(各狀態對象的獲取方式以及不同狀態之間轉換的方法介紹):

  • 瞬時對象:

    如何直接獲得 --- new 出來

    轉換到持久態 ---- save、saveOrUpdate 保存操作

    轉換到脫管態 ---- setId 設置OID持久化標識(這個id是數據庫中存在的)

  • 持久對象

    如何直接獲得 ---- 通過session查詢方法獲得 get、load、createQuery、createSQLQuery、createCriteria

    轉換到瞬時態 ---- delete 刪除操作 (數據表不存在對應記錄 )(其實還有id,只是不叫OID)

    轉換到脫管態 ---- close 關閉Session, evict、clear 從Session清除對象

  • 脫管對象

    如何直接獲得 ----- 無法直接獲得 ,必須通過瞬時對象、持久對象轉換獲得

    轉換到瞬時態 ---- 將id設置為 null,或者手動將數據庫的對應的數據刪掉或者將id修改成數據庫中不存在的

    轉換到持久態 ---- update、saveOrUpdate、lock (對象重新放入Session ,重新與session關聯)

 

在Hibernate所有的操作只認OID,如果兩個對象的OID一致,它就直接認為是同一個對象。

 

又稱為:hibernate一級緩存、session緩存、session一級緩存

 

  • 緩存的概念:在內存上存儲一些數據

緩存的介質是一般是內存(硬盤),用來存放數據,當第一次查詢數據庫的時候,將數據放入緩存,當第二次繼續使用這些數據的時候,就不需要要查詢數據庫了,直接從緩存獲取,

 

 

  • 一級緩存概念:

在Session接口中實現了一系列的java集合,這些java集合構成了Session的緩存,只要Session的生命周期沒有結束,session中的數據也就不會被清空。

 

  • 緩存作用:

將數據緩存到內存或者硬盤上,訪問這些數據,直接從內存或硬盤加載數據,無需到數據庫查詢。

好處: 快! 降低數據庫壓力。

 

  • 一級緩存的生命周期:

Session中對象集合(map),在Session創建時,對象集合(map)也就創建,緩存保存了Session對象數據,當Session銷毀後,集合(map)銷毀, 一級緩存釋放 !

  • 什麼對象會被放入一級緩存?

只要是持久態對象,都會保存在一級緩存 (與session關聯的本質,就是將對象放入了一級緩存)

 

一級緩存的作用:第一次get/load的時候,肯定會發出sql語句,查詢數據庫,(此時會將數據放入一級緩存),只要session不關閉,

第二次get/load的時候,直接從緩存中讀取數據,不會發出sql語句,查詢數據庫(這裡的數據指的是同一條記錄:OID相等)

 

【示例】證明一級緩存的存在性!

通過多次查詢同一個po對象數據,得到同一個對象,且第二次不再從數據庫查詢,證明一級緩存存在。

@Test

    public void testFirstCacheExist(){

        /**

         * 證明一級緩存的存在性

         * 證明思路:

         * 緩存的作用就是用來少查數據庫的,提高訪問速度

         * 第一步,通過get/load查詢數據,由於是第一次查詢,所以必然發出sql語句,查詢數據庫

         * 第二步,不關閉session,繼續使用當前的session去get/load數據,觀察是否發出sql語句

         * 如果不發出,表明是從一級緩存取出的數據

         */

        Session session = HibernateUtils.openSession();

        session.beginTransaction();

        

        //第一步:此時必然發出sql語句,然後自動將數據放入一級緩存

        Customer customer = (Customer) session.get(Customer.class, 1);

        System.out.println(customer);

        

        //第二步:此時不會發出sql語句,直接從一級緩存獲取數據

        Customer customer2 = (Customer) session.get(Customer.class, 1);

        System.out.println(customer2);

        

        session.getTransaction().commit();

        session.close();

    }

 

測試(同一個對象):

一級緩存的生命周期就是session的生命周期,不能跨Session,可以說,一級緩存和session共存亡!

【示例】

使用兩個不同Session來測試生命周期。(一級緩存和session共存亡)

@Test

    public void testFirstCachelifecycle(){

        /**

         * 一級緩存的聲明周期:與session同生命共存亡

         * 如何證明一級緩存的生命周期?

         * 只要證明數據不能跨session

         * 1 獲取session1,通過session1拿到customer對象,此時必然發出sql語句,關閉session1

         * 2 獲取session2,通過session2繼續抓取customer對象,觀察第二次是否發出sql語句

         * 如果發出,,表名session1銷毀的時候,把數據也銷毀了

         */

        Session session1 = HibernateUtils.openSession();

        session1.beginTransaction();

        

        //此時必然發出sql語句,因為是第一次查詢

        Customer customer = (Customer)session1.get(Customer.class, 1);

        

        System.out.println(customer);

        

//        此處如果需要查詢Customer,會發sql語句嗎?答:不會,直接走一級緩存

        //也能證明數據成功存入了一級緩存

        Customer customer2 = (Customer)session1.get(Customer.class, 1);

        System.out.println(customer2);

        

        session1.getTransaction().commit();

        session1.close();

        

        /**********第二次*********/

        Session session2 = HibernateUtils.openSession();

        session2.beginTransaction();

        

        //此時發sql語句嗎?答:發,因為session1中的數據跟隨session1一起銷毀了

        Customer customer3 = (Customer)session2.get(Customer.class, 1);

        System.out.println(customer3);

        

        session2.getTransaction().commit();

        session2.close();

        

        

    }

 

測試:

 

小結:緩存的作用,可以提高性能,減少數據庫查詢的頻率。

[補充:原則]所有通過hibernate操作(session操作)的對象都經過一級緩存。

一級緩存是無法關閉的!內置的!hibernate自己維護的!

 

什麼是快照?

答:快照,是數據在內存中的副本,是數據庫中數據在內存中的映射。

 

如:

一句話:

快照跟數據庫數據保持一致

快照的作用就是用來更新數據的。

 

采用快照技術進行更新,不需要手動的調用update 方法,完全是自動的發出update語句。

保正一級緩存、數據庫、快照的一致性

【注意】

 

一級緩存的快照是由Hibernate來維護的,用戶可以更改一級緩存(PO對象的屬性值),但無法手動更改快照!

快照的主要能力(作用)是:用來更新數據!當刷出緩存的時候,如果一級緩存和快照不一致,則更新數據庫數據。

【示例】

通過改變查詢出來的PO的屬性值,來查看一級緩存的更改;通過提交事務,來使用快照更新數據。

@Test

    public void testSnapShot(){

        /**

         * 證明快照的能力:自動更新數據

         * 1 從數據庫查找一個對象,改變對象的某個值,手動的flush,看控制台是否發出sql語句,

         * 如果控制台發出update語句,就可以表明快照的能力

         */

        Session session = HibernateUtils.openSession();

        session.beginTransaction();

        //get的時候,不光將數據放入一級緩存,還同時將數據同步到了快照中

        Customer customer = (Customer)session.get(Customer.class, 1);    

        

        System.out.println(customer);

        //修改customer對象的值

        customer.setName("lucy");

        

        //這個值是改變過後的值,是內存中的值

        System.out.println(customer);

        

        //提交事務

        //如果不手動flush,在事務commit的時候,會先flush,在commit

        session.getTransaction().commit();

        session.close();

        

        

    }

    

 

 

【能力擴展】快照可以用來更新數據,而且,可以用來更新部分數據。

 

【問題】update也是更新數據,快照也是更新數據?兩個有什麼區別?

Update更新的時候,會將所有值都更新,如果有某個屬性沒有賦值,值將會被置空

 

快照符合我們修改的要求:先查後改

 

 

 

什麼叫刷出緩存?

Session能夠在某些時間點,按照緩存中對象的變化來執行相關的SQL語句,來同步更新數據庫,這一過程被成為刷出緩存(flush)。

 

通俗的說法:將一級緩存的數據同步到數據庫,就是刷出緩存

 

 

什麼情況下session會執行 flush操作?

 

刷新緩存的三個時機:

  • 事務提交commit():該方法先刷出緩存(session.flush()),然後再向數據庫提交事務。
  • 手動刷出flush():直接調用session.flush()。
  • 查詢操作:當Query查詢(get、load除外,這兩個會優先從一級緩存獲取數據)時,會去比較一級緩存和快照,如果數據一致,則去數據庫直接獲取數據,如果緩存中持久化對象的屬性已經發生了變化,(一級緩存和快照不一樣了),則先刷出緩存,發出update語句,然後查詢,以保證查詢結果能夠反映出持久化對象的最新狀態。(Query查詢數據不走一級緩存)

 

【補充理解】:

關於Hibernate如何識別同一個對象?

根據OID,

問題:假如先查詢出來一個對象oid是1001,數據庫主鍵也是1001,但其他字段的屬性不一樣,那麼,再次查詢數據庫的數據出來的對象,和原來的對象是否是一個對象?

答案:是!

 

【示例】

1 、通過commit 的方式隱式的刷出緩存(證明略)

 

2 、通過flush的方式手動的刷出緩存

//采用flush的方式手動的刷出緩存

    @Test

    public void testflushcache2()

    {

        Session session = HibernateUtils.openSession();

        // 開啟 事務

        session.beginTransaction();

        

        //獲取數據

        Customer customer = (Customer)session.get(Customer.class, 1);

        

        System.out.println(customer);

          

        

        customer.setName("rose");

        

        //手動的flush,發出update語句,更新數據庫,並且同時更新快照

        session.flush();

        

        session.getTransaction().commit();

        //特點:在數據庫中存在對應的記錄,有OID,但是不受session管理

        session.close();

        

    }

 

 

 

3 、使用Query的時候,(不包含get、laod:原因:get和load的處理方式,是直接獲取緩存的數據,即使一級緩存和快照的數據不一致)

會去比較一級緩存和快照是否一致,如果一致,他直接去查詢(1條select語句)

當不一致了,會先發出update語句,更新數據庫,然後在查詢(1條update語句,1條select語句)

 

3.1 測試get和load 的處理方式

@Test

    public void testGetAndLoad_Cache(){

        /**

         * 證明get和load優先從緩存取數據,哪怕一級緩存和快照的數據不一致,

         * 它也是直接取緩存數據

         * 證明思路:

         * 第一步,將數據放入一級緩存和快照

         * 第二步,取 ,觀察是否發出sql語句和數據

         * 第三步,改

         * 第四部,取 ,觀察是否發出sql語句和數據

         *

         */

        Session session = HibernateUtils.openSession();

        session.beginTransaction();

        

        // 1 取,並且放入一級緩存和快照

        Customer customer1 = (Customer) session.get(Customer.class, 1);

        System.out.println(customer1);

        //2 取:肯定不發sql語句,直接從緩存取

        Customer customer2 = (Customer) session.get(Customer.class, 1);

        System.out.println(customer2);

        //3 改:

        customer2.setName("tom");

        //4 取,雖然此時一級緩存和快照不一致,但是get/load也是直接抓取緩存數據

        Customer customer4 = (Customer) session.get(Customer.class, 1);

        System.out.println(customer4);

        

        //隱式flush

        session.getTransaction().commit();

        session.close();

    }

    

 

 

3.2 測試query的工作方式:

【證明1】query對象不走一級緩存

 

    //query不走一級緩存

    @Test

    public void testQuery_cache(){

//        證明query不使用session緩存的數據,即使緩存中有,它也會發出sql語句,查詢數據庫

        Session session = HibernateUtils.openSession();

        session.beginTransaction();

        

//        /此處也會發出sql語句

        Customer customer = (Customer)session.get(Customer.class, 1);

        System.out.println(customer);

        

        //此時必須發出sql語句,因為它不會直接從一級緩存中拿數據

        Customer customer2 = (Customer)session.createQuery("from Customer where id = 1").uniqueResult();

        System.out.println(customer2);

          

        

        session.getTransaction().commit();

        session.close();

    }

 

【證明2】但是Query對象在查詢數據的時候,會去校驗一級緩存和快照的數據是否一致,

如果不一致,發出update語句,更新數據庫,然後再發出sql查詢語句

//證明2:query對象雖然不從一級緩存取數據,但是在它去數據庫查找數據之前,會干這麼一件事情:

    // 會比較一級緩存和快照是否一致,

    // 如果一致,直接去數據庫查找需要的數據

    // 如果不一致,先flush(先發出update語句),再去數據庫查找需要的數據

    @Test

    public void testQuery2(){

        Session session = HibernateUtils.openSession();

        session.beginTransaction();

        

        Customer customer1 = (Customer) session.get(Customer.class, 1);

        

        System.out.println(customer1);

        

        customer1.setName("tom");

        

        Customer customer2 = (Customer) session.createQuery("from Customer where id = 1").uniqueResult();

        

        System.out.println(customer2);

        

        session.getTransaction().commit();

        session.close();

    }

    

 

 

【提示】

flush和commit的區別:

  • flush是發語句的。
  • commit的是數據庫層面的是否保存更改的數據(是否提交數據,是否持久化數據到數據庫),若不手動發出flush, hibernate在commit之前自動先flush();

 

 

問題:能否改變一級緩存的刷出時機?答案是可以的.

【示例】

通過在session上設置手動flush模式測試:只能通過flush刷出緩存

@Test

    public void testFlushMode(){

        Session session = HibernateUtils.openSession();

        session.beginTransaction();

        // 1 第一步證明:先不設置FlushMode,看運行結果

        Customer customer = (Customer)session.get(Customer.class, 1);

        //改變緩存中對象的屬性

        customer.setName("lucy");

          

        

        // 2 第二證明:設置FlushMode,看運行結果

        //這個一旦設置,只有手動flush的時候,才會發出update語句

        session.setFlushMode(FlushMode.MANUAL);

        //第二種方式,手動flush,發出update語句

        session.flush();

        

        //在第一種情況下,此處必然發出update語句

        //在第二種情況下,此處必然不會發出update語句

        session.getTransaction().commit();

        session.close();

        

    }

 

操作一級緩存中的對象

 

一級緩存除了可以flush之外,還可以清除(clear,evict)、重載(refresh)。

 

  • flush:刷出一級緩存

作用:當一級緩存發生變化時,即和快照不同時,刷出一級緩存,會自動向數據庫提交update語句。

  • clear:清除一級緩存中所有的數據

作用:清除一級緩存中的所有對象,這些對象被清除後,會從持久態對象變成脫管態.

【擴展理解】

持久態對象與session關聯的另一層含義就是對象在一級緩存中存在。

  • evict:清除一級緩存指定對象

作用:清除一級緩存中的指定對象,使對象變成脫管態的。

  • refresh:刷新一級緩存

    通俗的講:不管內存中對象是否發生了更新,重新將數據庫中的內容加載到緩存中,覆蓋原來的值

     

 

@Test

    public void testClearAndEvict(){

        Session session = HibernateUtils.openSession();

        session.beginTransaction();

        //此時數據會被放入一級緩存

        Customer customer = (Customer)session.get(Customer.class, 1);

        System.out.println(customer);

        

        //在第二次獲取之前插入代碼:clear 一級緩存

        //clear:清除一級緩存中的所有數據

//        session.clear();

        //evict:清除一級緩存的指定對象,主要清除的是OID=1的這個對象

        session.evict(customer);

          

        

        //由於第一次get的時候,已經發出了sql語句查詢數據庫,所以,第二次get的時候就不會發sql語句

        //如果我們執行了session.clear()代碼,表示一級緩存數據被清空了,那麼這次獲取的時候

        //還是要繼續發出sql語句的

        Customer customer2 = (Customer)session.get(Customer.class, 1);

        

        System.out.println(customer2);

        session.getTransaction().commit();

        session.close();

    }

    

 

//refresh: 不管緩存中的數據是否發生了改變,將數據庫中的數據重新放入緩存

    //如果緩存的數據發生了改變,那麼這個改變將會被覆蓋

    @Test

    public void testRefresh(){

        Session session = HibernateUtils.openSession();

        session.beginTransaction();

        

        Customer customer = (Customer)session.get(Customer.class, 1);

        customer.setName("rose");

        

        System.out.println(customer);

        //從數據庫中根據OID重新加載這條記錄,原先的改變失效,數據庫中原始的值會覆蓋修改的值

        session.refresh(customer);

        

        System.out.println(customer);

        

        session.getTransaction().commit();

        session.close();

    }

 

【面試題】請你說說session的flush和refresh的區別?

 

為什麼要清除一級緩存:

大批量處理數據的時候,有些數據無需在一級緩存存在,或者已經處理完了,為了防止內存洩漏,一級緩存爆滿,可以手動清除一級緩存的對象。

【狀態變化】直接拿到持久態對象。

 

【理解 session的get方法和load方法區別】

兩者的區別:

get()方法是立即加載,即執行get方法後,立即發出查詢語句進行查詢,直接返回目標對象。

load()方法是延遲加載,即執行load方法後,不會立即發出查詢語句,返回具有id的目標對象代理類子對象,再訪問對象的除了ID之外的其他屬性的時候,才發出SQL語句進行查詢。

【示例】

分別用get和load,來根據不同id查詢不同對象,使用debug來查看語句發出的時機以及延遲加載時的代理類子對象。

@Test

    public void testGetAndLoad(){

        Session session = HibernateUtils.openSession();

        session.beginTransaction();

        

        //比較get和load的區別

        //1 觀察get和load的sql語句發出時機

        

        //get方法立即發出sql語句

//        Customer customer = (Customer) session.get(Customer.class, 1);

//        System.out.println(customer);

        

        //load方法:屬於懶加載,如果只用到id屬性,是不會發出sql查詢語句的

        //只有用到id以外的其他屬性的時候,才會發出sql查詢語句

        Customer customer = (Customer) session.load(Customer.class, 1);

        //簡單的打印id,發出sql語句嗎?

        System.out.println(customer.getId());

        //發出sql語句嗎?

        System.out.println(customer.getName());

        

        session.getTransaction().commit();

        session.close();

    }                

 

C2引用的Customer的代理子對象

繼續調試

初始化後,內部handler中initialized變為true (已經初始化), target 指向真正查詢結果對象

 

 

【關於load延遲加載的幾個情況提示】

  • 代理子對象,handler屬性,包含了主鍵id,因此只訪問id時,不需要發出sql語句。

【示例】

延遲加載時,訪問ID屬性不發出sql,訪問ID之外的屬性時發出sql

//load:不會立即發出sql,只有在訪問除id之外的其他屬性的時候,才發出sql

        Customer customer2 =(Customer) session.load(Customer.class, 2);

        System.out.println(customer2.getId());//不需要發出sql

        System.out.println(customer2.getName());//此時才發出sql

 

  • 當id在數據庫中不存在的時候,訪問其他屬性時會發生 ObjectNotFoundException。

【示例】

兩者查不到數據的區別:

結論:get會返回null,load會報錯:

 

【擴展了解】

代理子對象是誰來負責生成的?

答:通過javassist.jar來進行生成子對象(反射機制)

 

延遲加載的好處:節約資源,需要的時候再加載(提高內存利用率),如果不需要的話,先不加載。

 

 

多對多: 學生選課 (一個學生選多門課, 一門課被多個學生選擇)

一對多: 客戶和訂單 (一個客戶可以產生很多訂單, 一個訂單屬於一個客戶)

一對一: 一個公司對應建表規則

多對多: 一定產生第三方關系表, 需要三張表 (學生表、 課程表、 選課表), 關系表聯合主鍵,引入兩張實體表主鍵,作為外鍵

一對多: 在多方表,添加一方主鍵作為外鍵 ,不需要第三張表 , 需兩張表(客戶表 、訂單表),在訂單表添加客戶id

    一對一: 在任意一方添加對方主鍵作為外鍵

一個地址

范式:可以理解成是數據庫設計的規范/標准

為什麼表的設計需要有規范和標准?

防止數據冗余,科學的數據庫設計可以防止數據冗余

 

//一般數據庫的設計要符合3NF,符合的范式要求越高,表越多

范式之間的關系:

 

第一范式(1NF):保證每列的原子性(每列都是不可再分割的單元)

學生表

stuno

stuinfo

Coursename

1

Lucy23

Java

1

Tom18

Oracle

1

Rose12

Hibernate

    上表是不符合第一范式的,修改如下

stuno

Name

Age

Coursename

1

Lucy

23

Java

1

Tom

18

Oracle

1

Rose

12

Hibernate

 

第二范式:保證表有主鍵

上表符合第二范式嗎?不符合,因為沒有主鍵,修改如下:

stuno

Name

Age

Coursename

1

Lucy

23

Java

2

Tom

18

Oracle

3

Rose

12

Hibernate

 

第三范式:每張表不包含其他表中非主鍵以外的字段(每張表的字段都依賴於當前的主鍵)

上表符合第三范式嗎?答:不符合,修改如下

stuno

Name

Age

1

Lucy

23

2

Tom

18

3

Rose

12

 

Courseid

Coursename

1

Java

2

Oracle

3

Hibernate

 

關系表:

Stuno

Courseid

1

1

1

2

2

1

2

3

  

 

BCNF

4NF

5NF

 

Hibernate 是一個完全ORM框架,使用hibernate 編程,可以完成類和表映射

多對多 :

    Student {

        // 一個學生對應 多門課

        // Set、List、bag、數組 代表復數,基本區別:set不能重復,沒順序、list可以重復,有順序。bag:不能重復,而且有順序(缺點:效率低)

        Set<Cource> cources ;

}

    Cource {

        // 一門課,多個學生

        Set<Student> students ;

    }

 

一對多 :

    Customer {

        // 一個客戶 多個訂單

        Set<Order> orders ;

}

    Order {

        // 一個訂單 一個客戶

        Customer customer ;

    }

一對一:

    Company {

        // 一個公司 一個地址

        Address address;

    }

    Address {

        // 一個地址 對應一個公司

        Company company ;

    }

 

我們下面重點學習一對多和多對多!

 

 

案例:客戶和訂單(一對多)

建立一個包用於測試: cn.itcast.b_oneToMany

 

編寫方法:實體類的編寫,先寫單表的屬性和配置,再加關系。

 

【第一步】:實體類編寫

 

【第二步】:hbm映射文件編寫

Order.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="cn.itcast.b_onetomany.Order" table="t_order">

        <!-- 主鍵 -->

        <id name="id">

            <generator class="native"></generator>

        </id>

        <!-- 其他屬性 -->    

        <property name="name"></property>

        

        <!-- 配置關系

             name:類中屬性

             class:這個屬性的原型(屬性對應對象的完整的包路徑)

             column:customer在Order表中的外鍵的名字

         -->

        <many-to-one name="customer" class="cn.itcast.b_onetomany.Customer" column="cid"></many-to-one>

        

        

    </class>

 

</hibernate-mapping>

 

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 -->

    <class name="cn.itcast.b_onetomany.Customer" table="t_customer">

        <!-- 配置主鍵 -->

        <id name="id">

            <!-- 主鍵策略 -->

            <generator class="native"></generator>

        </id>

        <!-- 其他屬性 -->

        <property name="name"></property>

        

        <!-- 配置集合

             name:類中的屬性名

         -->

        <set name="orders">

            <!-- 配置當前Customer對象在order表中的外鍵的名字 -->

            <key column="cid"></key>

            <!-- 配置關系

                class:當前order對應的完整的包路徑(集合中裝載的數據的原型)

            -->

            <one-to-many class="cn.itcast.b_onetomany.Order"/>

        </set>

        

    </class>

 

 

</hibernate-mapping>

 

注意:兩個配置文件的外鍵必須對應!!!!!

 

【第三步】:核心配置文件中添加映射

<!-- 配置一對多的映射文件 -->

        <mapping resource="cn/itcast/b_onetomany/Customer.hbm.xml"/>

        <mapping resource="cn/itcast/b_onetomany/Order.hbm.xml"/>

 

 

【第四步】:建表測試

 

    @Test

    public void createTable()

    {

        HibernateUtils.getSessionFactory();

    }

建表成功:

 

提示:外鍵的數據類型自動會使用對方主鍵的類型

 

 

本節難點:cascade級聯和inverse外鍵維護

多表保存的原則:雙方都必須是持久態的對象!

 

目標:學習幾種保存方法。

    @Test

    public void testSave(){

        Session session = HibernateUtils.openSession();

        session.beginTransaction();

        

        Customer c1 = new Customer();

        c1.setName("rose");

        

        Order o1 = new Order();

        o1.setName(c1.getName()+"的訂單1");

        

        //雙向建立關系

        c1.getOrders().add(o1);

        o1.setCustomer(c1);

        

        //必須同時保存兩個對象,否則會報錯

        session.save(c1);

        session.save(o1);

        

        session.getTransaction().commit();

        session.close();

    }

    

 

這種保存要求:必須雙方都建立關系,而且都要執行保存操作.

 

【需求】

保存客戶的同時自動保存訂單。

默認情況下:

會報錯:

原因結論: 在hibernate代碼中,在session.flush前,不允許 持久態對象 關聯 瞬時態對象

持久態對象只能關聯持久態對象!

解決:采用級聯 ,cascade :cascade="save-update"

它的作用:

可以使持久態對象"關聯"瞬時態對象, 自動會隱式執行save操作,變為持久態,

可以使持久態對象"關聯"脫管對象,自動會隱式執行update操作,變為持久態

 

如果通過操作customer來級聯保存 order ,需要在Customer.hbm.xml(誰是持久的) 配置級聯.

Customer.hbm.xml:

測試代碼:

@Test

    public void testSave(){

        Session session = HibernateUtils.openSession();

        session.beginTransaction();

        

        Customer c1 = new Customer();

        c1.setName("lucy");

        

        Order o1 = new Order();

        o1.setName(c1.getName()+"的訂單1");

        

        Order o2= new Order();

        o2.setName(c1.getName()+"的訂單2");

        

        Order o3 = new Order();

        o3.setName(c1.getName()+"的訂單3");

        

        Order o4 = new Order();

        o4.setName(c1.getName()+"的訂單4");

          

        

        //當級聯保存的時候,當我們在Customer.hbm.xml中設置了級聯關系的時候,

        //那麼在設置關系的時候,只需要向Customer的orders集合中添加Order,就可以進行級聯保存

        c1.getOrders().add(o1);

        c1.getOrders().add(o2);

        c1.getOrders().add(o3);

        c1.getOrders().add(o4);

        

        

//        o1.setCustomer(c1);

        

        //必須同時保存兩個對象,否則會報錯

        session.save(c1);

//        session.save(o1);

        

        session.getTransaction().commit();

        session.close();

    }

 

使用級聯之後,Hibernate會對瞬時態的這個對象,會自動執行save操作.

 

 

問題:如果保存順序反過來呢?即先保存訂單,同時保存客戶呢?
分析:先操作order ,級聯保存 customer ,需要在 Order.hbm.xml 配置級聯

 

Order.hbm.xml :


//保存訂單的時候,能不能自動的保存customer

    @Test

    public void testSave3(){

        

        Session session = HibernateUtils.openSession();

        session.beginTransaction();

        

        Customer c1 = new Customer();

        c1.setName("jack");

        

        Order o1 = new Order();

        o1.setName(c1.getName()+"的訂單1");

        

        o1.setCustomer(c1);

        

        session.save(o1);

        

        session.getTransaction().commit();

        session.close();

        

    }

 

級聯對於大量的保存或更新操作非常有用。

 

 

下面有個面試題,前提:customer和order都配置了級聯保存(雙向都配置),那麼請問下面的1,2,3語句分別產生幾條插入的sql語句,

答案: 4 3 1

@Test

    public void testSaveByCascadeNavi(){

        Session session = HibernateUtils.openSession();

        session.beginTransaction();

        

        Customer c1 = new Customer();

        c1.setName("itcast2");

        

        Order o1 = new Order();

        o1.setName(c1.getName()+"的訂單1");

        

        Order o2 = new Order();

        o2.setName(c1.getName()+"的訂單1");

        

        Order o3 = new Order();

        o3.setName(c1.getName()+"的訂單1");

          

        

        o1.setCustomer(c1);

        c1.getOrders().add(o2);

        c1.getOrders().add(o3);

        //第一種情況 4

        session.save(o1);

        //第2種情況 3

        session.save(c1);

        //第3種情況 1

        session.save(o2);

        

        session.getTransaction().commit();

        session.close();

    }

 

Hibernate的外鍵:是由關系來提供

導航保存對於大量的保存或更新操作非常有用。

 

 

表之間的依賴關系:在一對多中 ,多方表(從表) 依賴 一方表(主表) (order依賴customer)。

 

 

        // 第一種情況:直接刪除多方的數據

//        Order order = new Order();

//        order.setId(7);

//        

//        session.delete(order);

        

刪除多方 (訂單),直接刪除

 

//第二種情況:直接刪除一方的數據

        Customer customer = new Customer();

        customer.setId(4);

        //當customer是一個脫管態對象的時候,先解除關系,刪除,所以刪除之後,你會發現多方的外鍵被置空

        session.delete(customer);

刪除一方(客戶),被依賴

結果:多方的外鍵被置空了,一方被刪除了(內部機制)

 

原理:Hibernate 先解除對一方外鍵依賴,然後進行刪除

(如果外鍵設置 not-null , 無法刪除 )課後可以試試

 

問題: 從關系型數據庫的角度來說:在一對多數據模型中,多方對一方存在依賴的, 如果一方數據被刪除,多方數據不完整,無意義。(客戶刪除的同時,將訂單一塊都刪了)

解決方法: 在一方 配置 cascade="delete" ,會級聯刪除.

級聯刪除的要求:被刪除的對象要是持久態的對象

 

    @Test

    public void testDelete(){

        Session session = HibernateUtils.openSession();

        session.beginTransaction();

        

        //刪除多方的數據:直接刪除

//        Order order = new Order();

//        order.setId(15);//設置oid,刪除只能根據id刪除

        

        // 刪除

//        session.delete(order);

        

        //刪除一方的數據

//        Customer customer = new Customer();

//        customer.setId(7);

        //當刪除的時候,hibernate會先解除關系(將cid置空),然後在進行一方的刪除操作

//        session.delete(customer);

        

        /**

         * 在進行級聯刪除的時候,如果刪除的對象還是脫管態對象,級聯刪除失效,

         * 默認處理方式:先解除關系,再一方刪除數據(多方數據還存在,並沒有達到級聯刪除的效果)

         *

         * 級聯刪除:一方對象必須是持久態,這樣子,才能實現級聯刪除的效果

         */

        //持久態

        Customer customer = (Customer)session.get(Customer.class, 6);

        //由於customer對象是持久態,所以會級聯刪除order訂單

        session.delete(customer);

        

        session.getTransaction().commit();

        session.close();

        

    }

 

結論:刪除操作中,刪除托管對象沒有級聯效果,刪除持久對象可以進行級聯刪除

 

 

Hibernate級聯開發配置, cascade常用的取值:

  • save-update:對關聯瞬時對象執行save操作,對關聯托管對象執行update
  • delete:對關聯對象進行刪除

 

在實際開發中,一對多模型中,一方一般是主動的一方(多方要依賴一方),如果配置級聯,通常在一方進行配置!!!

因為多方需要引用一方的主鍵作為外鍵使用

 

級聯刪除的情況:刪除客戶,訂單也已經沒有存在的意義了

刪除訂單,沒有必要刪除客戶,沒有必要再多方配刪除級聯

 

    實際項目開發中,一般級聯主要是用來進行級聯刪除操作,很少用來進行級聯保存。一般都是先有一方的數據,再有多方的數據,即先有客戶,再有訂單。所以,保存多方不配置級聯(不在多方配置級聯)。

    

 

總結:

一般在業務開發中,不要兩端都配置級聯,(多方盡量不要配置級聯,盡量在一方配置級聯)

配置了級聯之後,必須操作持久態對象,否則不會級聯刪除。

 

問題:多余sql的問題

示例:

將沒有關系的一個客戶和一個訂單建立關系。(雙方)

@Test

    public void testInverse(){

        

        Session session = HibernateUtils.openSession();

        session.beginTransaction();

        

//        獲取5號客戶

        Customer customer = (Customer)session.get(Customer.class, 5);

//        獲取訂單

        Order order = (Order)session.get(Order.class, 11);

        

        //添加雙向關聯關系

        customer.getOrders().add(order);

        order.setCustomer(customer);

        

        //此時當commit的時候,會隱式的flush

        

        session.getTransaction().commit();

        session.close();

    }

產生的sql:多余sql

 

分析圖:

解決方法:

采用inverse屬性來配置。

簡單的說這個屬性誰是true,就放棄了主鍵維護權。

inverse默認值是false,即雙方都有外鍵維護權。

inverse只對集合起作用,也就是只對one-to-many或many-to-many有效.

在業務開發中,一般是在一方放棄。(從業務場景上來分析,一般先存1 方,再存多方,那麼就存多方的時候建立關系就比較合理。)

再次運行,發現就一條語句了:

一般,我們都讓1方放棄外鍵維護權!

學習點:掌握多對多配置方式。

案例:學生和課程,學生選課

學生和課程 是經典 多對多關系,學生選課是關系表數據

    在多對多中,一般情況 沒必要使用 cascade 級聯的!!!

 

【第一步】:實體類編寫,創建cn.itcast.c_manytomany包,然後操作如下:

 

 

【第二步】:hbm映射文件編寫

student.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="cn.itcast.c_manytomany.Student" table="t_student">

        <id name="id">

            <generator class="native"></generator>

        </id>

        <property name="name"></property>

      

        <!-- 配置集合

             name:類中的屬性

             table:關系表名

         -->

        <set name="courses" table="t_s_c">

            <!-- 當前Student對象在關系表中的外鍵名字 -->

            <key column="sid"></key>

            <!-- 配置關系

                 class:當前的Course對應的完整的包路徑

                 當前集合中裝入的對象對應的完整的包路徑

                 column:當前的Course對象在關系表中的外鍵

             -->

            <many-to-many class="cn.itcast.c_manytomany.Course" column="cid"></many-to-many>

        </set>

        

        

    </class>

 

</hibernate-mapping>

 

Course.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="cn.itcast.c_manytomany.Course" table="t_course">

        <id name="id">

            <generator class="native"></generator>

        </id>

        <property name="name"></property>

        <!-- 配置集合

         table:配置多對多關系的時候,兩邊的table要一致

         -->

        <set name="stus" table="t_s_c">

            <key column="cid"></key>

            <many-to-many class="cn.itcast.c_manytomany.Student" column="sid"></many-to-many>

        </set>

        

        

    </class>

    

 

</hibernate-mapping>

 

 

 

【第三步】:核心配置中添加映射

【第四步】:建表測試

建表完成之後,查看建表語句:

 

圖解:

只要是多對一的標簽,都配置自己在對方的外鍵屬性。

 

即保存學生和課程數據的同時,在中間選課表插入關聯數據。

@Test

    public void testSave()

    {

        Session session = HibernateUtils.openSession();

        session.beginTransaction();

        //新建一個學生

        Student stu = new Student();

        stu.setName("rose");

        //新建課程

        Course course = new Course();

        course.setName("hibernate");

//        /雙向關聯

        stu.getCourses().add(course);

        course.getStus().add(stu);

          

        

        //發現拋出異常,如何解決

        //1.外鍵維護權,一方放棄inverse="true",並且不放棄維護權的一方,加入 cascade="save-update"

        //2.建立關系是,只需要建立一方的關系即可,並且建立關系的一方,加入 cascade="save-update"

        

        

        

        //雙向保存

        session.save(course);

        session.save(stu);

          

        

        session.getTransaction().commit();

        session.close();

        

    }

    

錯誤:

 

可以采用兩種方案解決問題:

第一種方案.外鍵維護權,一方放棄inverse="true",並且不放棄維護權的一方,加入 cascade="save-update":推薦方案

第二種方案.建立關系時,只需要建立一方的關系即可,並且建立關系的一方,加入 cascade="save-update"

 

 

第一種方案:

執行的代碼:

 

    @Test

    public void testSave1(){

        Session session = HibernateUtils.openSession();

        session.beginTransaction();

        //創建學生

        Student s1 = new Student();

        s1.setName("rose");

        ///創建課程

        Course c1 = new Course();

        c1.setName("hibernate");

        //建立雙向關聯關系

        s1.getCourses().add(c1);

        c1.getStus().add(s1);

        //保存

//        session.save(c1);

//        session.save(s1);

        

        //第一種方案:一方放棄外鍵維護權,另一方加入級聯保存,未來保存的時候,在另一方進行保存操作

        session.save(s1);

          

        

        session.getTransaction().commit();

        session.close();

    }

 

第二種方案:第二種方案xml文件的配置直接采用第一種方案的配置,不做任何修改,此時,我們知道Student.hbm.xml中配置了

Cascade="save-update",所以接下來的測試代碼如下:

 

實際業務中,一般不會去級聯保存,因為學生和課程是各自產生存在的.

 

結論:多對多模型中,一次創建關系,對應中間表 一條insert語句 !不需要雙方發生關系。

 

在實際業務中,要麼是在一方建立關系,要麼是如果兩方都建立關系,就配置inverse,讓一方主動放棄維護權。

我們推薦一方放棄維護權

 

兩個對象解除關系就是刪除中間表的關系數據,即將兩個對象解除關系。刪除的時候,對象必須是持久態對象

@Test

    public void deleteRelation(){

        Session session = HibernateUtils.openSession();

        session.beginTransaction();

        

        Student student = (Student) session.get(Student.class, 3);

        Course course = (Course) session.get(Course.class, 3);

        

        //注意:由於course放棄了對集合的維護權,所以此時只能在Student這一方進行集合操作

        student.getCourses().remove(course);

        

        //不需要手動的刪除,直接使用快照的更新功能,commit會隱式的flush

        session.getTransaction().commit();

        session.close();

        

    }

語句問題:查看語句。

 

變更選課內容,原來選的語文,改為選數學

----- hibernate 無法生成update語句 (只能由我們自己先delete,後insert—先解除關系,再增加關系 )

分析:假如能update,你要update中間表,但中間表無法通過實體操作。

插入測試數據,-

@Test

    public void changeRelation(){

        Session session = HibernateUtils.openSession();

        session.beginTransaction();

        

        //讓2號學生選修3號課程

        Student student = (Student) session.get(Student.class, 2);

        Course course = (Course) session.get(Course.class, 2);

        //解除關系

        student.getCourses().remove(course);

        

        //查找3號課程

        Course course2 = (Course) session.get(Course.class, 3);

        //添加關系

        student.getCourses().add(course2);

        

        //flush

        session.getTransaction().commit();

        session.close();

    }

 

 

多對多的刪除!!!!!!!!!!

一定不能級聯刪除!!!!!!!

 

 

刪除學生的時候,Hibernate會自動刪除關系表(中間表)的數據(無需級聯)

 

    @Test

    public void testDeleteEntity(){

        Session session = HibernateUtils.openSession();

        session.beginTransaction();

        

        Student stu = (Student)session.get(Student.class, 2);

        //刪除

        session.delete(stu);

        

        //flush

        session.getTransaction().commit();

        session.close();

        

    }

    

 

注意: 多對多不建議使用 級聯 (delete 級聯),造成數據丟失 !!!!!!!(如果兩邊都配置級聯,會兩邊刪除,造成表被清空)

測試一下:(實際業務中是不存在的!!3個學生對應3個課程,創建9個關系,隨便刪除哪個對象,數據直接被置空)

在課程或者學生方配置級聯

如果在學生方面配置了級聯,那麼當刪除持久態的學生對象時,會將對應的課程也同時刪除掉了!從而造成數據丟失!(刪除脫管的不會,因為脫管態對級聯不起作用)

(課後嘗試多對多的級聯刪除)

 

 

復習:

1 能夠說出PO對象三狀態

2 理解session一級緩存(能夠存儲數據)

3 理解快照(用來更新數據的)

4 get和load的區別

5 掌握一對多的配置

6 掌握一對多的CRUD操作

7 掌握多對多的配置

8 掌握對多對的CRUD操作

 

學會debug,多用debug

 

【作業一】

完成全天課程練習。

【作業二】

完成課前資料中的:《hibernate知識點作業練習》文檔中的練習。

 

 

 

 

 

 

 

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved