hibernate的flush和操作方式
操作hibernate不是直接操作數(shù)據(jù)庫(kù),hibernate在其中又加入一層緩存,而正是因?yàn)檫@層緩存的原因,很多時(shí)候我們需要認(rèn)真去分析我們的sql執(zhí)行順序,不要引起意外情況,下面這張圖是描述了uuid和native兩種主鍵策略下,各種操作的流程:

hibernate的flush原理.png
- 當(dāng)執(zhí)行save/update/delete等操作后,hibernate并不是直接生成sql語(yǔ)句執(zhí)行,而是先把操作存入actionQueue的相應(yīng)隊(duì)列中,然后再把當(dāng)前操作對(duì)象緩存到persistenceContext中
- 只有主鍵需要數(shù)據(jù)庫(kù)生成時(shí),在做save等操作的時(shí)候,才會(huì)直接發(fā)出sql語(yǔ)句去數(shù)據(jù)庫(kù)中執(zhí)行
- 在commit或者flush執(zhí)行時(shí),要檢查User對(duì)象,persistenceContext的User對(duì)象緩存以及actionQueue中的對(duì)象引用,數(shù)據(jù)上是否一致,沒(méi)有出現(xiàn)單獨(dú)被修改的情況,否則會(huì)拋出異常
actionQueue和persistenceContext的具體位置請(qǐng)參看下圖,網(wǎng)絡(luò)上說(shuō)的有些路徑和我的不一致,有可能是版本的問(wèn)題,請(qǐng)自行檢查。

1.png

2.png

