一、缓存(Cache):计算机领域非常通用的概念。它介于应用程序和永久性数据存储源(如硬盘上的文件或者数据库)之间,其作用是降低应用程序直接读写永久性数据存储源的频率,从而提高应用的运行性能。缓存中的数据是数据存储源中数据的拷贝。缓存的物理介质通常是内存或者硬盘
二、Hibernate中提供了两个级别的缓存
1、第一级别的缓存是 Session级别的缓存,它是属于事务范围的缓存。这一级别的缓存由hibernate管理的
2、第二级别的缓存是 SessionFactory级别的缓存,它是属于进程范围的缓存
注意:本篇的测试环境使用--HIbernate之HQL所搭建的环境
一级缓存:
缓存范围:缓存只能被当前Session对象访问。缓存的生命周期依赖于Session的生命周期,当Session被关闭后,缓存也就结束生命周期。
@Test public void testHibernateFirstLevelCache(){ Employee emp=(Employee) session.get(Employee.class, 8); System.out.println(emp.getName()); Employee emp1=(Employee) session.get(Employee.class, 8); System.out.println(emp1.getName()); }控制台信息:
Hibernate: select employee0_.ID as ID1_1_0_, employee0_.NAME as NAME2_1_0_, employee0_.SALARY as SALARY3_1_0_, employee0_.EMAIL as EMAIL4_1_0_, employee0_.DEPT_ID as DEPT_ID5_1_0_ from EMPLOYEE employee0_ where employee0_.ID=? name6 name6在执行第二个查询的时候并没有发出SQL语句到数据库查询,而是直接从session缓存中读取。
Hibernate一些与一级缓存相关的操作(时间点):
数据放入缓存:
1. save()。当session对象调用save()方法保存一个对象后,该对象会被放入到session的缓存中。
2. get()和load()。当session对象调用get()或load()方法从数据库取出一个对象后,该对象也会被放入到session的缓存中。
3. 使用HQL和QBC等从数据库中查询数据。
二级缓存:
1.SessionFactory级别的缓存,可以跨越Session存在,可以被多个Session所共享
2.SessionFactory的缓存可以分为两类:
–内置缓存: Hibernate 自带的, 不可卸载.通常在Hibernate的初始化阶段,Hibernate 会把映射元数据和预定义的 SQL语句放到SessionFactory的缓存中,映射元数据是映射文件中数据(.hbm.xml文件中的数据)的复制.该内置缓存是只读的.
–外置缓存(二级缓存):一个可配置的缓存插件.在默认情况下,SessionFactory不会启用这个缓存插件.外置缓存中的数据是数据库数据的复制,外置缓存的物理介质可以是内存或硬盘
3.适合放到二级缓存中:
(1)经常被访问
(2)改动不大
(3)数量有限
(4)不是很重要的数据,允许出现偶尔并发的数据。
这样的数据非常适合放到二级缓存中的。
4. 二级缓存的并发访问策略:-两个并发的事务同时访问持久层的缓存的相同数据时,也有可能出现各类并发问题.-二级缓存可以设定以下 4 种类型的并发访问策略,每一种访问策略对应一种事务隔离级别:
–非严格读写(Nonstrict-read-write):不保证缓存与数据库中数据的一致性. 提供Read Uncommited事务隔离级别,对于极少被修改,而且允许脏读的数据,可以采用这种策略–读写型(Read-write):提供ReadCommited数据隔离级别.对于经常读但是很少被修改的数据,可以采用这种隔离类型,因为它可以防止脏读–事务型(Transactional):仅在受管理环境下适用.它提供了 RepeatableRead事务隔离级别. 对于经常读但是很少被修改的数据,可以采用这种隔离类型,因为它可以防止脏读和不可重复读–只读型(Read-Only):提供 Serializable数据隔离级别,对于从来不会被修改的数据,可以采用这种访问策略
使用Hibernate的二级缓存:
1.选择合适的缓存插件: EHCache(jar包和配置文件), 并编译器配置文件
a、将hibernatehibernate-release-4.2.5.Finalhibernate-release-4.2.5.Finalliboptionalehcache路径下的所有jar包加入到工程的lib目录,并BuildPath!
ehcache-core-2.4.3,jar
hibernate-ehcache-4.2.5.Final.jar
slf4j-api-1.6.1.jar
b、将hibernatehibernate-release-4.2.5.Finalhibernate-release-4.2.5.Finalprojectetc路径下的ehcache.xml配置文件加入到工程的类路径下
2. 修改Hibernate的配置文件:hibernate.cfg.xml ,加入如下配置:
1、 开启二级缓存:
<!-- 启用二级缓存 --> <property name="cache.use_second_level_cache">true</property>2、指定使用的二级缓存产品:
<!-- 配置使用的二级缓存产品 --> <property name="hibernate.cache.region.factory_class">org.hibernate.cache.ehcache.EhCacheRegionFactory</property>3、指定使用二级缓存的持久化类
<!-- 对那个持久化类启用二级缓存 --> <class-cache usage="read-write" class="com.elgin.hibernate.entity.Employee"/>配置这一步的时候需要注意:class-cache元素最好放到mapping元素的下面,否则会报xml解析错误:元素类型为 "session-factory" 的内容必须匹配 "(property*,mapping*,(class-cache|collection-cache)*,event*,listener*)"
实际上这个配置也可以配置在持久化类对应的hbm配置文件中。
*******************************二级缓存测试开始**************************
未使用二级缓存的测试:
@Test public void testHibernateSecondLevelCache(){ Employee emp=(Employee) session.get(Employee.class, 8); System.out.println(emp.getName()); transcation.commit(); session.close(); session=sessionFactory.openSession(); transcation=session.beginTransaction(); Employee emp1=(Employee) session.get(Employee.class, 8); System.out.println(emp1.getName()); }运行之后,控制台显示:
Hibernate: select employee0_.ID as ID1_1_0_, employee0_.NAME as NAME2_1_0_, employee0_.SALARY as SALARY3_1_0_, employee0_.EMAIL as EMAIL4_1_0_, employee0_.DEPT_ID as DEPT_ID5_1_0_ from EMPLOYEE employee0_ where employee0_.ID=? name6 Hibernate: select employee0_.ID as ID1_1_0_, employee0_.NAME as NAME2_1_0_, employee0_.SALARY as SALARY3_1_0_, employee0_.EMAIL as EMAIL4_1_0_, employee0_.DEPT_ID as DEPT_ID5_1_0_ from EMPLOYEE employee0_ where employee0_.ID=? name6通过控制台结果可以看出:由于第二次查询之前使用了新的session,缓存中无数据,并且未开启二级缓存,所以重新发出了SQL语句进行了查询。
@Test public void testHibernateSecondLevelCache(){ Employee emp=(Employee) session.get(Employee.class, 8); System.out.println(emp.getName()); transcation.commit(); session.close(); session=sessionFactory.openSession(); transcation=session.beginTransaction(); Employee emp1=(Employee) session.get(Employee.class, 8); System.out.println(emp1.getName()); }控制台打印显示:
Hibernate: select employee0_.ID as ID1_1_0_, employee0_.NAME as NAME2_1_0_, employee0_.SALARY as SALARY3_1_0_, employee0_.EMAIL as EMAIL4_1_0_, employee0_.DEPT_ID as DEPT_ID5_1_0_ from EMPLOYEE employee0_ where employee0_.ID=? name6 name6通过与未使用的结果对比发现,第二次并没有发出SQL语句,而是直接使用,这就是Hibernate的二级缓存起作用了!
Hibernate集合级别的二级缓存配置:
1. 在上述hibernate配置文件的基础上新增如下配置:
<class-cache usage="read-write" class="com.elgin.hibernate.entity.Department"/> <collection-cache usage="read-write" collection="com.elgin.hibernate.entity.Department.emps"/>很明显上面的一句是开启持久化类Department的二级缓存,下面是开启Department下名为emps的集合属性开启二级缓存
注意:开启集合属性二级缓存的同时,一定要为集合中保存的持久化类开启二级缓存(本例中为Employee类,上述配置已开启,直接测试)
@Test public void testCollectionSecondLevelCache(){ Department dept=(Department) session.get(Department.class, 6); System.out.println(dept.getName()); System.out.println(dept.getEmps().size()); transcation.commit(); session.close(); session=sessionFactory.openSession(); transcation=session.beginTransaction(); Department dept1=(Department) session.get(Department.class, 6); System.out.println(dept1.getName()); System.out.println(dept1.getEmps().size()); }控制台信息:
Hibernate: select department0_.ID as ID1_0_0_, department0_.NAME as NAME2_0_0_ from DEPARTMENT department0_ where department0_.ID=? 测试部 Hibernate: select emps0_.DEPT_ID as DEPT_ID5_0_1_, emps0_.ID as ID1_1_1_, emps0_.ID as ID1_1_0_, emps0_.NAME as NAME2_1_0_, emps0_.SALARY as SALARY3_1_0_, emps0_.EMAIL as EMAIL4_1_0_, emps0_.DEPT_ID as DEPT_ID5_1_0_ from EMPLOYEE emps0_ where emps0_.DEPT_ID=? 2 测试部 2从上述信息可以看出先查询dept,并且dept中的emps属性使用了延迟加载。在使用新的session查询时,并没有发出SQL语句,而是直接使用,这就是配置的集合缓存的作用!
二级缓存配置文件ehcache.xml的配置
<ehcache> <!-- Sets the path to the directory where cache .data files are created. If the path is a Java System Property it is replaced by its value in the running VM. The following properties are translated: user.home - User's home directory user.dir - User's current working directory java.io.tmpdir - Default temp file path 指定一个目录,当 EHCache 把数据写到硬盘上时, 将把数据写到这个目录下. --> <diskStore path="java.io.tmpdir"/>
<!--Default Cache configuration. These will applied to caches programmatically created through the CacheManager. The following attributes are required for defaultCache: maxInMemory - Sets the maximum number of objects that will be created in memory eternal - Sets whether elements are eternal. If eternal, timeouts are ignored and the element is never expired. timeToIdleSeconds - Sets the time to idle for an element before it expires. Is only used if the element is not eternal. Idle time is now - last accessed time timeToLiveSeconds - Sets the time to live for an element before it expires. Is only used if the element is not eternal. TTL is now - creation time overflowToDisk - Sets whether elements can overflow to disk when the in-memory cache has reached the maxInMemory limit. 设置缓存的默认数据过期策略 --> <defaultCache maxElementsInMemory="10000" eternal="false" timeToIdleSeconds="120" timeToLiveSeconds="120" overflowToDisk="true" /> <!-- 设定具体的命名缓存的数据过期策略。每个命名缓存代表一个缓存区域 缓存区域(region):一个具有名称的缓存块,可以给每一个缓存块设置不同的缓存策略。 如果没有设置任何的缓存区域,则所有被缓存的对象,都将使用默认的缓存策略。即:<defaultCache.../> Hibernate在不同的缓存区域保存不同的类/集合。 对于类而言,区域的名称是类名。如:com.elgin.hibernate.entity.Employee 对于集合而言,区域的名称是类名加属性名。如com.elgin.hibernate.entity.Department.emps --> <!-- name:设置缓存的名字,它的取值为类的全限定名或类的集合的名字 maxElementsInMemory:设置基于内存的缓存中可存放的对象最大数目 eternal:设置对象是否为永久的,true表示永不过期,此时将忽略timeToIdleSeconds 和 timeToLiveSeconds属性; 默认值是false timeToIdleSeconds:设置对象空闲最长时间,以秒为单位, 超过这个时间,对象过期。当对象过期时,EHCache会把它从缓存中清除。 如果此值为0,表示对象可以无限期地处于空闲状态。 timeToLiveSeconds:设置对象生存最长时间,超过这个时间,对象过期。如果此值为0,表示对象可以无限期地存在于缓存中. 该属性值必须大于或等于 timeToIdleSeconds 属性值 overflowToDisk:设置基于内存的缓存中的对象数目达到上限后,是否把溢出的对象写到基于硬盘的缓存中 --> <cache name="com.elgin.hibernate.entity.Employee" maxElementsInMemory="10000" eternal="false" timeToIdleSeconds="300" timeToLiveSeconds="600" overflowToDisk="true" /> <cache name="com.elgin.hibernate.entity.Department.emps" maxElementsInMemory="1000" eternal="true" timeToIdleSeconds="0" timeToLiveSeconds="0" overflowToDisk="false" /> </ehcache>
Hibernate二级缓存的查询缓存
1. 默认情况下,设置的二级缓存对HQL查询和QBC查询是无效的, 可以通过如下设置使其生效:
a、在hibernate的配置文件hibernate.cfg.xml中增加如下配置,声明开启查询缓存
<property name="cache.use_query_cache">true</property>b、在代码中 调用Query或者Criteria的setCacheable(true)方法
2. 查询缓存依赖于二级缓存,如果想要使用,必须先配置Hibernate的二级缓存
测试代码:@Test public void testQueryCache(){ String hql="from Employee"; Query query=session.createQuery(hql); query.setCacheable(true); List<Employee> emps=query.list(); System.out.println(emps.size()); List<Employee> emps1=query.list(); System.out.println(emps1.size()); }控制台显示:
Hibernate: select employee0_.ID as ID1_1_, employee0_.NAME as NAME2_1_, employee0_.SALARY as SALARY3_1_, employee0_.EMAIL as EMAIL4_1_, employee0_.DEPT_ID as DEPT_ID5_1_ from EMPLOYEE employee0_ 20 20可以看出,第二次查询并没有发出SQL语句,说明第一次的结果被缓存了。