缓存(Cache): 计算机领域非常通用的概念。它介于应用程序和永久性数据存储源(如硬盘上的文件或者数据库)之间,其作用是降低应用程序直接读写永久性数据存储源的频率,从而提高应用的运行性能。缓存中的数据是数据存储源中数据的拷贝。缓存的物理介质通常是内存。
Hibernate中提供了两个级别的缓存
第一级别的缓存是 Session 级别的缓存,它是属于事务范围的缓存。这一级别的缓存由 hibernate 管理的,是hibernate内置的,一般情况下无需进行干预。
第二级别的缓存是 SessionFactory 级别的缓存,它是属于进程范围的缓存。
一级缓存
在 Session 接口的实现中包含一系列的 Java 集合, 这些 Java 集合构成了 Session 缓存. 只要 Session 实例没有结束生命周期, 存放在它缓存中的对象也不会结束生命周期
当session的save()方法持久化一个对象时,该对象被载入缓存,以后即使程序中不再引用该对象,只要缓存不清空,该对象仍然处于生命周期中。当试图get()、 load()对象时,会判断缓存中是否存在该对象,有则返回,此时不查询数据库。没有再查询数据库
Session 能够在某些时间点, 按照缓存中对象的变化来执行相关的 SQL 语句, 来同步更新数据库, 这一过程被称为刷出缓存(flush)
默认情况下 Session 在以下时间点刷出缓存:
当应用程序调用 Transaction 的 commit()方法的时, 该方法先刷出缓存(session.flush()),然后在向数据库提交事务(tx.commit())
当应用程序执行一些查询操作时,如果缓存中持久化对象的属性已经发生了变化,会先刷出缓存,以保证查询结果能够反映持久化对象的最新状态
调用 Session 的 flush() 方法
二级缓存
SessionFactory 的缓存可以分为两类:
内置缓存: Hibernate 自带的, 不可卸载. 通常在 Hibernate 的初始化阶段, Hibernate 会把映射元数据和预定义的 SQL 语句放到 SessionFactory 的缓存中, 映射元数据是映射文件中数据的复制, 而预定义 SQL 语句时 Hibernate 根据映射元数据推到出来的. 该内置缓存是只读的.
外置缓存(二级缓存): 一个可配置的缓存插件. 在默认情况下, SessionFactory 不会启用这个缓存插件. 外置缓存中的数据是数据库数据的复制, 外置缓存的物理介质可以是内存或硬盘
并发访问策略
transactional(事务型):
仅在受管理的环境中适用
提供Repeatable Read事务隔离级别
适用经常被读,很少修改的数据
可以防止脏读和不可重复读的并发问题
缓存支持事务,发生异常的时候,缓存也能够回滚
read-write(读写型):
提供Read Committed事务隔离级别
在非集群的环境中适用
适用经常被读,很少修改的数据
可以防止脏读
更新缓存的时候会锁定缓存中的数据
nonstrict-read-write(非严格读写型):
适用极少被修改,偶尔允许脏读的数据(两个事务同时修改数据的情况很少见)
不保证缓存和数据库中数据的一致性
为缓存数据设置很短的过期时间,从而尽量避免脏读
不锁定缓存中的数据
read-only(只读型):
适用从来不会被修改的数据(如参考数据)
在此模式下,如果对数据进行更新操作,会有异常
事务隔离级别低,并发性能高
在集群环境中也能完美运作
适合放入二级缓存中的数据:
很少被修改
不是很重要的数据, 允许出现偶尔的并发问题
不适合放入二级缓存中的数据:
经常被修改
财务数据, 绝对不允许出现并发问题
与其他应用数据共享的数据
二级缓存提供的供应商
二级缓存是可配置的的插件, Hibernate 允许选用以下类型的缓存插件:
EHCache: 可作为进程范围内的缓存, 存放数据的物理介质可以是内存或硬盘, 对 Hibernate 的查询缓存提供了支持
OpenSymphony `:可作为进程范围内的缓存, 存放数据的物理介质可以是内存或硬盘, 提供了丰富的缓存数据过期策略, 对 Hibernate 的查询缓存提供了支持
SwarmCache: 可作为集群范围内的缓存, 但不支持 Hibernate 的查询缓存
JBossCache:可作为集群范围内的缓存, 支持 Hibernate 的查询缓存
时间戳缓存:
Hibernate 提供了和查询相关的缓存区域:
时间戳缓存区域: org.hibernate.cahce.UpdateTimestampCache
时间戳缓存区域存放了对于查询结果相关的表进行插入, 更新或删除操作的时间戳. Hibernate 通过时间戳缓存区域来判断被缓存的查询结果是否过期, 其运行过程如下:
T1 时刻执行查询操作, 把查询结果存放在 QueryCache 区域, 记录该区域的时间戳为 T1
T2 时刻对查询结果相关的表进行更新操作, Hibernate 把 T2 时刻存放在 UpdateTimestampCache 区域.
T3 时刻执行查询结果前, 先比较 QueryCache 区域的时间戳和 UpdateTimestampCache 区域的时间戳, 若 T2 >T1, 那么就丢弃原先存放在 QueryCache 区域的查询结果, 重新到数据库中查询数据, 再把结果存放到 QueryCache 区域; 若 T2 < T1, 直接从 QueryCache 中获得查询结果
查询缓存:
对于经常使用的查询语句, 如果启用了查询缓存, 当第一次执行查询语句时, Hibernate 会把查询结果存放在查询缓存中. 以后再次执行该查询语句时, 只需从缓存中获得查询结果, 从而提高查询性能
查询缓存使用于如下场合:
应用程序运行时经常使用查询语句
很少对与查询语句检索到的数据进行插入, 删除和更新操作
使用查询缓存的步骤
配置二级缓存, 因为查询缓存依赖于二级缓存
在 hibernate 配置文件中启用查询缓存
<property name=“hibernate.cache.use_query_cache”>true</property>
对于希望启用查询缓存的查询语句, 调用 Query 的 setCacheable(true) 方法
查询缓存可以缓存属性
二级缓存的配置(EHCache):
jar包:
ehcache-1.5.0.jar
backport-util-concurrent-2.1.jar
commons-logging-1.1.1.jar
配置文件hibernate.cfg.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
<session-factory>
<property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property>
<property name="hibernate.connection.url">jdbc:mysql:///hibernate?useUnicode=true&characterEncoding=UTF-8</property>
<property name="hibernate.connection.username">root</property>
<property name="hibernate.connection.password">1234</property>
<property name="hibernate.dialect">org.hibernate.dialect.MySQL5Dialect</property>
<property name="hibernate.show_sql">true</property>
<property name="hibernate.format_sql">true</property>
<property name="hibernate.hbm2ddl.auto">update</property>
<property name="javax.persistence.validation.mode">none</property>
<!-- 配置c3p0连接池 -->
<property name="hibernate.connection.provider_class">org.hibernate.connection.C3P0ConnectionProvider</property>
<!-- 隔离级别 -->
<property name="hibernate.connection.isolation">4</property>
<!-- 配置session与本地线程绑定 -->
<property name="hibernate.current_session_context_class">thread</property>
<!-- 开启二级缓存 -->
<property name="hibernate.cache.use_second_level_cache">true</property>
<!-- 确定二级缓存供应商 -->
<property name="hibernate.cache.provider_class">org.hibernate.cache.EhCacheProvider</property>
<!-- 二级缓存 ## 查询缓存 -->
<property name="hibernate.cache.use_query_cache">true</property>
<!-- 5 添加映射配置文件 -->
<mapping resource="com/my/bean/Customer.hbm.xml"/>
<mapping resource="com/my/bean/Order.hbm.xml"/>
<!--配置需要缓存的 类或集合 -->
<!-- 类级别缓存 -->
<class-cache usage="read-write" class="com.my.bean.Customer"/>
<!-- --><class-cache usage="read-write" class="com.my.bean.Order"/>
<!-- 集合级别缓存 -->
<collection-cache usage="read-write" collection="com.my.bean.Customer.orderSet"/>
</session-factory>
</hibernate-configuration>
配置ehcache.xml
<diskStore>:指定一个目录, 当 EHCache 把数据写到硬盘上时, 将把数据写到这个文件目录下. 默认是C:/WINDOWS/Temp
<defaultCache>: 设置缓存的默认数据过期策略
<cache> 设定具体的命名缓存的数据过期策略
使用name属性,cn.itcast.second.Order
每个命名缓存代表一个缓存区域,每个缓存区域有各自的数据过期策略。命名缓存机制使得用户能够在每个类以及类的每个集合的粒度上设置数据过期策略。
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(先进先出)
demo:
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../config/ehcache.xsd">
<diskStore path="c:/ehcache"/>
<defaultCache
maxElementsInMemory="5"
eternal="false"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
overflowToDisk="true"
maxElementsOnDisk="10000000"
diskPersistent="false"
diskExpiryThreadIntervalSeconds="120"
memoryStoreEvictionPolicy="LRU"
/>
</ehcache>
测试类:
package com.my.bean;
import java.util.List;
import java.util.Set;
import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.Transaction;
import org.junit.Test;
import cn.itcast.a_query.Customer;
import cn.itcast.a_query.Order;
import cn.itcast.util.SessionUtils;
public class TestTwoCache {
@Test
public void demo01(){
/* 测试 :二级缓存是否存在
* 如果二级缓存开,只执行1次查询
* 如果二级缓存关,将执行2次查询
*/
Session session = SessionUtils.openSession();
Transaction transaction = session.beginTransaction();
Customer customer = (Customer) session.get(Customer.class, 3); //执行查询,将数据存放到一级缓存,与此也放入到了二级缓存
System.out.println(customer.getName());
Customer customer33 = (Customer) session.get(Customer.class, 3); //一级缓存获得数据
System.out.println(customer33.getName());
transaction.commit();
session.close();
Session session2 = SessionUtils.openSession();
Transaction transaction2 = session2.beginTransaction();
Customer customer2 = (Customer) session2.get(Customer.class, 3); //session 一级缓存失效,从二级缓存获得数据
System.out.println(customer2.getName());
transaction2.commit();
session2.close();
}
@Test
public void demo02(){
/* 保存类级别数据时,将以散装数据形式保存。
*/
Session session = SessionUtils.openSession();
Transaction transaction = session.beginTransaction();
Customer customer = (Customer) session.get(Customer.class, 3);
System.out.println(customer);
transaction.commit();
session.close();
Session session2 = SessionUtils.openSession();
Transaction transaction2 = session2.beginTransaction();
Customer customer2 = (Customer) session2.get(Customer.class, 3); //session 一级缓存失效,从二级缓存获得数据
System.out.println(customer2);
transaction2.commit();
session2.close();
}
@Test
public void demo03(){
// list 只放不取
Session session = SessionUtils.openSession();
Transaction transaction = session.beginTransaction();
// 存放
List<Customer> all = session.createQuery("from Customer").list();
for (Customer customer : all) {
System.out.println(customer.getName());
}
transaction.commit();
session.close();
Session session2 = SessionUtils.openSession();
Transaction transaction2 = session2.beginTransaction();
// get获取
Customer customer2 = (Customer) session2.get(Customer.class, 3); //session 一级缓存失效,从二级缓存获得数据
System.out.println(customer2);
// list不从缓存获取
List<Customer> all2 = session2.createQuery("from Customer").list();
for (Customer customer : all2) {
System.out.println(customer.getName());
}
transaction2.commit();
session2.close();
}
@Test
public void demo04(){
// 集合级别的缓存 -- 将Order类级别缓存注释掉
Session session = SessionUtils.openSession();
Transaction transaction = session.beginTransaction();
Customer customer = (Customer) session.get(Customer.class, 3); //查询客户
Set<Order> OrderSet = customer.getOrderSet();
System.out.println(OrderSet.size()); //查询所有订单
for(Order order : OrderSet){
System.out.println(order.getPrice());
}
transaction.commit();
session.close();
Session session2 = SessionUtils.openSession();
Transaction transaction2 = session2.beginTransaction();
// get获取
Customer customer2 = (Customer) session2.get(Customer.class, 3); //session 一级缓存失效,从二级缓存获得数据
Set<Order> orderSet2 = customer2.getOrderSet();
System.out.println(orderSet2.size()); //没有查询,获得所有的订单
for(Order order : orderSet2){
System.out.println(order.getPrice());
}
transaction2.commit();
session2.close();
}
@Test
public void demo05(){
// 时间戳缓存
Session session = SessionUtils.openSession();
Transaction transaction = session.beginTransaction();
Customer customer = (Customer) session.get(Customer.class, 3); //查询客户
System.out.println(customer.getName());
// 使用query进行更新
session.createQuery("update Customer set name = :name where id = :id")
.setString("name", "rose")
.setInteger("id", 3)
.executeUpdate();
transaction.commit();
session.close();
Session session2 = SessionUtils.openSession();
Transaction transaction2 = session2.beginTransaction();
// get获取
Customer customer2 = (Customer) session2.get(Customer.class, 3); //session 一级缓存失效,从二级缓存获得数据
System.out.println(customer2.getName());
transaction2.commit();
session2.close();
}
@Test
public void demo6(){
// 默认Query 每次都查询 查询缓存
Session session = SessionUtils.openSession();
Transaction transaction = session.beginTransaction();
Query query = session.createQuery("from Customer");
query.setCacheable(true); //将查询语句 与 结果 缓存到 查询缓存中
List<Customer> all = query.list();
System.out.println(all);
transaction.commit();
session.close();
Session session2 = SessionUtils.openSession();
Transaction transaction2 = session2.beginTransaction();
Query query2 = session2.createQuery("from Customer");
query2.setCacheable(true); //从查询缓存中 获取数据
List<Customer> all2 = query2.list();
System.out.println(all2);
transaction2.commit();
session2.close();
}
}
原创文章,作者:Maggie-Hunter,如若转载,请注明出处:https://blog.ytso.com/12083.html