hibernate 二级缓存
理解缓存定义:
缓存(Cache): 计算机领域非常通用的概念。它介于应用程序和永久性数据存储源(如硬盘上的文件或者数据库)之间,其作用是降低应用程序直接读写永久性数据存储源的频率,从而提高应用的运行性能。缓存中的数据是数据存储源中数据的拷贝。缓存的物理介质通常是内存
理解二级缓存定义:
Hibernate中提供了两个级别的缓存
第一级别的缓存是 Session 级别的缓存,它是属于事务范围的缓存。这一级别的缓存由 hibernate 管理的,一般情况下无需进行干预,一级缓存中,不同Session对象间数据不共享
第二级别的缓存是 SessionFactory 级别的缓存,它是属于进程范围的缓存,同一个SessionFactory开启的不同Session间数据共享
SessionFactory 的缓存可以分为两类:
内置缓存: Hibernate 自带的, 不可卸载. 通常在 Hibernate 的初始化阶段, Hibernate 会把映射元数据和预定义的 SQL 语句放到 SessionFactory 的缓存中, 映射元数据是映射文件中数据的复制, 而预定义 SQL 语句时 Hibernate 根据映射元数据推到出来的. 该内置缓存是只读的(一般是把映射文件和配置文件放到缓存中)
外置缓存(二级缓存): 一个可配置的缓存插件. 在默认情况下, SessionFactory 不会启用这个缓存插件. 外置缓存中的数据是数据库数据的复制, 外置缓存的物理介质可以是内存或硬盘
hibernate二级缓存的结构:
理解二级缓存的并发访问策略:
两个并发的事务同时访问持久层的缓存的相同数据时, 也有可能出现各类并发问题.
二级缓存可以设定以下 4 种类型的并发访问策略, 每一种访问策略对应一种事务隔离级别
非严格读写(Nonstrict-read-write): 不保证缓存与数据库中数据的一致性. 提供 Read Uncommited 事务隔离级别, 对于极少被修改, 而且允许脏读的数据, 可以采用这种策略
读写型(Read-write): 提供 Read Commited 数据隔离级别.对于经常读但是很少被修改的数据, 可以采用这种隔离类型, 因为它可以防止脏读
事务型(Transactional): 仅在受管理环境下适用. 它提供了 Repeatable Read 事务隔离级别. 对于经常读但是很少被修改的数据, 可以采用这种隔离类型, 因为它可以防止脏读和不可重复读
只读型(Read-Only):提供 Serializable 数据隔离级别, 对于从来不会被修改的数据, 可以采用这种访问策略(很强,但是性能低)
缓存中存放的数据:
适合放入二级缓存中的数据:
很少被修改
不是很重要的数据, 允许出现偶尔的并发问题
不适合放入二级缓存中的数据:
经常被修改
财务数据, 绝对不允许出现并发问题
与其他应用数据共享的数据
缓存提供的供应商:
Hibernate 的二级缓存是进程或集群范围内的缓存, 缓存中存放的是对象的散装数据
二级缓存是可配置的的插件, Hibernate 允许选用以下类型的缓存插件:
EHCache: 可作为进程范围内的缓存, 存放数据的物理介质可以是内存或硬盘, 对 Hibernate 的查询缓存提供了支持
OpenSymphony OSCache:可作为进程范围内的缓存, 存放数据的物理介质可以是内存或硬盘, 提供了丰富的缓存数据过期策略, 对 Hibernate 的查询缓存提供了支持
SwarmCache: 可作为集群范围内的缓存, 但不支持 Hibernate 的查询缓存
JBossCache:可作为集群范围内的缓存, 支持 Hibernate 的查询缓存
配置进程范围内的二级缓存(配置ehcache缓存):
1 拷贝ehcache-1.5.0.jar到当前工程的lib目录下
2 开启二级缓存 (在hibernate.cfg.xml中配置)
<property name="hibernate.cache.use_second_level_cache">true</property>
3 要指定缓存的供应商 (在hibernate.cfg.xml中配置)
<property name="hibernate.cache.provider_class">org.hibernate.cache.EhCacheProvider</property>
4 指定使用二级缓存的类
* 方法一 在使用类的*.hbm.xml配置
选择需要使用二级缓存的持久化类, 设置它的二级缓存的并发访问策略, <class> 元素的 cache 子元素表明 Hibernate 会缓存对象的简单属性, 但不会缓存集合属性, 若希望缓存集合属性中的元素, 必须在 <set> 元素中加入 <cache> 子元素
*方法二 在hibernate.cfg.xml文件中配置(建议)
<!-- 指定使用二级缓存的类 放在maping下面 -->
<!-- 配置类级别的二级缓存 -->
<class-cache class="cn.itcast.c3p0.Customer" usage="read-write"/>
<class-cache class="cn.itcast.c3p0.Order" usage="read-write"/>
<!-- 配置集合级别的二级缓存 Order类和Customer是多对一的关系 -->
<collection-cache collection="cn.itcast.c3p0.Customer.orders" usage="read-write"/>
类级别的缓存
测试二级缓存和散装数据(类级别的二级缓存)
需要引入两个jar包
在srping下能找到
..\lib\concurrent\backport-util-concurrent.jar
..\lib\jakarta-commons\commons-logging.jar
public void testCache() { Session session=sf.openSession(); Transaction tx=session.beginTransaction(); Customer customer=(Customer)session.load(Customer.class, 4); System.out.println(customer.getAge()); System.out.println(customer);//测试散装数据 tx.commit(); session.close(); session=sf.openSession(); tx=session.beginTransaction(); customer=(Customer)session.load(Customer.class, 4); System.out.println(customer.getAge()); //cn.itcast.c3p0.Customer@1551b0 System.out.println(customer); //测试散装数据 tx.commit(); session.close();
session=sf.openSession();
tx=session.beginTransaction();
customer=(Customer)session.load(Customer.class, 4);
System.out.println(customer.getAge());
//cn.itcast.c3p0.Customer@1758500
System.out.println(customer); //测试散列 是重组的对象
tx.commit();
session.close();
}
测试类级别的二级缓存只适用于get和load获取数据,对query接口可以将数据放置到类级别的二级缓存中(同样可以放置到二级缓存中的对象还有get和load方法,QBC和SQLQuery),但是不能使用query接口从缓存中获取数据;query接口将查询的对象放置到二级缓存的查询缓存
@test public void testQuery(){ session=sf.openSession(); tx=session.beginTransaction(); Query query=session.createQuery(“from Customer where id=1”); query.list(); tx.comit(); session.close(); session=sf.openSession(); tx=session.beginTransaction(); Query query=session.createQuery(“from Customer where id=1”); query.list(); Customer c = (Customer)session.get(Customer.class,1);//不会查询数据库 c.getName(); tx.comit(); session.close(); }
结论:二级缓存数据读取的时候,要将读取的数据,放置到一级缓存中一份,读取数据的时候,实质上是要依赖于一级缓存(从二级缓存放到一级缓存时一级缓存要组装一个新对象)
集合级别缓存
(集合级别也就是一对多 多的一端,查一的一端的时候连带多的一端也会查出来,所以要配置集合级别的缓存)
测试集合级别的二级缓存(存放查询条件,即OID)
@Test public void testCollectionCache(){ session=sf.openSession(); tx=session.beginTransaction(); //查询客户,关联集合,可以使用立即检索查看效果(2条sql语句) Customer customer=(Customer)session.load(Customer.class, 1); System.out.println(customer.getName()); System.out.println(customer.getOrders().size()); tx.commit(); session.close(); session=sf.openSession(); tx=session.beginTransaction(); //不再显示sql语句,从二级缓存中获取 customer=(Customer)session.load(Customer.class, 1); System.out.println(customer.getName()); System.out.println(customer.getOrders().size()); tx.commit(); session.close(); }
时间戳缓存级别
Hibernate 提供了和查询相关的缓存区域:
时间戳缓存区域: org.hibernate.cahce.UpdateTimestampCache
时间戳缓存区域存放了对于查询结果相关的表进行插入, 更新或删除操作的时间戳. Hibernate 通过时间戳缓存区域来判断被缓存的查询结果是否过期, 其运行过程如下:
T1 时刻执行查询操作, 把查询结果存放在 QueryCache 区域, 记录该区域的时间戳为 T1
T2 时刻对查询结果相关的表进行更新操作, Hibernate 把 T2 时刻存放在 UpdateTimestampCache 区域.
T3 时刻执行查询结果前, 先比较 QueryCache 区域的时间戳和 UpdateTimestampCache 区域的时间戳, 若 T2 >T1, 那么就丢弃原先存放在 QueryCache 区域的查询结果, 重新到数据库中查询数据, 再把结果存放到 QueryCache 区域; 若 T2 < T1, 直接从 QueryCache 中获得查询结果
测试时间戳缓存区域(当T1>T2,说明查询在后,更新在前)
public void testUpdateTimeStampCache() { Session session=sf.openSession(); Transaction tx=session.beginTransaction(); Query query=session.createQuery("update Customer c set c.age=55 where c.id=4"); int row = query.executeUpdate();//T2 System.out.println(row); tx.commit(); Customer customer=(Customer)session.load(Customer.class, 4);//T1 System.out.println(customer.getAge()); //注意:不能使用set的方式更新数据,因为set的方式清理session的一级缓存和二级缓存没有关系 session.close(); /******************************/ session=sf.openSession(); tx=session.beginTransaction(); //T1>T2不产生select语句, customer=(Customer)session.load(Customer.class, 4); System.out.println(customer.getAge()); tx.commit(); session.close(); }
测试时间戳缓存区域,当T1<T2,说明查询在前,更新在后
public void testUpdateTimeStampCache() { Session session=sf.openSession(); Transaction tx=session.beginTransaction(); Customer customer=(Customer)session.load(Customer.class, 4);//T1 System.out.println(customer.getAge()); //注意:不能使用set的方式更新数据,因为set的方式清理session的一级缓存和二级缓存没有关系 Query query=session.createQuery("update Customer c set c.age=55 where c.id=4"); int row = query.executeUpdate();//T2 System.out.println(row); tx.commit(); session.close(); /******************************/ session=sf.openSession(); tx=session.beginTransaction(); //T2>T1重新查询数据库 customer=(Customer)session.load(Customer.class, 4); System.out.println(customer.getAge()); tx.commit(); session.close(); }
查询级别缓存
对于经常使用的查询语句, 如果启用了查询缓存, 当第一次执行查询语句时, Hibernate 会把查询结果存放在查询缓存中. 以后再次执行该查询语句时, 只需从缓存中获得查询结果, 从而提高查询性能
查询缓存使用于如下场合
应用程序运行时经常使用查询语句
很少对与查询语句检索到的数据进行插入, 删除和更新操作
使用查询缓存的步骤
配置二级缓存, 因为查询缓存依赖于二级缓存
在 hibernate 配置文件中启用查询缓存
<property name=“hibernate.cache.use_query_cache">true</property>
对于希望启用查询缓存的查询语句, 调用 Query 的 setCacheable(true) 方法
注意:query查询和QBC查询只能从二级缓存区域的查询缓存区读取数据
查询缓存
public void testQueryCache() { Session session=sf.openSession(); Transaction tx=session.beginTransaction(); Query query=session.createQuery("select c from Customer c"); /* * 设置查询缓存 * * 如果查询缓存存在 直接返回 * * 如果查询缓存不存在 查询数据库 将查询结果放置到查询缓存中 */ query.setCacheable(true);//先不加,再添加,看效果 query.list(); tx.commit(); session.close(); session=sf.openSession(); tx=session.beginTransaction(); query=session.createQuery("select c from Customer c"); query.setCacheable(true);//先不加,再添加,看效果 query.list(); tx.commit(); session.close(); }
测试一级缓存更新数据会同步到二级缓存
public void testUpdate() {
Session session=sf.openSession();
Transaction tx=session.beginTransaction();
Customer customer=(Customer)session.load(Customer.class, 4);
System.out.println(customer.getAge());
customer.setAge(45);
tx.commit();
session.close();
session=sf.openSession();
tx=session.beginTransaction();
customer=(Customer)session.load(Customer.class, 4);
System.out.println(customer.getAge()); //45
tx.commit();
session.close();
}
测试二级缓存的数据存放到临时目录
测试代码
//测试缓存溢出存放到临时目录 @Test public void testowerFlow() { Session session=sf.openSession(); Transaction tx=session.beginTransaction(); Query query=session.createQuery(" from Order o"); //30个对象 query.list().size(); tx.commit(); session.close(); }
配置进程范围内的二级缓存(配置ehcache缓存)
<diskStore>:指定一个目录, 当 EHCache 把数据写到硬盘上时, 将把数据写到这个文件目录下. 默认是C:\WINDOWS\Temp
<defaultCache>: 设置缓存的默认数据过期策略
<cache> 设定具体的命名缓存的数据过期策略
使用name属性,cn.itcast.second.Order(比defaultCache多的一个属性)
每个命名缓存代表一个缓存区域,每个缓存区域有各自的数据过期策略。命名缓存机制使得用户能够在每个类以及类的每个集合的粒度上设置数据过期策略。
cache元素的属性
name:设置缓存的名字,它的取值为类的全限定名或类的集合的名字
maxElementsInMemory :设置基于内存的缓存中可存放的对象最大数目
eternal:设置对象是否为永久的,true表示永不过期,此时将忽略timeToIdleSeconds 和 timeToLiveSeconds属性; 默认值是false
timeToIdleSeconds:设置对象空闲最长时间,以秒为单位, 超过这个时间,对象过期。当对象过期时,EHCache会把它从缓存中清除。如果此值为0,表示对象可以无限期地处于空闲状态。
timeToLiveSeconds:设置对象生存最长时间,超过这个时间,对象过期。如果此值为0,表示对象可以无限期地存在于缓存中. 该属性值必须大于或等于 timeToIdleSeconds 属性值
overflowToDisk:设置基于内在的缓存中的对象数目达到上限后,是否把溢出的对象写到基于硬盘的缓存中
diskPersistent 当jvm结束时是否持久化对象 true false 默认是false
diskExpiryThreadIntervalSeconds 指定专门用于清除过期对象的监听线程的轮询时间
memoryStoreEvictionPolicy - 当内存缓存达到最大,有新的element加入的时候,?移除缓存中element的策略。默认是LRU(最近最少使用),可选的有LFU(最不常使用)和FIFO(先进先出)
总结:如果配置二级缓存:即
删除的数据会影响二级缓存
修改的数据会影响二级缓存
查询的数据会全部进入二级缓存
而:添加的数据不会影响二级缓存
注意
Hibernate没有自带二级缓存实现技术(通过插件配置)
二级缓存中的数据不一定与数据库中的数据一一对应,完全匹配
二级缓存数据无法与应用程序直接进行数据交换,需要通过一级缓存进行中转
二级缓存中的数据存储格式不是Java对象的格式