一、复习数据库隔离级别
隔离级别 | 是否存在脏读 | 是否存在不可重复读 | 是否存在幻读 |
---|---|---|---|
Read UnCommitted | Y | Y | Y |
Read Committed | N | Y | Y |
Repeated Read | N | N | Y |
Serializable | N | N | N |
说明:
Read UnCommitted表示未提交读,即在未提交之前我们就可以读到数据,也就是脏读。Read Committed表示提交之后才能读到数据。Repeated Read表示可重复读。Serializable表示序列化读。
不可重复读的意思是说我们第一次读到数据,但是别人也能读到,而当别人将数据改变之后我刷新再读,发现两次读到的数据不一样了,这就是不可重复读。不能避免别人修改或添加。
幻读的意思是第一次根据相应的条件查出来了10条数据,但是此时别人又录入了数据,我再次查询发现同样条件可以查出来20条数据,两次结果不一样了。就是说避免不了别人添加,但是可以避免别人修改。
隔离级别由低到高,一般使用Read Committed这个级别,这个级别存在不可重复读取,但是我们可以使用相应的锁就可以避免了,而最后一种使用的极少,因为是序列化了,变成串行的了,并发性太差了。MySQL的默认隔离级别是
Repeated Read
,Oracle的默认级别是Read Committed
。
二、Session flush测试(工程hibernate_session_flush
)
SessionFlushTest.java
package junit.test;
import java.util.Date;
import org.hibernate.Session;
import org.hibernate.Transaction;
import org.junit.Test;
import cn.itcast.model.User1;
import cn.itcast.model.User2;
import cn.itcast.model.User3;
import cn.itcast.util.HibernateUtils;
public class SessionFlushTest {
//测试uuid主键生成策略
@Test
public void testSave1(){
Session session = HibernateUtils.getSession();
Transaction tx = null;
try{
tx = session.beginTransaction();
User1 user1 = new User1();
user1.setName("张三1");
user1.setPassword("123");
user1.setCreateTime(new Date());
user1.setExpireTime(new Date());
//这里因为user1的主键生成策略采用的是id,所以调用save方法后,只是将user1纳入到session的管理
//不会发出insert语句,但是id已经生成,session中existsInDatabase状态为false,表示在数据库中还不存在
session.save(user1);
//调用flush时hibernate会清理缓存,发出sql语句
//那么我们可以看到flush过的数据并且session中existsInDatabase状态为true,但是在数据库中看不到
//因为MySQL的默认隔离级别是Read Committed
//session.flush();
//默认情况下,commit操作会先执行flush清理缓存,所以不用显示的调用flush
//commit后数据是无法回滚的。
tx.commit();
}catch(Exception e){
e.printStackTrace();
tx.rollback();
}finally{
HibernateUtils.closeSession(session);
}
}
//测试native主键生成策略
@Test
public void testSave2(){
Session session = null;
Transaction tx = null;
try {
session = HibernateUtils.getSession();
tx = session.beginTransaction();
User2 user2 = new User2();
user2.setName("张三");
user2.setPassword("123");
user2.setCreateTime(new Date());
user2.setExpireTime(new Date());
//因为主键生成策略是native,所以调用save方法后,将立刻发出sql语句,返回由数据库生成的id
//纳入session的管理,修改了session中existsInDatabase的状态为true
//如果数据库的隔离级别设置为Read Committed,那么那么我们是看不到save添加过的数据
session.save(user2);
tx.commit();
} catch (Exception e) {
e.printStackTrace();
tx.rollback();
}finally{
HibernateUtils.closeSession(session);
}
}
//测试uuid主键生成策略
@Test
public void testSave3(){
Session session = HibernateUtils.getSession();
Transaction tx = null;
try{
tx = session.beginTransaction();
User1 user1 = new User1();
user1.setName("张三2");
user1.setPassword("123");
user1.setCreateTime(new Date());
user1.setExpireTime(new Date());
session.save(user1);
//这里如果我们事先进行手工刷新,会将user1对象保存到数据库中,将sess中的insertions中的user1对象
//清除,并且设置existsInDatabase为true,那么此时是可以成功提交的。
//session.flush();
//将user1对象从session中逐出,即session的EntityEntries对象中逐出
session.evict(user1);
//此时是无法成功提交的,因为hibernate在清理缓存时,在session的insertions集合中取出user1对象进行
//insert操作后,需要更新EntityEntries对象中的existsInDatabase为true,而我们已经将user1对象逐出
//所以找不到相关数据,无法更新,抛出异常。
tx.commit();
}catch(Exception e){
e.printStackTrace();
tx.rollback();
}finally{
HibernateUtils.closeSession(session);
}
}
//测试native主键生成策略
@Test
public void testSave4(){
Session session = null;
Transaction tx = null;
try {
session = HibernateUtils.getSession();
tx = session.beginTransaction();
User2 user2 = new User2();
user2.setName("张三");
user2.setPassword("123");
user2.setCreateTime(new Date());
user2.setExpireTime(new Date());
session.save(user2);
//这里就算将对象逐出,但是这种主键生成策略中在使用save方法后默认是执行flush的,即
//已经将数据保存到数据库中了,还是可以成功提交的
session.evict(user2);
tx.commit();
} catch (Exception e) {
e.printStackTrace();
tx.rollback();
}finally{
HibernateUtils.closeSession(session);
}
}
//测试assigned主键生成策略
@Test
public void testSave5(){
Session session = null;
Transaction tx = null;
try {
session = HibernateUtils.getSession();
tx = session.beginTransaction();
User3 user3 = new User3();
user3.setId("001");
user3.setName("张三");
session.save(user3);
user3.setName("王五");
session.update(user3);
User3 user4 = new User3();
user4.setId("002");
user3.setName("李四");
session.save(user4);
/*
* Hibernate: insert into _user3 (name, password, createTime, expireTime, id) values (?, ?, ?, ?, ?)
Hibernate: insert into _user3 (name, password, createTime, expireTime, id) values (?, ?, ?, ?, ?)
Hibernate: update _user3 set name=?, password=?, createTime=?, expireTime=? where id=?
hibernate会按照save(insert)、update、delete的顺序提交相关的操作
* */
tx.commit();
} catch (Exception e) {
e.printStackTrace();
tx.rollback();
}finally{
HibernateUtils.closeSession(session);
}
}
//测试assigned主键生成策略
@Test
public void testSave6(){
Session session = null;
Transaction tx = null;
try {
session = HibernateUtils.getSession();
tx = session.beginTransaction();
User3 user3 = new User3();
user3.setId("003");
user3.setName("张三");
session.save(user3);
user3.setName("王五");
session.update(user3);
//如果手动进行刷新,那么会hibernate会按照我们的操作顺序执行相关的操作
session.flush();
User3 user4 = new User3();
user4.setId("004");
user3.setName("李四");
session.save(user4);
tx.commit();
} catch (Exception e) {
e.printStackTrace();
tx.rollback();
}finally{
HibernateUtils.closeSession(session);
}
}
}
说明:
1.从测试方法1中我们可以看到使用uuid主键生成策略在使用save方法的时候不会发出相关的sql语句,只有在提交的时候清理缓存时才会发出相应的sql语句。当然我们可以在使用save方法之后手动进行flush清理缓存,那样是会发出sql语句的。但是这样即使我们保存在数据库中了,在提交之前还是会因为数据库的隔离级别的问题而看不到数据。
2.从测试方法2中我们可以看到使用native这种主键生成策略在使用save方法时是会立即清理缓存,发出相关sql语句的。同时save方法执行之后提交之前同样是看不到数据的。还要注意此时实体类中id的类型是int型而不是String类型。
3.测试方法3和4再次说明了这个问题,即uuid主键生成方式中不会自动刷新,而native主键生成方式则会自动刷新。
4.测试方法5和6中采用的自己分配主键,此时hibernate会按照save(insert)、update、delete的顺序提交相关的操作,而不会自动刷新,当然如果我们使用手动刷新,那么hibernate会按照我们自己的顺序执行相关的操作。
最后:
- flush方法主要做清理缓存和执行sql这两个工作;
- session在下列情况下执行flush:1.默认在事务提交的时候;2.显式调用flush方法时;3.在执行查询前,如:iterate