3.png
接下來(lái)就是對(duì)各種主鍵策略的代碼測(cè)試,具體測(cè)試內(nèi)容,請(qǐng)看代碼中的注釋:
實(shí)體類(lè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>
單元測(cè)試:
package entity;
import java.util.Date;
import org.hibernate.classic.Session;
import junit.framework.TestCase;
import util.hibernateUtil;
/**
* 測(cè)試目的:
*
* 不同的id策略(uuid,native,assigned)下,save之后commit之前,系統(tǒng)會(huì)不會(huì)自動(dòng)清理緩存,包括各種異常情況測(cè)試
*
* @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會(huì)在session的insertion中添加一條操作記錄,同時(shí)會(huì)在persistenceContext中添加一個(gè)user緩存對(duì)象
// 但這時(shí)候,并沒(méi)有發(fā)出sql語(yǔ)句,系統(tǒng)不會(huì)自動(dòng)flush
session.save(user);
// 只有commit的時(shí)候,系統(tǒng)會(huì)自動(dòng)flush,并且發(fā)出sql語(yǔ)句,真正實(shí)現(xiàn)持久化
session.getTransaction().commit();
} catch (Exception e) {
e.printStackTrace();
session.getTransaction().rollback();
} finally {
hibernateUtil.closeSession(session);
}
}
// 主鍵策略是uuid的情況下,各種緩存引起的問(wèn)題
public void test2() {
Session session = null;
try {
session = hibernateUtil.getSession();
session.beginTransaction();
User user = new User();
user.setUserName("王蕊");
user.setAddtime(new Date());
// 在save后,hibernate會(huì)在session的insertion中添加一條操作記錄,同時(shí)會(huì)在persistenceContext中添加一個(gè)user緩存對(duì)象
// 但這時(shí)候,并沒(méi)有發(fā)出sql語(yǔ)句,系統(tǒng)不會(huì)自動(dòng)flush
session.save(user);
// 這個(gè)時(shí)候如果設(shè)置id為null,會(huì)提示錯(cuò)誤:
// identifier of an instance of entity.User was altered from
// 402881e65b956dbc015b956e88d00001 to null
// 這表示在persistenceContext中登記的user對(duì)象,和實(shí)際的user對(duì)象在執(zhí)行checkId操作的時(shí)候?qū)Σ簧狭耍虼似爻霎惓? // user.setId(null);
// 如果這里選擇刪除當(dāng)前對(duì)象緩存,其實(shí)刪除的就是persistenceContext的對(duì)象,等到commit的時(shí)候,insertion操作檢查persistenceContext
// 中的對(duì)象緩存時(shí),發(fā)現(xiàn)沒(méi)有了,就會(huì)曝出異常:
// org.hibernate.AssertionFailure: possible nonthreadsafe access to
// session
// session.evict(user);
// 如果我們更新一下user對(duì)象,然后在新建一個(gè)新的user1對(duì)象,看看實(shí)際的sql執(zhí)行過(guò)程,是不是和我們代碼的順序不一樣
// 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的時(shí)候,save函數(shù)必須從數(shù)據(jù)庫(kù)中獲取主鍵,因此當(dāng)save后就會(huì)直接發(fā)出sql語(yǔ)句,而不是等待flush
// 這時(shí)候查看對(duì)象的狀態(tài)existsInDatabase是true,并且insertion隊(duì)列中已經(jīng)沒(méi)有待執(zhí)行的操作了
session.save(user);
// 這里執(zhí)行commit的時(shí)候,就不會(huì)引發(fā)sql語(yǔ)句的執(zhí)行了
session.getTransaction().commit();
} catch (Exception e) {
e.printStackTrace();
session.getTransaction().rollback();
} finally {
hibernateUtil.closeSession(session);
}
}
// 主鍵策略是native的情況下,各種緩存引起的問(wèn)題
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);
// 如果我們?cè)俅螄L試插入-更新-插入操作,看看sql的執(zhí)行順序是什么樣的?結(jié)果發(fā)現(xiàn)和uuid的時(shí)候一樣,還是先執(zhí)行了insert,然后再執(zhí)行update
// 這是怎么回事呢?
// 原因是:只有save是立刻發(fā)出sql語(yǔ)句的,因?yàn)樗枰獢?shù)據(jù)庫(kù)分配主鍵,但是update不需要立刻去數(shù)據(jù)庫(kù)執(zhí)行,因此他還是按照hibernate的方式執(zhí)行
// 因此,當(dāng)執(zhí)行到update函數(shù)的時(shí)候,它還是會(huì)被暫時(shí)記錄到更新操作隊(duì)列中的,當(dāng)commit的時(shí)候,按照插入,更新,刪除等順序,一個(gè)個(gè)的執(zhí)行。
// Hibernate: insert into _user (userName, addtime) values (?, ?)
// Hibernate: insert into _user (userName, addtime) values (?, ?)
// Hibernate: update _user set userName=?, addtime=? where id=?
// 如果我們更新一下user對(duì)象,然后在新建一個(gè)新的user1對(duì)象,看看實(shí)際的sql執(zhí)行過(guò)程,是不是和我們代碼的順序不一樣
// 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的主鍵策略,因?yàn)橹麈I是自己在代碼中指定的,因此不需要去數(shù)據(jù)庫(kù)中生成主鍵,因此當(dāng)save之后,并沒(méi)有
// 直接發(fā)出sql,而是按照管理在insertion中添加了操作記錄,并且在persistenceContext生成了緩存對(duì)象
// 這里執(zhí)行commit的時(shí)候,就不會(huì)引發(fā)sql語(yǔ)句的執(zhí)行了
session.getTransaction().commit();
} catch (Exception e) {
e.printStackTrace();
session.getTransaction().rollback();
} finally {
hibernateUtil.closeSession(session);
}
}
// 主鍵策略是assigned的情況下,各種緩存引起的問(wèn)題
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);
// 這個(gè)時(shí)候如果設(shè)置id為null,commit的時(shí)候系統(tǒng)會(huì)拋異常??梢钥吹?,insetion和緩存中的id沒(méi)有發(fā)生變化,但是實(shí)際user對(duì)象的id變?yōu)榱?
// 這在執(zhí)行checkid操作的時(shí)候會(huì)報(bào)錯(cuò),系統(tǒng)就會(huì)拋出異常。
// user.setId(0);
// 如果這里選擇刪除當(dāng)前對(duì)象緩存,其實(shí)刪除的就是persistenceContext的對(duì)象,等到commit的時(shí)候,insertion操作檢查persistenceContext
// 中的對(duì)象緩存時(shí),發(fā)現(xiàn)沒(méi)有了,就會(huì)曝出異常:
// org.hibernate.AssertionFailure: possible nonthreadsafe access to
// session
// session.evict(user);
// 這里和uuid的方式一樣,在save和update的時(shí)候都是不發(fā)sql語(yǔ)句的,只有到了commit的時(shí)候,一次性的按照插入,更新,刪除的順序執(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);
}
}
}