hibernate的flush和操作方式
操作hibernate不是直接操作數(shù)據(jù)庫止后,hibernate在其中又加入一層緩存,而正是因為這層緩存的原因葛虐,很多時候我們需要認(rèn)真去分析我們的sql執(zhí)行順序胎源,不要引起意外情況,下面這張圖是描述了uuid和native兩種主鍵策略下屿脐,各種操作的流程:
- 當(dāng)執(zhí)行save/update/delete等操作后涕蚤,hibernate并不是直接生成sql語句執(zhí)行,而是先把操作存入actionQueue的相應(yīng)隊列中的诵,然后再把當(dāng)前操作對象緩存到persistenceContext中
- 只有主鍵需要數(shù)據(jù)庫生成時万栅,在做save等操作的時候,才會直接發(fā)出sql語句去數(shù)據(jù)庫中執(zhí)行
- 在commit或者flush執(zhí)行時西疤,要檢查User對象烦粒,persistenceContext的User對象緩存以及actionQueue中的對象引用,數(shù)據(jù)上是否一致代赁,沒有出現(xiàn)單獨被修改的情況扰她,否則會拋出異常
actionQueue和persistenceContext的具體位置請參看下圖,網(wǎng)絡(luò)上說的有些路徑和我的不一致芭碍,有可能是版本的問題徒役,請自行檢查。
接下來就是對各種主鍵策略的代碼測試豁跑,具體測試內(nèi)容廉涕,請看代碼中的注釋:
實體類:
package entity;
import java.util.Date;
public class User {
private int id;
private String userName;
private Date addtime;
public Date getAddtime() {
return addtime;
}
public void setAddtime(Date addtime) {
this.addtime = addtime;
}
public User(){}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
}
映射文件:
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="entity.User" table="_user" >
<id name="id">
<generator class="native" />
</id>
<property name="userName" />
<property name="addtime" type="time" />
</class>
</hibernate-mapping>
單元測試:
package entity;
import java.util.Date;
import org.hibernate.classic.Session;
import junit.framework.TestCase;
import util.hibernateUtil;
/**
* 測試目的:
*
* 不同的id策略(uuid,native,assigned)下,save之后commit之前艇拍,系統(tǒng)會不會自動清理緩存狐蜕,包括各種異常情況測試
*
* @author arkulo
*
*/
public class TestOneToOne extends TestCase {
// uuid情況下,普通方式
public void test1() {
Session session = null;
try {
session = hibernateUtil.getSession();
session.beginTransaction();
User user = new User();
user.setUserName("王蕊");
user.setAddtime(new Date());
// 在save后卸夕,hibernate會在session的insertion中添加一條操作記錄层释,同時會在persistenceContext中添加一個user緩存對象
// 但這時候,并沒有發(fā)出sql語句快集,系統(tǒng)不會自動flush
session.save(user);
// 只有commit的時候贡羔,系統(tǒng)會自動flush廉白,并且發(fā)出sql語句,真正實現(xiàn)持久化
session.getTransaction().commit();
} catch (Exception e) {
e.printStackTrace();
session.getTransaction().rollback();
} finally {
hibernateUtil.closeSession(session);
}
}
// 主鍵策略是uuid的情況下乖寒,各種緩存引起的問題
public void test2() {
Session session = null;
try {
session = hibernateUtil.getSession();
session.beginTransaction();
User user = new User();
user.setUserName("王蕊");
user.setAddtime(new Date());
// 在save后猴蹂,hibernate會在session的insertion中添加一條操作記錄,同時會在persistenceContext中添加一個user緩存對象
// 但這時候楣嘁,并沒有發(fā)出sql語句磅轻,系統(tǒng)不會自動flush
session.save(user);
// 這個時候如果設(shè)置id為null,會提示錯誤:
// identifier of an instance of entity.User was altered from
// 402881e65b956dbc015b956e88d00001 to null
// 這表示在persistenceContext中登記的user對象逐虚,和實際的user對象在執(zhí)行checkId操作的時候?qū)Σ簧狭肆铮虼似爻霎惓? // user.setId(null);
// 如果這里選擇刪除當(dāng)前對象緩存,其實刪除的就是persistenceContext的對象叭爱,等到commit的時候撮躁,insertion操作檢查persistenceContext
// 中的對象緩存時,發(fā)現(xiàn)沒有了买雾,就會曝出異常:
// org.hibernate.AssertionFailure: possible nonthreadsafe access to
// session
// session.evict(user);
// 如果我們更新一下user對象把曼,然后在新建一個新的user1對象,看看實際的sql執(zhí)行過程凝果,是不是和我們代碼的順序不一樣
// Hibernate: insert into User (userName, addtime, id) values (?, ?,
// ?)
// Hibernate: insert into User (userName, addtime, id) values (?, ?,
// ?)
// Hibernate: update User set userName=?, addtime=? where id=?
// user.setUserName("謹(jǐn)言");
// session.update(user);
// User user1 = new User();
// user1.setUserName("李四");
// user1.setAddtime(new Date());
// session.save(user1);
session.getTransaction().commit();
} catch (Exception e) {
e.printStackTrace();
session.getTransaction().rollback();
} finally {
hibernateUtil.closeSession(session);
}
}
// native情況下祝迂,普通方式
public void test3() {
Session session = null;
try {
session = hibernateUtil.getSession();
session.beginTransaction();
User user = new User();
user.setUserName("王蕊");
user.setAddtime(new Date());
// 當(dāng)id策略是native的時候,save函數(shù)必須從數(shù)據(jù)庫中獲取主鍵器净,因此當(dāng)save后就會直接發(fā)出sql語句型雳,而不是等待flush
// 這時候查看對象的狀態(tài)existsInDatabase是true,并且insertion隊列中已經(jīng)沒有待執(zhí)行的操作了
session.save(user);
// 這里執(zhí)行commit的時候山害,就不會引發(fā)sql語句的執(zhí)行了
session.getTransaction().commit();
} catch (Exception e) {
e.printStackTrace();
session.getTransaction().rollback();
} finally {
hibernateUtil.closeSession(session);
}
}
// 主鍵策略是native的情況下纠俭,各種緩存引起的問題
public void test4() {
Session session = null;
try {
session = hibernateUtil.getSession();
session.beginTransaction();
User user = new User();
user.setUserName("王蕊");
user.setAddtime(new Date());
session.save(user);
// 如果我們再次嘗試插入-更新-插入操作,看看sql的執(zhí)行順序是什么樣的浪慌?結(jié)果發(fā)現(xiàn)和uuid的時候一樣冤荆,還是先執(zhí)行了insert,然后再執(zhí)行update
// 這是怎么回事呢权纤?
// 原因是:只有save是立刻發(fā)出sql語句的钓简,因為它需要數(shù)據(jù)庫分配主鍵,但是update不需要立刻去數(shù)據(jù)庫執(zhí)行汹想,因此他還是按照hibernate的方式執(zhí)行
// 因此外邓,當(dāng)執(zhí)行到update函數(shù)的時候,它還是會被暫時記錄到更新操作隊列中的古掏,當(dāng)commit的時候损话,按照插入,更新,刪除等順序丧枪,一個個的執(zhí)行光涂。
// Hibernate: insert into _user (userName, addtime) values (?, ?)
// Hibernate: insert into _user (userName, addtime) values (?, ?)
// Hibernate: update _user set userName=?, addtime=? where id=?
// 如果我們更新一下user對象,然后在新建一個新的user1對象拧烦,看看實際的sql執(zhí)行過程忘闻,是不是和我們代碼的順序不一樣
// user.setUserName("謹(jǐn)言");
// session.update(user);
// User user1 = new User();
// user1.setUserName("李四");
// user1.setAddtime(new Date());
// session.save(user1);
session.getTransaction().commit();
} catch (Exception e) {
e.printStackTrace();
session.getTransaction().rollback();
} finally {
hibernateUtil.closeSession(session);
}
}
// assigned情況下,普通方式
public void test5() {
Session session = null;
try {
session = hibernateUtil.getSession();
session.beginTransaction();
User user = new User();
user.setUserName("王蕊");
user.setAddtime(new Date());
user.setId(111);
session.save(user);
// 這里采用assigned的主鍵策略恋博,因為主鍵是自己在代碼中指定的服赎,因此不需要去數(shù)據(jù)庫中生成主鍵,因此當(dāng)save之后交播,并沒有
// 直接發(fā)出sql,而是按照管理在insertion中添加了操作記錄践付,并且在persistenceContext生成了緩存對象
// 這里執(zhí)行commit的時候秦士,就不會引發(fā)sql語句的執(zhí)行了
session.getTransaction().commit();
} catch (Exception e) {
e.printStackTrace();
session.getTransaction().rollback();
} finally {
hibernateUtil.closeSession(session);
}
}
// 主鍵策略是assigned的情況下,各種緩存引起的問題
public void test6() {
Session session = null;
try {
session = hibernateUtil.getSession();
session.beginTransaction();
User user = new User();
user.setUserName("王蕊");
user.setAddtime(new Date());
user.setId(111);
session.save(user);
// 這個時候如果設(shè)置id為null永高,commit的時候系統(tǒng)會拋異常隧土。可以看到命爬,insetion和緩存中的id沒有發(fā)生變化曹傀,但是實際user對象的id變?yōu)榱?
// 這在執(zhí)行checkid操作的時候會報錯,系統(tǒng)就會拋出異常饲宛。
// user.setId(0);
// 如果這里選擇刪除當(dāng)前對象緩存皆愉,其實刪除的就是persistenceContext的對象,等到commit的時候艇抠,insertion操作檢查persistenceContext
// 中的對象緩存時幕庐,發(fā)現(xiàn)沒有了,就會曝出異常:
// org.hibernate.AssertionFailure: possible nonthreadsafe access to
// session
// session.evict(user);
// 這里和uuid的方式一樣家淤,在save和update的時候都是不發(fā)sql語句的异剥,只有到了commit的時候,一次性的按照插入絮重,更新冤寿,刪除的順序執(zhí)行
// Hibernate: insert into User (userName, addtime, id) values (?, ?,
// ?)
// Hibernate: insert into User (userName, addtime, id) values (?, ?,
// ?)
// Hibernate: update User set userName=?, addtime=? where id=?
// user.setUserName("謹(jǐn)言");
// session.update(user);
// User user1 = new User();
// user1.setUserName("李四");
// user1.setAddtime(new Date());
// session.save(user1);
session.getTransaction().commit();
} catch (Exception e) {
e.printStackTrace();
session.getTransaction().rollback();
} finally {
hibernateUtil.closeSession(session);
}
}
}