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

时间:2022-09-18 18:12:19

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

概念介绍

 

我们先来看一些概念:

1.脏读:脏读又称为无效数据的读出,是指在数据库访问中,事物t1将某一值修改,然后事物t2读取该值,此后t1因为某种原因撤销对该值的修改,这就导致了t2所读取的数据是无效的。脏读就是指当一个事物正在访问数据,并且对数据进行了修改,而这种修改还没有提交到数据库中,这时,另外一个事物也访问这个数据,然后使用了这个数据。因为这个数据还是没有提交的数据,那么另外一个事物读到的这个数据就是脏数据,依据脏数据所做的操作是不正确的。

2.不可重复读:比如我在读一个帖子,我查出来的数据是张三、李四,然后我一刷新发现最开始的张三变成了张八,这就是所谓的不可重复读,因为我读出的数据没重复了嘛。

3.幻读:我在查数据的时候,开始查出来的记录为3条,我一刷新,发现记录变为了8条,这就是幻读。

4.提交读:提交了之后才可以读取,oracle默认就是这个,这种方式是不存在脏读的。

5.可重复度:很显然是和不可重复读相反的,它可以避免不可重复读,但是这个不能避免幻读。

6.序列化:这种方式非常严格,通俗的说就是,当我在做一件事情的时候,其他任何人都不能做,非常安全,但是效率极低。

隔离级别

 

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

下面我们通过实际的例子来体会hibernate清除缓存的应用。

hibernate映射数据库和主键的生成策略有关。

案例一

 

uuid的方式生成主键的例子:

java" id="highlighter_463825">
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?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>

测试方法:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
/**
  * 测试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代表数据库中是否有对应的数据)。如图:

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

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

2.调用完flush()方法之后,会清空session中的actionqueue的临时存储的值,然后将persistencecontext中的existsindatabase的值设为true,表示此时,数据库中有对应的数据,但是此时打开数据库打开表是看不到数据的,因为我们mysql数据库默认的隔离级别为提交读,即,必须提交才能读取数据,调用commit()方法之后,数据库中有数据。

案例二

 

native方式生成主键的例子:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?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>

测试方法:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
/**
  * 测试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主键生成策略的程序,在来一个测试方法:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
/**
  * 测试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属性,所以会出现错误,导致无法保存。

为此,我们改进上述程序:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
/**
  * 测试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()方法:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
/**
  * 测试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;

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

注:从图中,我们可以看出,mysql数据库默认的隔离级别为可重复读,也就是说不会出现不可重复读,即必须提交之后才能读。

2.修改mysql当前的隔离级别(假设修改为未提交读,即没有提交就可以读):

set transaction isolation level read uncommited;

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

总结

以上就是本文关于hibernate的session_flush与隔离级别代码详解的全部内容,希望对大家有所帮助。感兴趣的朋友可以继续参阅本站其他相关专题,如有不足之处,欢迎留言指出。感谢朋友们对本站的支持!

原文链接:http://blog.csdn.net/lzm1340458776/article/details/32729127