hibernate的三種狀態的轉化
學過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所託管