Hibernate的Session_flush与隔离级别
来源:互联网 发布:最便宜的淘宝网 编辑:程序博客网 时间:2024/06/14 07:41
我们先来看一些概念:
1.脏读:脏读又称为无效数据的读出,是指在数据库访问中,事物T1将某一值修改,然后事物T2读取该值,此后T1因为某种原因撤销对该值的修改,这就导致了T2所读取的数据是无效的。脏读就是指当一个事物正在访问数据,并且对数据进行了修改,而这种修改还没有提交到数据库中,这时,另外一个事物也访问这个数据,然后使用了这个数据。因为这个数据还是没有提交的数据,那么另外一个事物读到的这个数据就是脏数据,依据脏数据所做的操作是不正确的。
2.不可重复读:比如我在读一个帖子,我查出来的数据是张三、李四,然后我一刷新发现最开始的张三变成了张八,这就是所谓的不可重复读,因为我读出的数据没重复了嘛。
3.幻读:我在查数据的时候,开始查出来的记录为3条,我一刷新,发现记录变为了8条,这就是幻读。
4.提交读:提交了之后才可以读取,Oracle默认就是这个,这种方式是不存在脏读的。
5.可重复度:很显然是和不可重复读相反的,它可以避免不可重复读,但是这个不能避免幻读。
6.序列化:这种方式非常严格,通俗的说就是,当我在做一件事情的时候,其他任何人都不能做,非常安全,但是效率极低。
隔离级别:
下面我们通过实际的例子来体会Hibernate清除缓存的应用。
Hibernate映射数据库和主键的生成策略有关。
案例一:UUID的方式生成主键的例子:
public class User {private String uid;private String uname;private Date birthday;public String getUid() {return uid;}public void setUid(String uid) {this.uid = uid;}public String getUname() {return uname;}public void setUname(String uname) {this.uname = uname;}public Date getBirthday() {return birthday;}public void setBirthday(Date birthday) {this.birthday = birthday;}}
User.hbm.xml:
<?xml version="1.0"?><!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"><!-- package表示实体类的包名 --> <hibernate-mapping package="com.lixue.bean"><!-- class结点的name表示实体的类名,table表示实体映射到数据库中table的名称 --><class name="User" table="t_user"><id name="uid"><!-- 通过UUID的方式生成 --><generator class="uuid"/></id><property name="uname"/><property name="birthday"/></class></hibernate-mapping>
测试方法:
/** * 测试uuid主键生成策略 */public void testSave1(){/*定义的Session和事物*/Session session = null;Transaction transaction = null;try {/*获取session和事物*/session = HibernateUtils.getSession();transaction = session.beginTransaction();/*创建用户*/User user = new User();user.setUname("习近平");user.setBirthday(new Date());/** * 因为User的主键生成策略为uuid,所以调用完save之后,只是将User纳入到Session管理 * 不会发出insert语句,但是ID已经生成,PersistenceContext中的existsInDatebase状态为false */session.save(user);/** * 调用flush,Hibernate会清理缓存(将session->insertions中临时集合中的对象插入数据库,在清空临时集合) * 此时并不能在数据库中看到数据,但是如果数据库的隔离级别设置为未提交读, * 那么我们可以看到flush过的数据,并且PersistenceContext中existsInDatabase状态为true */session.flush();/** * 提交事物 * 默认情况下,commit操作会执行flush清理缓存, * 所以不用显示的调用flush * commit后数据是无法回滚的 */transaction.commit();} catch (Exception e) {e.printStackTrace();transaction.rollback();} finally{HibernateUtils.closeSession(session);}}
我们可以通过断点调试程序:
1.由于User的主键生成侧率为UUID,调用save()方法之后,只能将User对象纳入Session管理,不会发出insert语句,但是ID已经生成了(注:save之后又两个地方很重要,首先是session->actionQueue->insertions->elementData数组中有某个元素存储了我们的对象,这是一个临时集合对象,另外还有一个就是PersistenceContext->EntityEntries->map->table->某个数组元素->value存储了该对象,value下面还有一个属性那就是existsInDatabase代表数据库中是否有对应的数据)。如图:
2.调用完flush()方法之后,会清空session中的actionQueue的临时存储的值,然后将PersistenceContext中的existsInDatabase的值设为true,表示此时,数据库中有对应的数据,但是此时打开数据库打开表是看不到数据的,因为我们MySQL数据库默认的隔离级别为提交读,即,必须提交才能读取数据,调用commit()方法之后,数据库中有数据。
案例二:native方式生成主键的例子:
public class User1 {private Integer uid;private String uname;private Date birthday;public Integer getUid() {return uid;}public void setUid(Integer uid) {this.uid = uid;}public String getUname() {return uname;}public void setUname(String uname) {this.uname = uname;}public Date getBirthday() {return birthday;}public void setBirthday(Date birthday) {this.birthday = birthday;}}
User1.hbm.xml:
<?xml version="1.0"?><!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"><!-- package表示实体类的包名 --> <hibernate-mapping package="com.lixue.bean"><!-- class结点的name表示实体的类名(赋值映射文件的时候要记得修改类名,否则会出现bug),table表示实体映射到数据库中table的名称 --><class name="User1" table="t_user1"><id name="uid"><!-- 自增长 --><generator class="native"/></id><property name="uname"/><property name="birthday"/></class></hibernate-mapping>
测试方法:
/** * 测试native主键生成策略 */public void testSave2(){/*定义的Session和事物*/Session session = null;Transaction transaction = null;try {/*获取session和事物*/session = HibernateUtils.getSession();transaction = session.beginTransaction();/*创建用户*/User1 user = new User1();user.setUname("李克强");user.setBirthday(new Date());/** * 因为User1的主键生成策略是native,所以调用Session.save()后,将执行insert语句,并且会清空临时集合对象 * 返回由数据库生成的ID,纳入Session的管理,修改了Session中existsInDatabase状态为true, * 如果数据库的隔离级别设置为未提交读,那么我们可以看到save过的数据 */session.save(user);transaction.commit();} catch (Exception e) {e.printStackTrace();transaction.rollback();} finally{HibernateUtils.closeSession(session);}}
通过断点调试程序:
1.由于主键的生成策略为native,所以调用save()方法之后,将执行insert语句,并且会清空临时集合对象中的数据,返回由数据库生成的ID。
2.将对象纳入session管理,修改了PersistenceContext中的existsInDatabase属性为true(表示数据库中有对应的数据,但是看不到,因为隔离界别的原因)
案例:我们再来测试一下Hibernate的另一个方法,那就是evict(),表示将对象从session逐出。
针对UUID主键生成策略的程序,在来一个测试方法:
/** * 测试uuid主键生成策略 */public void testSave3(){/*定义的Session和事物*/Session session = null;Transaction transaction = null;try {/*获取session和事物*/session = HibernateUtils.getSession();transaction = session.beginTransaction();/*创建用户*/User user = new User();user.setUname("胡锦涛");user.setBirthday(new Date());/** * 因为User的主键生成策略为uuid,所以调用完save之后,只是将User纳入到Session管理 * 不会发出insert语句,但是ID已经生成。Session中的existsInDatebase状态为false */session.save(user);/*将user对象从session中逐出,即从PersistenceContext的EntityEntries属性中逐出*/session.evict(user);/** * 无法成功提交,因为Hibernate在清理缓存时,在session的insertions临时集合中取出user对象进行insert * 操作后,需要更新entityEntries属性中的existsInDatabase为true,而我们调用了evict方法 * 将user从session的entityEntries中逐出了,所以找不到existsInDatabase属性,无法更新,抛出异常 */transaction.commit();} catch (Exception e) {e.printStackTrace();transaction.rollback();} finally{HibernateUtils.closeSession(session);}}
通过断点调试:
1.由于使用的是UUID的主键生成策略,所以调用save()方法之后,不会发送insert语句,只是将对象纳入了session管理,ID已经生成,数据库中没有与之对应的数据(即existsInDatabase属性值为false)。
2.调用evict()之后,将User对象从session中逐出,即从PersistenceContext的EntityEntries属性中逐出。
3.当我再调用commit()方法时,我们会发现,我们的数据保存不了,因为一开始我们的existsInDatabase属性为false,即数据库中不存在对应数据,紧接着我们又调用了evict()将PersistenceContext中的对象属性(existsInDatabase属性也包括在内)全删除了,但是actionQueue中的临时存储数据还没被删除。我们只调用commit()方法时会先隐式的调用flush()方法,这个方法的作用之前也讲过,它会将actionQueue中的临时对象进行insert操作,然后将PersistenceContext中的existsInDatabase属性值设为true,但很遗憾,PersistenceContext中并没有existsInDatabase属性,所以会出现错误,导致无法保存。
为此,我们改进上述程序:
/** * 测试uuid主键生成策略 */public void testSave4(){/*定义的Session和事物*/Session session = null;Transaction transaction = null;try {/*获取session和事物*/session = HibernateUtils.getSession();transaction = session.beginTransaction();/*创建用户*/User user = new User();user.setUname("胡锦涛");user.setBirthday(new Date());/** * 因为User的主键生成策略为uuid,所以调用完save之后,只是将User纳入到Session管理 * 不会发出insert语句,但是ID已经生成。PersistenceContext中的existsInDatebase状态为false */session.save(user);/** * flush后Hibernate会清理缓存,会将user对象保存到数据库中,将session中的insertions中的user对象 * 清除,并且设置PersistenceContext中existsInDatabase的状态为true */session.flush();/*将user对象从session中逐出,即从PersistenceContext的EntityEntries属性中逐出*/session.evict(user);/** * 可以成功提交,因为Hibernate在清理缓存时,在session的insertions集合中无法 * 找到user对象(调用flush时清空了),所以就不会发出insert语句,也不会更新session中的existsInDatabase的状态 */transaction.commit();} catch (Exception e) {e.printStackTrace();transaction.rollback();} finally{HibernateUtils.closeSession(session);}}
注:修改后的程序我们在save之后显示的调用了flush()方法,再调用evict()方法。
通过断点调试:
1.因为还是UUID的生成策略,所以在调用save之后,不会发出insert语句,只是将对象纳入session管理,PersistenceContext中的existsInDatabase属性为false。
2.调用完save()之后,我们又调用了flush()方法,这个方法的作用是清理缓存,即发出insert语句,将session中的insertions中的临时对象插入到数据库,然后清空该临时集合,并且将PersistenceContext中的existsInDatabase属性设置为true。
3.调用完flush()之后又调用evict()方法,它的作用是将user对象从session中清除,即清除PersistenceContext的EntityEntries属性。
4.调用完evict()方法之后又调用commit()方法,它的会隐式的先调用flush()方法,而flush的作用是清除缓存,即将session->insertions临时集合中的对象insert到数据库中,但是我们之前就调用了flush()方法(注:调用完这个方法之后会清空临时集合),所以临时集合根本就没有对象,所以不会发出insert语句。也不会去更新PersistenceContext中的existsInDatabase状态。可以成功提交。
案例:我们再来考虑下native方式的主键生成策略中使用evict()方法:
/** * 测试native主键生成策略 */public void testSave5(){/*定义的Session和事物*/Session session = null;Transaction transaction = null;try {/*获取session和事物*/session = HibernateUtils.getSession();transaction = session.beginTransaction();/*创建用户*/User1 user = new User1();user.setUname("马英九");user.setBirthday(new Date());/** * 因为User1的主键生成策略是native,所以调用Session.save()后,将执行insert语句, * 返回由数据库生成的ID,纳入Session的管理,修改了Session中existsInDatabase状态为true,并且清空了临时集合 * 如果数据库的隔离级别设置为未提交读,那么我们可以看到save过的数据 */session.save(user);/*将user对象从session中逐出,即从PersistenceContext的EntityEntries属性中逐出*/session.evict(user);/** * 可以成功提交,因为Hibernate在清理缓存的时候在session的insertions集合中 * 无法找到user对象,所以就不会发出insert语句,也不会更新session中的existtsInDatabase的状态 */transaction.commit();} catch (Exception e) {e.printStackTrace();transaction.rollback();} finally{HibernateUtils.closeSession(session);}}
通过调试:
1.由于主键生成策略为native,所以调用完save方法之后,马上就会发出insert语句,返回由数据库生成的ID,将对象纳入session管理,修改PersistenceContext中的existsInDatabase属性为true即数据库中有与之对应的数据,并且会清空临时集合中的对象。但是由于MySQL隔离级别的原因我们在没有提交之前是看不到数据的。
2.调用完save之后又调用evict()方法,将对象从session中逐出,即从PersistenceContext中的EntityEntries中逐出。
3.调用完evict()方法之后又调用commit()方法,此时是可以成功保存提交的,因为调用commit()之前会隐式调用flush()方法,即清理缓存,去临时集合中找对象insert到数据库,但是会发现临时集合中已经没有数据了,所以不会发出insert语句,也就不会去更新PersistenceContext中的existsInDatabase属性。
通过上述几个案例,我们可以看出,有时候我们需要显示的调用flush()方法,去清理缓存。另外,从上面我们也发现了一个问题,那就是当我们save()了数据,没提交之前是看不到数据的,即数据库的隔离界别限制了,现在我们来说说MySQL的隔离级别:
1.查看MySQL数据库当前的隔离级别:
select @@tx_isolation;
注:从图中,我们可以看出,MySQL数据库默认的隔离级别为可重复读,也就是说不会出现不可重复读,即必须提交之后才能读。
2.修改MySQL当前的隔离级别(假设修改为未提交读,即没有提交就可以读):
set transaction isolation level read uncommited;
- Hibernate的Session_flush与隔离级别
- Hibernate的Session_flush与隔离级别
- 与hibernate有关的数据库的隔离级别
- 细谈Hibernate数据库事务与隔离级别
- Hibernate Mysql事务与隔离级别
- Hibernate设置事务的隔离级别
- Hibernate事务隔离级别
- Hibernate 事物隔离级别
- hibernate隔离级别
- Hibernate 事务隔离级别
- SSH:Hibernate框架(Hibernate数据库事务与隔离级别)
- Hibernate中的Session的refresh()方法执行效果与数据库的隔离级别有关
- 数据库的锁与隔离级别
- 事务的传播特性与隔离级别
- 锁与隔离级别的关系
- 事务的实现,隔离级别与锁
- 数据库事务的特点与隔离级别
- spring的事务配置与隔离级别
- 折腾
- WebGIS 学习笔记(3) GeoServer 的层
- C# 流总结
- Surface、SurfaceView、SurfaceHolder及SurfaceHolder.Callback之间的关系
- virtual box 安装增强功能 (Ubuntu10.04)
- Hibernate的Session_flush与隔离级别
- hdoj1007
- 高性能通信设计实现中的一些共通点
- unity3d相关资源
- 一个MVC例子
- LeetCode | Reorder List
- 云中间层服务 - 区域感知负载均衡器 Ribbon
- 捉虫经历:weblogic 下调用 getServletContext() 方法报错
- 程序员长期保持身心健康的几点建议