hibernate的flush和操作方式
操作hibernate不是直接操作数据库,hibernate在其中又加入一层缓存,而正是因为这层缓存的原因,很多时候我们需要认真去分析我们的sql执行顺序,不要引起意外情况,下面这张图是描述了uuid和native两种主键策略下,各种操作的流程:
- 当执行save/update/delete等操作后,hibernate并不是直接生成sql语句执行,而是先把操作存入actionQueue的相应队列中,然后再把当前操作对象缓存到persistenceContext中
- 只有主键需要数据库生成时,在做save等操作的时候,才会直接发出sql语句去数据库中执行
- 在commit或者flush执行时,要检查User对象,persistenceContext的User对象缓存以及actionQueue中的对象引用,数据上是否一致,没有出现单独被修改的情况,否则会抛出异常
actionQueue和persistenceContext的具体位置请参看下图,网络上说的有些路径和我的不一致,有可能是版本的问题,请自行检查。
接下来就是对各种主键策略的代码测试,具体测试内容,请看代码中的注释:
实体类:
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之前,系统会不会自动清理缓存,包括各种异常情况测试
*
* @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缓存对象
// 但这时候,并没有发出sql语句,系统不会自动flush
session.save(user);
// 只有commit的时候,系统会自动flush,并且发出sql语句,真正实现持久化
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缓存对象
// 但这时候,并没有发出sql语句,系统不会自动flush
session.save(user);
// 这个时候如果设置id为null,会提示错误:
// identifier of an instance of entity.User was altered from
// 402881e65b956dbc015b956e88d00001 to null
// 这表示在persistenceContext中登记的user对象,和实际的user对象在执行checkId操作的时候对不上了,因此曝出异常
// user.setId(null);
// 如果这里选择删除当前对象缓存,其实删除的就是persistenceContext的对象,等到commit的时候,insertion操作检查persistenceContext
// 中的对象缓存时,发现没有了,就会曝出异常:
// org.hibernate.AssertionFailure: possible nonthreadsafe access to
// session
// session.evict(user);
// 如果我们更新一下user对象,然后在新建一个新的user1对象,看看实际的sql执行过程,是不是和我们代码的顺序不一样
// 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("谨言");
// 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());
// 当id策略是native的时候,save函数必须从数据库中获取主键,因此当save后就会直接发出sql语句,而不是等待flush
// 这时候查看对象的状态existsInDatabase是true,并且insertion队列中已经没有待执行的操作了
session.save(user);
// 这里执行commit的时候,就不会引发sql语句的执行了
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的执行顺序是什么样的?结果发现和uuid的时候一样,还是先执行了insert,然后再执行update
// 这是怎么回事呢?
// 原因是:只有save是立刻发出sql语句的,因为它需要数据库分配主键,但是update不需要立刻去数据库执行,因此他还是按照hibernate的方式执行
// 因此,当执行到update函数的时候,它还是会被暂时记录到更新操作队列中的,当commit的时候,按照插入,更新,删除等顺序,一个个的执行。
// 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执行过程,是不是和我们代码的顺序不一样
// user.setUserName("谨言");
// 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的主键策略,因为主键是自己在代码中指定的,因此不需要去数据库中生成主键,因此当save之后,并没有
// 直接发出sql,而是按照管理在insertion中添加了操作记录,并且在persistenceContext生成了缓存对象
// 这里执行commit的时候,就不会引发sql语句的执行了
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);
// 这个时候如果设置id为null,commit的时候系统会抛异常。可以看到,insetion和缓存中的id没有发生变化,但是实际user对象的id变为了0
// 这在执行checkid操作的时候会报错,系统就会抛出异常。
// user.setId(0);
// 如果这里选择删除当前对象缓存,其实删除的就是persistenceContext的对象,等到commit的时候,insertion操作检查persistenceContext
// 中的对象缓存时,发现没有了,就会曝出异常:
// org.hibernate.AssertionFailure: possible nonthreadsafe access to
// session
// session.evict(user);
// 这里和uuid的方式一样,在save和update的时候都是不发sql语句的,只有到了commit的时候,一次性的按照插入,更新,删除的顺序执行
// 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("谨言");
// 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);
}
}
}