深入理解hibernate的三種狀態

似是故人來發表於2015-01-25

原文地址: http://www.cnblogs.com/xiaoluo501395377/p/3380270.html

學過hibernate的人都可能都知道hibernate有三種狀態,transient(瞬時狀態),persistent(持久化狀態)以及detached(離線狀態),大傢伙也許也知道這三者之間的區別,比如瞬時狀態就是剛new出來一個物件,還沒有被儲存到資料庫中,持久化狀態就是已經被儲存到資料庫中,離線狀態就是資料庫中有,但是session中不存在該物件。但是大家又是否對hibernate的session的那幾個特殊方法一清二楚呢?或者說大家是否能夠一眼就快速看出一個測試用例在反覆的呼叫session的諸如save,update方法後會到底發出多少條SQL語句呢?本篇隨筆將會給你答案,本篇隨筆將會以大量的測試用例來掩飾hibernate的這三種狀態的轉變,相信看完本篇隨筆的你會對hibernate的那三種狀態有更深入的理解。

好了,廢話不多說了,相信大家都知道hibernate的這三種狀態的含義,那我們就通過一張圖來開始我們的深入hibernate的三種狀態之旅吧。

1.TestTransient

    session = HibernateUtil.openSession();
        session.beginTransaction();
        User user = new User();
        user.setUsername("aaa");
        user.setPassword("aaa");
        user.setBorn(new Date());
        /*
         *  以上user就是一個Transient(瞬時狀態),此時user並沒有被session進行託管,即在session的
         *  快取中還不存在user這個物件,當執行完save方法後,此時user被session託管,並且資料庫中存在了該物件
         *  user就變成了一個Persistent(持久化物件)
         */
        session.save(user);
        session.getTransaction().commit();

此時我們知道hibernate會發出一條insert的語句,執行完save方法後,該user物件就變成了持久化的物件了

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

2.TestPersistent01

      

       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();


在呼叫了save方法後,此時user已經是持久化物件了,被儲存在了session快取當中,這時user又重新修改了屬性值,那麼在提交事務時,此時hibernate物件就會拿當前這個user物件和儲存在session快取中的user物件進行比較,如果兩個物件相同,則不會傳送update語句,否則,如果兩個物件不同,則會發出update語句。

Hibernate: insert into t_user (born, password, username) values (?, ?, ?)
Hibernate: update t_user set born=?, password=?, username=? where id=?

3.TestPersistent02

       

    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();

這個時候會發出多少sql語句呢?還是同樣的道理,在呼叫save方法後,u此時已經是持久化物件了,記住一點:如果一個物件以及是持久化狀態了,那麼此時對該物件進行各種修改,或者呼叫多次update、save方法時,hibernate都不會傳送sql語句,只有當事物提交的時候,此時hibernate才會拿當前這個物件與之前儲存在session中的持久化物件進行比較,如果不相同就傳送一條update的sql語句,否則就不會傳送update語句

Hibernate: insert into t_user (born, password, username) values (?, ?, ?)
Hibernate: update t_user set born=?, password=?, username=? where id=?

4.TestPersistent03

       

            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
            session = HibernateUtil.openSession();
            session.beginTransaction();
            User u = new User();
            u.setBorn(sdf.parse("1976-2-3"));
            u.setUsername("zhangsan2");
            u.setPassword("zhangsan2");
            session.save(u);
            /*
             * 以下三條語句沒有任何意義
             */
            session.save(u);
            session.update(u);
            session.update(u);
            u.setUsername("zhangsan3");
            session.getTransaction().commit();

相信這個測試用例,大家應該都知道結果了,沒錯,此時hibernate也會發出兩條sql語句,原理一樣的

Hibernate: insert into t_user (born, password, username) values (?, ?, ?)
Hibernate: update t_user set born=?, password=?, username=? where id=?

5.TestPersistent04

       

            session = HibernateUtil.openSession();
            session.beginTransaction();
            //此時u是Persistent
            User u = (User)session.load(User.class, 4);
            //由於u這個物件和session中的物件不一致,所以會發出sql完成更新
            u.setUsername("bbb");
            session.getTransaction().commit();

