hibernate(二)一級快取和三種狀態解析

一杯涼茶發表於2016-11-27

      序言

        前一篇文章知道了什麼是hibernate,並且建立了第一個hibernate工程,今天就來先談談hibernate的一級快取和它的三種狀態,先要對著兩個有一個深刻的瞭解,才能對後面我要講解的一對多,一對一、多對多這種對映關係更好的理

 

                                               --WH

一、一級快取和快照

    什麼是一級快取呢?

      很簡單,每次hibernate跟資料庫打交道時,都是通過session來對要操作的物件取得關聯,然後在進行操作,那麼具體的過程是什麼樣的呢?

        1、首先session將一個物件加入自己的管理範圍內,其實也就是把該物件放入自己的一級快取中,例如,session.save(xxx);這個語句就是將xxx儲存在自己的一級快取中,等待事務提交後,hibernate才真正的發sql語句,對資料庫進行操作。注意:session進行操作的時候,是將物件加入自己的一級快取,並不是就直接跟資料庫打交道了。

        2、在一級快取中會做些什麼事情呢?為什麼能夠知道是發insert、還是update又或者delete呢?那這裡就要提到一個快照的概念了,講講內部是什麼原理。

舉例來說明問題吧。

session.save()

        User user = new User();
        user.setUsername("xiaoming");
        user.setPassword("123");
        
        session.save(user);//加入了一級快取,這其中做了什麼事情呢?看圖
        user.setUsername("baibai");//那這裡改了user的屬性,具體會發生什麼事情呢
        //提交事務
        tx.commit();
        //關閉session,持久化物件就會變為脫管狀態。
        session.close();

        

 傳送的sql語句  

//insert語句
Hibernate:
    insert
    into
        user
        (username, password, c_id)
    values
        (?, ?, ?)
//update語句
Hibernate:
    update
        user
    set
        username=?,
        password=?,
        c_id=?
    where
        id=?

session.get()

        //通過圖中分析,通過select語句查詢,並且user會在session的一級快取中,
        User user =(User)session.get(User.class, "8");
        //會發出update語句。
        user.setUsername("heihei");

分析圖

      

結果

//select語句
Hibernate: 
    select
        user0_.id as id0_0_,
        user0_.username as username0_0_,
        user0_.password as password0_0_,
        user0_.c_id as c4_0_0_ 
    from
        user user0_ 
    where
        user0_.id=?
//update語句
Hibernate: 
    update
        user 
    set
        username=?,
        password=?,
        c_id=? 
    where
        id=?

 

在舉一個例子說明有一級快取的存在,對於快取來說,肯定有對快取的一些操作,比如evict(xx)清除xx快取、clear():清除一級快取中所有資料、flush()刷出一級快取(也就是不用事務提交快取就刷出來了,就會發sql語句,事務提交也是一個刷出快取的方法,) 就用上面這個get的例子

session.evict()

        //通過圖中分析,通過select語句查詢,並且user會在session的一級快取中,
        User user =(User)session.get(User.class, "15");
        //將user移除session一級快取
        session.evict(user);
        //不會發出update語句。
        user.setUsername("heihei");      

 結果就傳送一條select查詢語句

Hibernate: 
    select
        user0_.id as id0_0_,
        user0_.username as username0_0_,
        user0_.password as password0_0_,
        user0_.c_id as c4_0_0_ 
    from
        user user0_ 
    where
        user0_.id=?

 

     總結一下:通過上面的例子和講解,我們應該知道,並不是session一操作物件,就立馬會發sql語句,而是先將其儲存到自己內部的一級快取中,如果不手動刷出快取,就會等待事務提交時刷出快取,然後再進行傳送sql語句,對資料庫進行操作。並且其中有一個快照區需要知道是幹嘛的,理解了快照區,就可以知道每次會傳送幾條sql語句。注意一點,session每次操作的是在一級快取中的物件,不會操作快照區中的物件,快照區只是用來當作比較的一個地方,當物件加入一級快取之後,外部在怎麼改變,也只是改變快照區中的資料,並不是改變一級快取中物件的屬性。這點就記清楚。如果還不是很清楚,等下面在帶你們看幾個例子,就瞬間明白了。

 

 

 

