在介绍hibernate的缓存机制前,我们先了解一下什么是缓存:
缓存(Cache): 计算机领域非常通用的概念。里面放东西,说白了缓存就是一个集合。它介于应用程序和永久性数据存储源(如硬盘上的文件或者数据库)之间,其作用是降低应用程序直接读写永久性数据存储源的频率,从而提高应用的运行性能。缓存中的数据是数据存储源中数据的拷贝并且缓存的物理介质通常是内存。
了解jdbc的人都知道,当需要连接数据库时,一般都会做一个连接池,那么连接池和缓存有什么区别呢?
相同点:两者都可以是在内存里,实现时一样的,都是为了提高性能的。
不同点:连接池是一个重量级的池子,也就是说池子里面的资源是很宝贵的东西。
下面我们来了解一下hibernate中的缓存机制:一级缓存、二级缓存、查询缓存。
一级缓存:
1、在hibernate中,一个线程对应一个session,一个线程可以看成是一个session,也就是说session是和线程绑定在一起的。
2、理解一级缓存:
在session接口中包含了一系列的java接口,这些java集合构成了session级别的一级缓存,只要是session实例的生命周期没有结束,存放在其中的缓存对象就不会死亡。iterate、load、get、save等都使用使用一级缓存。
3、一级缓存的清理
session具有一个缓存,位于缓存中的对象称为持久化对象,他和数据库中的相关记录对应,session在某些时间点,按照缓存中对象的变化来执行相关的sql语句,来同步更新数据库,这一过程称为清理缓存,默认情况下session在以下时间点清理缓存:
(1)提交事务的时候,会先清理缓存session.flush();
(2)缓存中的持久化对象发生变化,会先清理缓存以保证持久化对象的最新状态。
(3)显示调用session.flush();
清理相关的知识点:
session.flush();会清理缓存,缓存中德持久化对象不会丢失,会产生insert语句。
session.clear()清空缓存,缓存中的持久化对象丢失。
session.reflush()让session和数据库同步,执行查询,把数据库的最新信息显示出来,更新本地缓存的对象状态.。
当session加载了一个对象后,回味该对象的值类型的属性复制一份快照,当清理缓存时,通过比较对象的当前属性和快照,来判断对象的那些属性发生了变化,
发生变化的执行sql语句,没有变化的不执行sql语句。。
在不使用refresh等的情况下,清理缓存时,要让一级缓存中的对象和快照中的对象进行对比,不同的话在提交的时候会产生updata语句。
4、其他知识点
iter = session.createQuery("from Student s where s.id<5").iterate();
while (iter.hasNext()) {
Student student = (Student)iter.next();
System.out.println(student.getName());
iterate在没有使用缓存的情况下会有n+1的问题。
第一次iterate查询会发出N+1条sql语句,第一条sql语句查询所有的id,然后根据id查询实体对象,有N个id就发出N条语句查询实体。
iterate查询不同的属性,一级缓存不会缓存,因为一级缓存是用来缓存实体对象的。
session间不能共有一级缓存,一级缓存会伴随着session的消亡而失效。
二级缓存:
1、二级缓存需要sessionFactory来管理,是进程级别的缓存,所有人都可以使用,是共享的。
2、二级缓存比较复杂,一般用第三方的产品,hibernate只提供了一个简单的实现,用hashtable实现的。
3、使用场合:长时间不改变的数据。
4、配置步骤:
(1)ehcache.xml 可以设置默认的,所有的类都遵循这个配置,也可以对某个对象单独的配置。
(2)在hibernate.cfg.xml配置文件配置缓存,让hibernate知道我们使用的是那个二级缓存。包括配置属性:是否启用二级缓存、二级缓存的提供商。
(3)手动指定哪些实体类的对象放到缓存,在hibernate.cfg.xml配置
<class-cache class="com.bjpowernode.hibernate.Student" usage="read-only"/>
或者
在映射文件中的id标签前面<cache usage="read-only"/>
usage属性表示使用缓存的策略,一般优先使用read-only,表示如果数据放到缓存,就不能再修改了,因为经常修改的数据也不需要放到缓存中。
read-only策略效率好,因为不能改缓存,但是可能出现脏数据的问题,这个问题的解决方案只能依赖缓存的超时,因为可能对象的数据被修改了,但是
缓存却没有变,这样造成数据不同步,也就是脏数据的问题。
read-write当持久化对象发生变化时,缓存里面就会跟着变化,数据库中也改变了,这种方式需要加上锁,效率比read-only慢。
5、知识点:
二级缓存必须让sessionfactory管理,让sessionfactory来清除,可以调用evict方法。
查询数据后会默认放到二级和一级缓存中,我们也可以控制查询出来的数据不放到缓存里面的,就是说我们可以控制一级缓存和二级缓存的交换。
session.setCacheMode(CacheMode.IGNORE);禁止将一级缓存中的数据往二级缓存里放。
和一级缓存一样,二级缓存也不存放普通属性的查询数据,这和一级缓存是一样的,只存放实体对象。
session级别的缓存对性能提高没有太大的意义,因为生命周期太短了。
查询缓存:
1、 一级缓存和二级缓存都只是存放实体对象的,如果查询实体对象的普通属性的数据,只能放到查询缓存里面,查询缓存还存放查询实体对象的id。
2、查询缓存的生命周期不确定,当它关联的表发生修改时(通过hibernate),查询缓存的生命周期就结束。
3、查询缓存默认是关闭的,可以在hibernate.cfg.xml配置<property name="hibernate.cache.use_query_cache">true</property>。并且必须在程序中手动启动查询缓存,在query接口中的setCacheable(true)方法来启用。
4、查询缓存意义不是很大,查询缓存说明白了就是存放由list方法或者iterate方法查询的数据,我们在查询时很少出现完全相同的条件查询,这就是说命中率低,
这样的缓存里的数据总是变化的。除非多次查询都是查询相同条件的数据,也就是说返回的结果总是一样,这样的缓存配置才有意义。