我們來看看此時會發出多少sql語句呢?同樣記住一點:當session呼叫load、get方法時,此時如果資料庫中有該物件,則該物件也變成了一個持久化物件,被session所託管。因此,這個時候如果對物件進行操作,在提交事務時同樣會去與session中的持久化物件進行比較,因此這裡會傳送兩條sql語句

Hibernate: select user0_.id as id0_0_, user0_.born as born0_0_, user0_.password as password0_0_, user0_.username as username0_0_ from t_user user0_ where user0_.id=?
Hibernate: update t_user set born=?, password=?, username=? where id=?

6.TestPersistent05

      

           session = HibernateUtil.openSession();
            session.beginTransaction();
            //此時u是Persistent
            User u = (User)session.load(User.class, 4);
            u.setUsername("123");
            //清空session
            session.clear();
            session.getTransaction().commit();

再看這個例子,當我們load出user物件時,此時user是持久化的物件,在session快取中存在該物件,此時我們在對user進行修改後,然後呼叫session.clear()方法,這個時候就會將session的快取物件清空,那麼session中就沒有了user這個物件,這個時候在提交事務的時候,發現已經session中已經沒有該物件了,所以就不會進行任何操作,因此這裡只會傳送一條select語句

Hibernate: select user0_.id as id0_0_, user0_.born as born0_0_, user0_.password as password0_0_, user0_.username as username0_0_ from t_user user0_ where user0_.id=?

7.TestDetached01

      

           session = HibernateUtil.openSession();
            session.beginTransaction();
            //此時u是一個離線物件,沒有被session託管
            User u = new User();
            u.setId(4);
            u.setPassword("hahahaha");
            //當執行save的時候總是會新增一條資料,此時id就會根據Hibernate所定義的規則來生成
            session.save(u);
            session.getTransaction().commit();
我們看到,當呼叫了u.setId(4)時,此時u是一個離線的物件,因為資料庫中存在id=4的這個物件,但是該物件又沒有被session所託管,所以這個物件就是離線的物件,要使離線物件變成一個持久化的物件,應該呼叫什麼方法呢?我們知道呼叫save方法,可以將一個物件變成一個持久化物件,但是,當save一執行的時候,此時hibernate會根據id的生成策略往資料庫中再插入一條資料,所以如果呼叫save方法,此時資料庫會傳送一條插入的語句:

Hibernate: insert into t_user (born, password, username) values (?, ?, ?)
所以對於離線物件,如果要使其變成持久化物件的話,我們不能使用save方法,而應該使用update方法

8.TestDetached02

      

           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("world");
            u.setUsername("world");
            //會發出一條sql
            session.update(u);
            session.getTransaction().commit();

此時我們看到,當呼叫了update方法以後,此時u已經變成了一個持久化的物件,那麼如果此時對u物件進行修改操作後,在事務提交的時候,則會拿該物件和session中剛儲存的持久化物件進行比較,如果不同就發一條sql語句

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

9.TestDetached03

      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();

我們看這個例子,前面的操作一樣,呼叫update方法後,user變成了一個持久化物件,在對user進行一些修改後,此時又通過 u.setId(333)方法設定了u的ID,那麼這個時候,hibernate會報錯,因為我們的u當前已經是一個持久化物件,如果試圖修改一個持久化物件的ID的值的話,就會丟擲異常,這點要特別注意

org.hibernate.HibernateException: identifier of an instance of com.xiaoluo.bean.User was altered from 5 to 333
10.TestDetached04

      session = HibernateUtil.openSession();
            session.beginTransaction();
            User u = new User();
            u.setId(5);
            //現在u就是transient物件
            session.delete(u);
            //此時u已經是瞬時物件,不會被session和資料庫所管理
            u.setPassword("wangwu");
            session.getTransaction().commit();

接著我們來看這個例子,這裡在呼叫了session.delete()方法以後,此時後u就會變成一個瞬時物件,因為此時資料庫中已經不存在該物件了,既然u已經是一個瞬時物件了,那麼對u再進行各種修改操作的話,hibernate也不會傳送任何的修改語句,因此這裡只會 有一條 delete的語句發生:

