Hibernate的Session_flush与隔离级别代码详解

本文研究的主要是Hibernate的Session_flush与隔离级别,具体介绍和实例如下。

概念介绍

我们先来看一些概念:

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增删改查操作代码详解
  • Hibernate中获取Session的两种方式代码示例
  • hibernate通过session实现增删改查操作实例解析
  • 快速了解Hibernate中的Session
  • Hibernate三种状态和Session常用的方法
  • 简介Java的Hibernate框架中的Session和持久化类
  • Hibernate管理Session和批量操作分析
  • hibernate关于session的关闭实例解析
(0)

相关推荐

  • hibernate通过session实现增删改查操作实例解析

    今天学习了一下如何通过hibernate来实现对数据库的增删改查,下面来简单介绍一下: 首先创建个Student类: package com.hibernate.model; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.Id; import javax.persistence.T

  • hibernate关于session的关闭实例解析

    本文研究的主要是hibernate关于session的关闭,具体如下. Student student = new Student(); student.setName("Jan"); student.setAge("22"); student.setAddress("广东省肇庆市"); Session session =HibernateUtil.getSessionFactory().getCurrentSession(); session.

  • Hibernate中获取Session的两种方式代码示例

    Session:是应用程序与数据库之间的一个会话,是Hibernate运作的中心,持久层操作的基础.对象的生命周期/事务的管理/数据库的存取都与Session息息相关. Session对象是通过SessionFactory构建的,下面举个例子来介绍Hibernate两种获取session的方式. 日志,是编程中很常见的一个关注点.用户在对数据库进行操作的过程需要将这一系列操作记录,以便跟踪数据库的动态.那么一个用户在向数据库插入一条记录的时候,就要向日志文件中记录一条记录,用户的一系列操作都要在

  • Hibernate管理Session和批量操作分析

    本文详细分析了Hibernate管理Session和批量操作的用法.分享给大家供大家参考.具体分析如下: Hibernate管理Session Hibernate自身提供了三种管理Session对象的方法 ① Session对象的生命周期与本地线程绑定 ② Session对象的生命周期与JTA事务绑定 ③ Hibernate委托程序管理Session对象的生命周期 在Hibernate的配置文件中,hibernate.current_session_context_class属性用于指定Sess

  • Hibernate中Session增删改查操作代码详解

    把三状态转换图放在这,方便分析方法的作用: 1.Session的save()方法 Session是Hibernate所有接口中最重要的接口,提供了对数据保存,更新,查询和删除的方法. Session的save()方法可以使临时态或游离态转换为持久态.例如,保存一个Customer对象: SessionFactory sessionFactory; Configuration configuration = new Configuration().configure(); sessionFacto

  • Hibernate三种状态和Session常用的方法

    我们知道hibernate的核心就是对数据库的操作,里面的核心接口就是org.hibernate.Session接口.要想对数据库操作我们就要理清楚对象在整个操作中的所属的状态(Transient,Persistent,Detached).就像马士兵老师在视频中所说的,我们并不必死抠这些字眼,我们通过自己编写测试类就可以他们之间不同的区别. 其实三种状态各自的不必总结那么多,只是一个重要的地方就是Transient状态里面的对象是没有id的. session中常用的方法是save(),updat

  • 快速了解Hibernate中的Session

    Hibernate中的Session是一级缓存,可以理解为进程级(是线程吧)的缓存,在进程(是线程吧)运行期间一直存在. session可以理解为一个可以操作数据库的对象,该对象中有操作数据库的方法. 在Java里面,缓存通常是指Java对象的属性占用的内存空间,通常是一些集合类型的属性.在Session接口的实现类SessionImpl中定义了一系列的Java集合,这些Java集合就构成了Session的缓存. 总的来说(我对Session的理解):Session是Hibernate和DB的中

  • 简介Java的Hibernate框架中的Session和持久化类

    Session Session对象用于获取与数据库的物理连接. Session对象是重量轻,设计了一个互动是需要与数据库每次被实例化.持久化对象被保存,并通过一个Session对象中检索. 会话中的对象不应该保持开放很长一段时间,因为他们通常不被线程安全的,他们应该被创建并根据需要摧毁他们.这次会议的主要功能是提供创建,读取和删除操作映射的实体类的实例.实例中可能存在以下三种状态之一在给定时间点: 短暂性: 持久化类的未与会话相关联,并在数据库中没有代表性,没有标识值的新实例被Hibernate

  • Hibernate的Session_flush与隔离级别代码详解

    本文研究的主要是Hibernate的Session_flush与隔离级别,具体介绍和实例如下. 概念介绍 我们先来看一些概念: 1.脏读:脏读又称为无效数据的读出,是指在数据库访问中,事物T1将某一值修改,然后事物T2读取该值,此后T1因为某种原因撤销对该值的修改,这就导致了T2所读取的数据是无效的.脏读就是指当一个事物正在访问数据,并且对数据进行了修改,而这种修改还没有提交到数据库中,这时,另外一个事物也访问这个数据,然后使用了这个数据.因为这个数据还是没有提交的数据,那么另外一个事物读到的这

  • Hibernate初体验及简单错误排除代码详解

    Hibernate是什么,有多少好处,想必查找这类博文的都知道,所以就不多说了.下面是我对Hibernate简单使用的一个小小的总结.与君(主要是刚入门的)共勉吧! 创建的顺序 创建Hibernate的配置文件 创建持久化的类 创建对象-关系的映射文件 通过HibernateAPI编写访问数据库的代码 关于详细的步骤 导入Hibernate必须的jar包(hibernate-release-版本号.Final\lib\required) 然后是导入MySQL的jdbc的驱动(mysql-conn

  • Hibernate组件映射代码详解

    本文研究的主要是Hibernate组件映射的相关内容,具体如下. 组件关联映射的属性是复杂类型的持久化类,但不是实体类,即数据库中没有表与该属性对应,但该类的属性要持久保存. 比如:外国人的名字name分为firstName和lastName. MyName.java: public class MyName { private String firstName; private String lastName; public String getFirstName() { return fir

  • java面向对象设计原则之接口隔离原则示例详解

    目录 概念 实现 拓展 概念 小接口原则,即每个接口中不存在子类用不到却必须实现的方法,如果不然,就要将接口拆分.如下图所示定义了一个接口,包含了5个方法,实现类A用到了3个方法.实现类B用到了3个方法,类图如下: 类A没有方法4.方法5,却要实现它:类B没有方法2.方法3,但还是要实现这两个方法,不符合接口隔离原则.改造为将其拆分为三个接口,实现方式改为下图所示,符合接口隔离原则: 实现 面向对象机制中一个类可以实现多个接口,通过多重继承分离,通过接口多继承(实现)来实现客户的需求,代码更加清

  • TypeScript类型级别和值级别示例详解

    目录 对值级别编程类型级别编程区分 类型级编程 挑战是如何工作的 挑战 对值级别编程类型级别编程区分 首先,让我们对值级别编程和类型级别编程进行重要区分. 值级别编程让我们编写将在生产中运行的代码即运行期,并为我们的用户提供有用的东西. 类型级别编程帮助我们确保代码在发布之前即编译期不包含错误,在运行期会被完全删除 JavaScript没有类型,所以所有JavaScript都是值级别的代码: // A simple Javascript function: function sum(a, b)

  • spring boot application properties配置实例代码详解

    废话不多说了,直接给大家贴代码了,具体代码如下所示: # =================================================================== # COMMON SPRING BOOT PROPERTIES # # This sample file is provided as a guideline. Do NOT copy it in its # entirety to your own application. ^^^ # ========

  • Yii2中事务的使用实例代码详解

    前言 一般我们做业务逻辑,都不会仅仅关联一个数据表,所以,会面临事务问题. 数据库事务(Database Transaction) ,是指作为单个逻辑工作单元执行的一系列操作,要么完全地执行,要么完全地不执行. 事务处理可以确保除非事务性单元内的所有操作都成功完成,否则不会永久更新面向数据的资源.通过将一组相关操作组合为一个要么全部成功要么全部失败的单元,可以简化错误恢复并使应用程序更加可靠.一个逻辑工作单元要成为事务,必须满足所谓的ACID(原子性.一致性.隔离性和持久性)属性.事务是数据库运

  • Spring之ORM模块代码详解

    Spring框架七大模块简单介绍 Spring中MVC模块代码详解 ORM模块对Hibernate.JDO.TopLinkiBatis等ORM框架提供支持 ORM模块依赖于dom4j.jar.antlr.jar等包 在Spring里,Hibernate的资源要交给Spring管理,Hibernate以及其SessionFactory等知识Spring一个特殊的Bean,有Spring负责实例化与销毁.因此DAO层只需要继承HibernateDaoSupport,而不需要与Hibernate的AP

  • Java语言中的内存泄露代码详解

    Java的一个重要特性就是通过垃圾收集器(GC)自动管理内存的回收,而不需要程序员自己来释放内存.理论上Java中所有不会再被利用的对象所占用的内存,都可以被GC回收,但是Java也存在内存泄露,但它的表现与C++不同. JAVA中的内存管理 要了解Java中的内存泄露,首先就得知道Java中的内存是如何管理的. 在Java程序中,我们通常使用new为对象分配内存,而这些内存空间都在堆(Heap)上. 下面看一个示例: public class Simple { public static vo

随机推荐