二、hibernate中物件的三種狀態  

       瞬時狀態transient、持久狀態(託管)persistent、遊離(脫管)detached狀態     
          注意:託管、脫管要分清楚,分不清楚就用持久和遊離

      瞬時狀態:使用new操作符初始化的物件的狀態就是瞬時的,

          1、在資料庫表中,沒有任何一條資料與它對應

          2、不在session的快取中

      持久狀態:在session的快取中,

          1、在session的快取中(注意,很多書上都覺得持久化狀態都在資料庫表中有相應記錄,這個是錯誤的,比如一個瞬時狀態的物件剛被session.save(),事務還沒提交,此時瞬時狀態就已經變為持久化狀態了,但是在資料庫中還沒有記錄)

          2、在資料庫中可能有記錄。

      脫管狀態:從session的快取中移除出來了

          1、是從session快取中出來的,也就是從持久狀態轉變而來的,沒有別的方式能到達遊離(脫管)狀態,只有這一種。

          2、不在session的管理範圍內

          3、在資料庫中有記錄

        

      根據上面的文字性描述,可能還不太理解,那現在根據我畫的圖和官方給予的圖來加深印象吧。

 

           誤區1、很多書上覺得通過id值就可以判斷物件是在哪個狀態,比如說,沒有id值,就是瞬時狀態,有id並且在session管理範圍內,就是持久狀態,有id不在session範圍內就是脫管狀態,   

            解釋:從上面的解釋來看,瞬時狀態變為脫管狀態,只要加上id就行了,但是從官方圖來說,瞬時狀態不能直接變為遊離狀態,而遊離狀態可以通過delete直接到達瞬時狀態(其實說直接,也是先將遊離狀態的物件加入到session快取中變為持久狀態,然後delete,在變為瞬時狀態而已。),那麼上面所用的通過有沒有id值來判斷三種狀態就是有偏差的,可以這麼理解,瞬時狀態在資料庫中就一定沒有對應的記錄,而遊離狀態一定是通過持久狀態轉變而來的,並且在資料庫中可能有記錄(沒有記錄是因為多執行緒,有別的程式把那條記錄給刪除了,所以一般就覺得是有記錄的),所以瞬時狀態和遊離狀態的區分點就在:從什麼轉變而來的,如果直接new的,那麼就是瞬時狀態物件,如果從持久態轉變的,那麼就是遊離狀態。

 

             誤區2:很多書上或者部落格中會說識別是不是持久化狀態,是看在session快取內,還有就是在資料庫中有記錄。

             解釋:這裡的前半句對,但是後半句錯誤,持久狀態可能在資料庫中有記錄,也可能在資料庫中沒有記錄,說說沒有記錄的時候吧,就是當session.save儲存瞬時狀態時,事務還沒有提交,物件還只是在session的一級快取中,資料庫中就沒有該記錄,但此時它就已經是持久狀態物件了。

    

           誤區3:認為session一操作,就會發出sql語句,這個上面已經說明白了,應該很清楚了。

   

     小總結一下:怎麼區分這三種狀態

          1、如果是在session快取中,那麼就一定是持久狀態

          2、如果是剛new出來的物件,那麼就肯定是瞬時狀態

          3、如果是從session快取中出來的,也就是通過一些session.clear、ecivt等操作,清除了快取的,那麼就是遊離狀態,

        只有瞬時狀態能確定資料庫中沒有對應記錄,其他兩個狀態,都是不確定資料庫中是否有對應記錄

        一切都以官方給出的圖為準,其他的快速識別狀態的方法,我認為是不可取的,自己還是沒弄明白其中的道理,也就永遠沒把握自己是不是對的。

    

    講了那麼久的理論,現在就通過那張官方給的圖,讓我們來實驗試驗各種狀態間的轉換吧。

1、怎麼變成瞬時狀態、和如何從瞬時狀態變為持久狀態?   

        User user = new User();//瞬時狀態
        user.setUsername("aaa");//瞬時狀態
        session.save(user);//持久狀態,這裡使用saceOrUpdate也一樣。將user放在session的一級快取中,快照區也有一份        
        //提交事務
        tx.commit();//事務提交之後,才傳送sql語句
        //關閉session
        session.close();//session關閉後,user就變為遊離(脫管)狀態了

2、怎麼直接變為持久狀態   

        //從資料庫中查詢id為7的使用者,並且此時user在session的一級快取中,快照區也有一份一樣的
        User user = (User) session.get(User.class, "7");
        
        //提交事務
        tx.commit();
        //關閉session,持久化物件就會變為脫管狀態。
        session.close();

 

3、持久狀態變為瞬時狀態

   

        //並且user會在session的一級快取中,持久狀態,快照區也有一份一樣的
        User user =(User)session.get(User.class, "15");
        //將user移除session一級快取,並且從資料庫中刪除該記錄,快照區中的物件也刪除了,所以變成瞬時狀態而不是變成遊離狀態
        session.delete(user);
        //不會發出update語句。改變的只是瞬時狀態的屬性
        user.setUsername("heihei");

 