Hibernate: delete from t_user where id=?

11.TestDetached05

      session = HibernateUtil.openSession();
            session.beginTransaction();
            User u = new User();
            u.setId(4);
            u.setPassword("zhaoliu");
            //如果u是離線狀態就執行update操作,如果是瞬時狀態就執行Save操作
            //但是注意:該方法並不常用
            session.saveOrUpdate(u);
            session.getTransaction().commit();

這裡我們來看看 saveOrUpdate這個方法,這個方法其實是一個"偷懶"的方法,如果物件是一個離線物件,那麼在執行這個方法後,其實是呼叫了update方法,如果物件是一個瞬時物件,則會呼叫save方法,記住:如果物件設定了ID值,例如u.setId(4),那麼該物件會被假設當作一個離線物件,此時就會執行update操作。

Hibernate: update t_user set born=?, password=?, username=? where id=?
如果此時我將u.setId(4)這句話註釋掉,那麼此時u就是一個瞬時的物件,那麼此時就會執行save操作,就會傳送一條insert語句

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

12.TestDetached06

       

            session = HibernateUtil.openSession();
            session.beginTransaction();
            //u1已經是持久化狀態
            User u1 = (User)session.load(User.class, 3);
            System.out.println(u1.getUsername());
            //u2是離線狀態
            User u2 = new User();
            u2.setId(3);
            u2.setPassword("123456789");
            //此時u2將會變成持久化狀態,在session的快取中就存在了兩份同樣的物件,在session中不能存在兩份拷貝,否則會丟擲異常
            session.saveOrUpdate(u2);

我們再來看一下這個例子,此時我們的u1已經是持久化的物件了,儲存在session快取中,u2通過呼叫saveOrUpdate方法後也變成了一個持久化的物件,此時也會儲存在session快取中,這個時候session快取中就存在了一個持久化物件有兩個引用拷貝了,這個時候hibernate就會報錯

org.hibernate.NonUniqueObjectException: a different object with the same identifier value was already associated with the session: [com.xiaoluo.bean.User#3]
一個session中不能存在對一個持久化物件的雙重copy的,要解決這個方法,我們這裡又要介紹session的另一個方法  merge方法,這個方法的作用就是解決一個持久化物件兩分拷貝的問題,這個方法會將兩個物件合併在一起成為一個物件。


       session = HibernateUtil.openSession();
            session.beginTransaction();
            //u1已經是持久化狀態
            User u1 = (User)session.load(User.class, 3);
            System.out.println(u1.getUsername());
            //u2是離線狀態
            User u2 = new User();
            u2.setId(3);
            u2.setPassword("123456789");
            //此時u2將會變成持久化狀態,在session的快取中就存在了兩份同樣的物件,在session中不能存在兩份拷貝,否則會丟擲異常
//            session.saveOrUpdate(u2);
            //merge方法會判斷session中是否已經存在同一個物件,如果存在就將兩個物件合併
            session.merge(u2);
            //最佳實踐:merge一般不用
            session.getTransaction().commit();

我們看到通過呼叫了merge方法以後,此時會將session中的兩個持久化物件合併為一個物件,但是merge方法不建議被使用

Hibernate: select user0_.id as id0_0_, user0_.born as born0_0_, user0_.password as password0_0_, user0_.username as username0_0_ from t_user user0_ where user0_.id=?
zhangsan
Hibernate: update t_user set born=?, password=?, username=? where id=?

終於寫完了本篇隨筆,本篇隨筆可能概念性的內容比較少,基本都是通過測試用例來分析hibernate的三種狀態可能會出現的各種情況。

最後總結一下:

①.對於剛建立的一個物件,如果session中和資料庫中都不存在該物件,那麼該物件就是瞬時物件(Transient)

②.瞬時物件呼叫save方法,或者離線物件呼叫update方法可以使該物件變成持久化物件,如果物件是持久化物件時,那麼對該物件的任何修改,都會在提交事務時才會與之進行比較,如果不同,則傳送一條update語句,否則就不會傳送語句

③.離線物件就是,資料庫存在該物件,但是該物件又沒有被session所託管

相關文章