原文地址: 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所託管