4、持久狀態變為遊離狀態

 

        //並且user會在session的一級快取中,持久狀態,快照區中也有一份一樣的
        User user =(User)session.get(User.class, "15");
        //將user移除session一級快取,並沒有刪除資料庫記錄,所以變為了遊離狀態,清除快取,那麼快照區中也沒了
        session.evict(user);
        //不會發出update語句。改變的只是遊離狀態的屬性,沒影響
        user.setUsername("heihei");

 

5、遊離狀態變為持久狀態

     

        //並且user會在session的一級快取中,持久狀態,快照區有一份一樣的
        User user =(User)session.get(User.class, "15");
        //將user移除session一級快取,並沒有刪除資料庫記錄,所以變為了遊離狀態。快照區中也沒了
        session.evict(user);
        //不會發出update語句。改變的只是遊離狀態的屬性,沒影響
        user.setUsername("heihei");
        //會發出update語句進行更新,重新回到持久狀態。加入到一級快取中。快照區中也有一份一樣的。
        session.update(user);
     //猜一下這裡會不會發兩條update語句?如果覺得是兩條的話,就沒記住我說的話,肯定是一條update語句呀,自己可以看上面講解快照區時的圖片,這裡改變了快照區中user的屬性,
     //因為上面用的是update方法,快照區會先跟一級快取中的對比,如果有不一樣,那麼就傳送update語句,而不會先發一個update然後再對比,不一樣在發update,跟insert不一樣。
     user.setUsername("sss");

6、遊離狀態變為瞬時狀態

     這個也沒什麼好講的,上面已經分析過了,遊離狀態直接變為瞬時狀態其實也就是先進過持久狀態,然後再變為瞬時狀態。

 

 

 


三、經過上面的學習,現在深入一點,讓你知道會傳送多少條sql語句。

     1、Test01

       session = HibernateUtil.openSession();
            session.beginTransaction();
            User user = new User();
            user.setUsername("aaa");
            user.setPassword("aaa");
            user.setBorn(new Date());
            //以上u就是Transient(瞬時狀態),表示沒有被session管理並且資料庫中沒有
            //執行save之後,被session所管理,而且,資料庫中已經存在,此時就是Persistent狀態
            session.save(user);
            //此時u是持久化狀態,已經被session所管理,當在提交時,會把session中的物件和快照區的物件進行比較
            //如果兩個物件中的值不一致就會繼續發出相應的sql語句
            user.setPassword("bbb");
            //此時會發出2條sql,一條使用者做插入,一條用來做更新
            session.getTransaction().commit();

    Hibernate: insert into t_user (born, password, username) values (?, ?, ?)

    Hibernate: update t_user set born=?, password=?, username=? where id=?

    Test02

SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
            session = HibernateUtil.openSession();
            session.beginTransaction();
            User u = new User();
            u.setBorn(new Date());
            u.setUsername("zhangsan");
            u.setPassword("zhangsan");
            session.save(u);
            u.setPassword("222");
            //該條語句沒有意義
            session.save(u);
            u.setPassword("zhangsan111");
            //沒有意義
            session.update(u);
            u.setBorn(sdf.parse("1988-12-22"));
            //沒有意義
            session.update(u);
            session.getTransaction().commit();

    沒有意義是什麼意思呢?記得我一開始說的那個注意點嘛,所有的操作都會先存放在session的一級快取中,當物件進入一級快取中,在怎麼改變屬性,都只改變快照區中物件的屬性,在用session進行操作,還是操作的一級快取中的物件,記住了這一點,那麼上面的程式就簡單了,u儲存到了快取中,u改變屬性,session在save u沒一點意義,並且連續改改,也只是改快照區中的屬性,等到事務提交的時候,在做對比和進行相應的操作。

    Hibernate: insert into t_user (born, password, username) values (?, ?, ?)

    Hibernate: update t_user set born=?, password=?, username=? where id=?

 

   Test03

       SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
            session = HibernateUtil.openSession();
            session.beginTransaction();
            User u = new User();
            u.setId(5);
            //完成update之後也會變成持久化狀態
            session.update(u);
            u.setBorn(sdf.parse("1998-12-22"));
            u.setPassword("lisi");
            u.setUsername("lisi");
            //會丟擲異常
            u.setId(333);
            session.getTransaction().commit();

      這個例子會報異常,就是上面我說過的,在一級快取中的物件,如果改變快照區中的id屬性,就會報異常

 

      在這裡,推薦一個博文,如果還想看更多的例子,就進去這個文章裡面看,http://www.cnblogs.com/xiaoluo501395377/p/3380270.html 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

        

 

 

 

 

  

 

 

 

 

 

相關文章