一、session缓存
在session接口的实现中包含一系列的Java集合,这些Java集合构成了Session缓存,只要Session实例没有结束生命周期,且没有清理缓存,则存放在它缓存中的对象也不会结束生命周期。
Session缓存可减少Hibernate应用程序访问数据库的频率,这也正是提高响应效率的最根本原因。实际上session就是hibernate的一级缓存,sessionFactory则为hibernate的二级缓存。
二、session缓存的存在证明
@Test public void sessionCacheTest(){ Student student1=(Student) session.get(Student.class, 1); System.out.println(student1); Student student2=(Student) session.get(Student.class, 1); System.out.println(student2); System.out.println(student1==student2); }
结果截图:
这段程序解释:当第一次调用session.get()时,会先去缓存中找,如果没有则去数据库中找,并且将找出来的数据放入session缓存中,当第二次session.get()和第一个同一个对象时,又先去缓存找,如果缓存中存在,则直接将缓存中的引用给它,这里面明显缓存中存在,所以这时不会再去数据库中取数据了,因为缓存中的数据和数据库中的数据是一个对象。
三、操作session缓存的方法
3.1flush()
flush()方法使得数据库中的数据和session缓存中的数据保持一致。如果数据库中的数据和session缓存中的数据不一致(仅仅是在不一致的情况下才发送更新sql),则会发送sql语句去更新数据库与缓存中的数据保持一致。
flush()有可能会发送sql语句,但是不会去主动提交事务,提交事务是由docommit()进行提交的。
注意:在未提交事务或显示的调用session.flush()方法之前,也有可能会进行flush()操作。
比如当执行HQL或QBC查询,会先进行flush()操作,以得到数据库表的最新的记录。
若记录的ID是由底层数据库使用自增的方式生成的,则在调用save()方法后,就会立即发送insert语句,因为save方法后,必须保证对象的ID是存在的!若记录的ID是由hibernate为我们生成的则会走到commit()方法时才会发送sql语句。
Session按照缓存中对象的属性变化来同步更新数据库
默认情况下Session在一下时间点刷新缓存:
1.显示调用Session的flush()方法
2.当应用程序调用Transaction的commit()方法时,该方法先flush(),然后在向数据库提交事务
3.当应用程序执行一些查询(HQL Criteria)操作时,如果缓存中持久化对象的属性已经发生变化,会先flush()缓存以保证查询结果能够反映持久化对象的最新状态。
flush缓存的例外情况:
如果对象使用native生成器生成OID,那么当调用Session的sava()方法保存对象时,会立即执行向数据库插入该实体的insert语句。
commit()和flush()方法的区别:
flush执行一系列sql语句,但不提交事务;commit()方法先调用flush()方法,然后提交事务,意味着对数据库操作永久保存下来。
设定刷新缓存的时间点
若希望改变flush的默认时间点,可以通过Session的setFlushMode()方法显式设定flush的时间点。
@Test public void sessionFlushTest(){ Student student=(Student) session.get(Student.class, 1); student.setStuAge(21); }
运行结果:
这段代码是将一个对象加载到session缓存中,然后改了年龄这个属性,改完之后事务提交之前会默认调用flush()方法(根据跟源码所知),所以此时数据库中的数据和session缓存中的数据明显不一致了,所以会发送sql去更新数据库,以保持数据库中的数据记录与对应的缓存中的记录保持一致。
注意:在未提交事务或显示的调用session.flush()方法之前,也有可能会进行flush()操作。
比如当执行HQL或QBC查询,会先进行flush()操作,以得到数据库表的最新的记录。
若记录的ID是由底层数据库使用自增的方式生成的,则在调用save()方法后,就会立即发送insert语句,因为save方法后,必须保证对象的ID是存在的!但如果这个ID是由hibernate为我们生成的,那么在commit()时才会发送sql语句。
3.2refresh()
session的refresh()方法会强制缓存中的数据和数据库中的数据保持一致,调用此方法,会向数据库发送一条查询语句以获取数据库中数据的最新状态,调用这个方法之前需要注意mysql的隔离级别。接下来我们说下。测试用断点+手动改数据库字段的方式。
@Test public void sessionRefreshTest(){ Student student=(Student) session.get(Student.class, 1); System.out.println(student); session.refresh(student); System.out.println(student); }
如果采用mysql默认事务隔离级别,那么refresh()前的student和之后的student的状态一致,但是当把mysql的事务隔离级别改为读已提交,那么我们可以通过断点+手动改字段的方式可知refresh()之后读取的是我们改之后的,也就是数据库中的最新状态。当然也可以通过hibernate.cfg.xml中设置事务的隔离级别,其配置为:
<!-- 设置数据库的隔离级别 --> <property name="connection.isolation">2</property>
数据库的隔离级别
对于同时运行的多个事务,当这些事务访问数据库中相同的数据时,如果没有采取必要的隔离机制,就会导致各种并发问题
1.脏读:对于两个事务T1,T2,若T1读取了已经被T2更新但还没有被提交的字段之后,若T2回滚,T1读取的内容就是临时无效的。
2.不可重复读:对于两个事务T1,T2,若T1读取了一个字段,然后T2更新了该字段,之后T1再次读取同一个字段,值就不同了。
3.幻读:对于两个事务T1,T2若T1从一个表中读取了一个字段,然后T2在该表中插入了一些新的行,之后如果T1再次读取同一个表,就会多出几行。
数据库事务的隔离性:数据库系统必须具有隔离并发运行的各个事务的能力,使它们不会相互影响,避免各种并发问题。
一个事务与其他事务隔离的程度称为隔离级别,数据库规定了多种事务隔离级别,不同隔离级别对应不同干扰程度,隔离级别越高,数据一致性就越好,但并发性越弱。
数据库提供的4中事务隔离级别:
1.READ UNCOMMITED(读未提交数据):允许事务读取未被其他事务提交的变更,脏读,不可重复读和幻读的问题都会出现。
2.READ COMMITED(读已提交数据):只允许事务读取已经被其它事务提交的变更,可以避免脏读,但不可重复读和幻读任然可能出现。
3.REPEATABLE READ(可重复读):确保事务可以多次从一个字段中读取相同的值,在这个事务持续期间,禁止其它事务对这个字段进行更新,可以避免脏读和不可重复读,但幻读的问题任然存在。
4.SERIALLZABLE(串行化):确保事务可以从一个表中读取相同的行,在这个事务持续期间,禁止其他事务对该表执行插入,更新和删除操作,所有并发问题都可以避免,但性能十分低下。
Oracle支持的2中事务隔离级别:READ COMMITED,SERIALLZABLE。Oracle默认的事务隔离级别为:READ COMMITED
Mysql支持4种事务隔离级别,默认事务隔离级别为REPEATABLE READ
在Mysql中设置隔离级别
每启动一个mysql程序,就会获得一个单独的数据库连接,每个数据库连接都有一个全局变量@@tx_isolation,表示当前的事务隔离级别,Mysql默认的隔离级别为:Repeatable Read
查看你当前的隔离级别:select @@tx_isolation;
设置当前mysql连接的隔离级别:
settransaction isolation level read committed;
设置数据库系统的全局的隔离级别:
set global transaction isolation level read committed;
在hibernate中设置隔离级别
jdbc数据库连接使用数据库系统默认的隔离级别,在hibernate的配置文件中以显示的设置隔离级别,没一个隔离级别都对应一个整数:
1.READ UNCOMMITED
2.READ COMMITED;
3.REPAEATABLE READ;
4.SERIALLZEBLE
hibernate通过Hibernate映射文件制定hibernate.connection.isolation属性来设置事务的隔离级别。
3.3clear()清理缓存
session的clear()方法可以清空session缓存中的所有数据。
@Test public void sessionClearTest(){ Student student1=(Student) session.get(Student.class, 1); session.clear(); Student student2=(Student) session.get(Student.class, 1); }
之前我们知道session的get方法查同一标识符对象时只会发送一条select sql语句,之后无论查多少次,在session没有关闭的情况下,都会从缓存中取数据,如果我们在中间加入清理缓存的方法clear(),那么我们可以明显的看出来缓存中没有数据时又向数据库中发送了select 的查询sql语句。