1. 理解二级缓存定义
Hibernate中提供了两个级别的缓存
• 第一级别的缓存是 Session 级别的缓存,它是属于事务范围的缓存。这一级别的缓存由 hibernate 管理的,一般情况下无需进行干预
• 第二级别的缓存是 SessionFactory 级别的缓存,它是属于进程范围的缓存
SessionFactory 的缓存可以分为两类 :
• 内置缓存 : Hibernate 自带的 , 不可卸载 . 通常在 Hibernate 的初始化阶段 , Hibernate 会把映射元数据和预定义的 SQL 语句放到 SessionFactory 的缓存中 , 映射元数据是映射文件中数据的复制 , 而预定义 SQL 语句时 Hibernate 根据映射元数据推到出来的 . 该内置缓存是只读的 .
• 外置缓存 ( 二级缓存 ) : 一个可配置的缓存插件 . 在默认情况下 , SessionFactory 不会启用这个缓存插件 . 外置缓存中的数据是数据库数据的复制 , 外置缓存的物理介质可以是内存或硬盘
理解二级缓存的并发访问策略
两个并发的事务同时访问持久层的缓存的相同数据时 , 也有可能出现各类并发问题 .
二级缓存可以设定以下 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 的查询缓存
4 种缓存插件支持的并发访问策略(x代表支持, 空白代表不支持)
配置进程范围内的二级缓存(配置ehcache缓存)
cache 元素的属性 ()
• name: 设置缓存的名字 , 它的取值为类的全限定名或类的集合的名字
• maxElementsInMemory : 设置基于内存的缓存中可存放的对象最大数目
• eternal: 设置对象是否为永久的 ,true 表示永不过期 , 此时将忽略 timeToIdleSeconds 和 timeToLiveSeconds 属性 ; 默认值是 false
• timeToIdleSeconds: 设置对象空闲最长时间 , 以秒为单位 , 超过这个时间 , 对象过期。当对象过期时 ,EHCache 会把它从缓存中清除。如果此值为 0, 表示对象可以无限期地处于空闲状态。
• timeToLiveSeconds:
设置对象生存最长时间
,
超过这个时间
,
对象过期。
如果此值为 0,
表示对象可以无限期地存在于缓存中
.
该属性值必须大于或等于
timeToIdleSeconds
属性值
• overflowToDisk: 设置基于内在的缓存中的对象数目达到上限后 , 是否把溢出的对象写到基于硬盘的缓存中
• diskPersistent 当 jvm 结束时是否持久化对象 true false 默认是 false
• diskExpiryThreadIntervalSeconds 指定专门用于清除过期对象的监听线程的轮询时间
示例步骤:
如何在项目中配置二级缓存:
* 引入二级缓存的插件encache-1.5.0.jar
* 先使用encache-1.5.0.jar该缓存的默认配置,此时执行的该jar包中的ehcache-failsafe.xml文件
* 表示缓存中的数据存不下的情况下,存到硬盘的临时目录
<diskStore path="java.io.tmpdir"/> %USERPROFILE%\Local Settings\Temp该目录
* 采用默认的配置,defaultCache
放入缓存中的数据要采用的默认配置(针对缓存中所有对象的)
<defaultCache
maxElementsInMemory="10"
eternal="false"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
overflowToDisk="true"
maxElementsOnDisk="10000000"
diskPersistent="false"
diskExpiryThreadIntervalSeconds="120"
/>
* 在hibernate.cfg.xml中增加如下配置
* 开启二级缓存,默认是不开启的
<property name="hibernate.cache.use_second_level_cache">true</property>
* 配置缓存提供的供应商
property name="hibernate.cache.provider_class">org.hibernate.cache.EhCacheProvider</property>
* 配置缓存
* 方法一:在*.hbm.xml文件配置
* 方法二: 在ibernate.cfg.xml配置(建议),要放置mapping元素的下面
<!--配置类级别的二级缓存-->
<class-cache class="cn.itcast.cache.Customer" usage="read-write"/>
<class-cache class="cn.itcast.cache.Order" usage="read-write"/>
<!-- 配置集合级别的二级缓存 -->
<collection-cache collection="cn.itcast.cache.Customer.orderes" usage="read-write"/>
* 注使用二级缓存,还需要引入两个jar包
..\lib\concurrent\backport-util-concurrent.jar
..\lib\commons-logging.jar
* 针对某个对象设置缓存配置
* 在src下新建ehcache.xml文件,文件的结构和ehcache-failsafe.xml文件相同
* 在ehcache.xml文件增加如下配置,name指定该配置针对Order类
<cache
name="cn.itcast.cache.Order"
maxElementsInMemory="1"
eternal="true"
overflowToDisk="true"
maxElementsOnDisk="100000"
diskPersistent="true"
diskExpiryThreadIntervalSeconds="120"
/>
* 启用查询缓存
* 在hibernate.cfg.xml文件中增加如下配置
<!-- 启用查询缓存 -->
<property name="hibernate.cache.use_query_cache">true</property>
* 在程序代码中使用query查询,查询的条件才能放置到二级缓存(查询缓存)中
query=session.createQuery("from Customer");
//启用查询缓存
query.setCacheable(true);
//直接从二级缓存中获取数据
query.list();
示例代码:
省略 Customer.java Order.java两个 bean对象
Customer.hbm.xml 配置文件
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="cn.itcast.cache.Customer" table="customers">
<!--配置类级别的二级缓存,此时二级缓存中能存放Customer对象 -->
<!-- <cache usage="read-write"/> -->
<id name="id" type="integer">
<column name="id"/>
<generator class="increment"/>
</id>
<property name="name" type="string">
<column name="name"/>
</property>
<property name="age" type="integer">
<column name="age"/>
</property>
<set name="orderes" table="orders" inverse="true" lazy="true">
<!-- 配置集合级别的二级缓存,此时orderes订单集合放入到二级缓存 -->
<!-- <cache usage="read-write"/> -->
<key>
<column name="customer_id"/>
</key>
<one-to-many class="cn.itcast.cache.Order"/>
</set>
</class>
</hibernate-mapping>
Order.hbm.xml 配置文件
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
<class name="cn.itcast.cache.Order" table="orders">
<id name="id" type="integer">
<column name="id"/>
<generator class="increment"/>
</id>
<property name="orderNumber" type="string">
<column name="orderNumber"/>
</property>
<property name="price" type="double">
<column name="price"/>
</property>
<many-to-one name="customer" class="cn.itcast.cache.Customer">
<column name="customer_id"/>
</many-to-one>
</class>
</hibernate-mapping>
hibernate.cfg.xml 配置文件
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
<session-factory>
<property name="hibernate.connection.username">root</property>
<property name="hibernate.connection.password">root</property>
<property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property>
<property name="hibernate.connection.url">jdbc:mysql://localhost:3306/test</property>
<property name="hibernate.dialect">org.hibernate.dialect.MySQL5Dialect</property>
<property name="hibernate.hbm2ddl.auto">update</property>
<property name="hibernate.show_sql">true</property>
<property name="hibernate.format_sql">false</property>
<!-- 配置数据库的隔离级别 数据库中的四种隔离级别,分别对应的值
1:Read uncommitted isolation
2:Read committed isolation
4:Repeatable read isolation
8:Serializable isolation
-->
<property name="hibernate.connection.isolation">2</property>
<!-- 配置session对象的生命周期和本地线程绑定 ,值为thread-->
<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>
<!-- 加载映射文件-->
<mapping resource="cn/itcast/cache/Customer.hbm.xml"/>
<mapping resource="cn/itcast/cache/Order.hbm.xml"/>
<!-- 配置二级缓存中存放的数据类型,要放置到mapping元素的下面 -->
<!--配置类级别的二级缓存-->
<class-cache class="cn.itcast.cache.Customer" usage="read-write"/>
<class-cache class="cn.itcast.cache.Order" usage="read-write"/>
<!-- 配置集合级别的二级缓存 -->
<collection-cache collection="cn.itcast.cache.Customer.orderes" usage="read-write"/>
</session-factory>
</hibernate-configuration>
ehcache.xml 二级缓存配置文件, 这个文件需要在 /src目录下, 验证时 maxElementsOnDisk="100000" 属性不支持
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../config/ehcache.xsd">
<!--
The ehcache-failsafe.xml is a default configuration for ehcache, if an ehcache.xml is not configured.
-->
<diskStore path="D:/temp"/>
<defaultCache
maxElementsInMemory="10"
eternal="false"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
overflowToDisk="true"
maxElementsOnDisk="100000"
diskPersistent="true"
diskExpiryThreadIntervalSeconds="120"
/>
<cache
name="cn.itcast.cache.Order"
maxElementsInMemory="1"
eternal="true"
overflowToDisk="true"
maxElementsOnDisk="100000"
diskPersistent="true"
diskExpiryThreadIntervalSeconds="120"
/>
</ehcache>
AppCache.java 实验代码
package cn.itcast.cache;
import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.cfg.Configuration;
import org.junit.Test;
public class AppCache {
private static SessionFactory sf=null;
static{
Configuration config=new Configuration();
config.configure("cn/itcast/cache/hibernate.cfg.xml");
sf=config.buildSessionFactory();
}
/*
* 知识点12:测试二级缓存和散列数据
* * 测试类级别的二级缓存(使用get load)
* * 使用query测试查询级别的二级缓存
*/
@Test
public void testSecondCache(){
/**************************************************************************************************/
Session session=sf.openSession();
Transaction tx=session.beginTransaction();
/**
* 由于开启二级缓存
* * 下面的查询查询出id=1的客户后,
* * 放入该对象到一级缓存一份,
* * 同时还放入该数据到二级缓存中一份,放置查出的Customer对象到类级别的缓存区域中
* * 产生select语句
*/
Customer c=(Customer)session.get(Customer.class, 1);
System.out.println(c.getAge());
tx.commit();
session.close(); //一级缓存消失
/**************************************************************************************************/
session=sf.openSession(); //开启一个新的session
tx=session.beginTransaction();
/*
*下面的查询查询出id=1的客户的过程
* * 先到session的一级缓存区查找id=1的客户
* * 如果找到 直接返回
* * 如果没有找到,到sessionFactory的二级缓存中查找id=1的客户对象
* * 如果找到 直接返回
* * 如果没有找到,在查询数据库
*
*/
//从二级缓存中获取Customer对象
Customer c1=(Customer)session.get(Customer.class, 1);
System.out.println(c1.getAge());
System.out.println("c1 "+c1); // cn.itcast.cache.Customer@14b5f4a
tx.commit();
session.close();//
/************************************************************************************************/
session=sf.openSession();
tx=session.beginTransaction();
//从二级缓存中获取Customer对象
Customer c2=(Customer)session.get(Customer.class, 1);
System.out.println(c2.getAge());
System.out.println("c2 "+c2); // cn.itcast.cache.Customer@ae533a,
//C1不等于C2,二级缓存中不是直接存放对象,而是存放散列数据
tx.commit();
session.close();
/*************************************************************************************************/
}
//知识点13:测试一级缓存更新数据会同步到二级缓存
@Test
public void testUpdate(){
/**************************************************************************************************/
Session session=sf.openSession();
Transaction tx=session.beginTransaction();
Customer c=(Customer)session.get(Customer.class, 1);
System.out.println(c.getAge());
c.setAge(45);
tx.commit();
session.close();
session=sf.openSession(); //开启一个新的session
tx=session.beginTransaction();
//从二级缓存中获取Customer对象
Customer c1=(Customer)session.get(Customer.class, 1);
System.out.println(c1.getAge());
tx.commit();
session.close();//
/*************************************************************************************************/
}
//知识点xxxx:测试\集合级别的二级缓存
//集合级别的缓存放置的查询的条件,真正的实体还是在类级别的缓存区域中
@Test
public void testCollectionUpdate(){
/**************************************************************************************************/
Session session=sf.openSession();
Transaction tx=session.beginTransaction();
//Order对象放置放置类级别的二级缓存中
/*
* 客户关联的订单集合存放到集合级别的缓存中,此时集合级别的缓存中存放的该订单集合中订单的id
* 1 ,2 ,3, 4, 5, 6,7,8,9,10
*/
Customer c=(Customer)session.get(Customer.class, 1);
System.out.println(c.getAge());
tx.commit();
session.close();
session=sf.openSession(); //开启一个新的session
tx=session.beginTransaction();
/**
* 从二级缓存中获取Customer对象
* 再次查询客户关联的订单集合
* * 到session的一级缓存中区查找,没有找到
* * 到二级缓存中,到集合级别的二级缓存中查找订单,集合级别的二级缓存中放置到订单的id[ 1 ,2 ,3, 4, 5, 6,7,8,9,10]
* 获取集合中订单的时候,select * from orders where id= 1 ,2 ,3, 4, 5, 6,7,8,9,10
* 所以会产生10调价语句
*/
Customer c1=(Customer)session.get(Customer.class, 1);
System.out.println(c1.getAge());
tx.commit();
session.close();//
/*************************************************************************************************/
}
//知识点14:测试二级缓存的数据存放到临时目录
@Test
public void testTempFile(){
/**************************************************************************************************/
Session session=sf.openSession();
Transaction tx=session.beginTransaction();
Query query=session.createQuery("from Order o");
query.list();
tx.commit();
session.close();
/*************************************************************************************************/
}
//知识点15:时间戳缓存区域,不用所任何配置
@Test
public void testUpdateTimeStamp(){
/**************************************************************************************************/
Session session=sf.openSession();
Transaction tx=session.beginTransaction();
//查询id=1的客户,放置该客户对象到一级缓存和二级缓存,同时还要把查询的时间放置到类级别的时间戳区域T1
Customer c=(Customer)session.get(Customer.class, 1); //select
System.out.println(c.getAge());
// //修改的时候把修改的时间记录到更新时间戳缓存区域 T2
// //修改年龄(insert update delete)
Query query=session.createQuery("update Customer c set c.age=90 where c.id=1");
query.executeUpdate();
tx.commit();
session.close();
session=sf.openSession(); //开启一个新的session
tx=session.beginTransaction();
/*
* 比对T1和T2的时间
* * T1>T2 不查询数据库
* * T1<T2 查询数据库
*/
Customer c1=(Customer)session.get(Customer.class, 1); //
System.out.println(c1.getAge());
tx.commit();
session.close();//
/*************************************************************************************************/
}
/*
* 知识点16: 查询缓存
* * 使用query接口
*/
@Test
public void testQueryCache(){
/**************************************************************************************************/
Session session=sf.openSession();
Transaction tx=session.beginTransaction();
/**
* 如果没有配置,则类级别的二级缓存中,不能存放Customer对象
* <class-cache class="cn.itcast.cache.Customer" usage="read-write"/>
*
* * 执行query查询session.createQuery("from Customer");
* * 目的查询所有的Customer对象,则对象的id放置查询缓存【1 2 3】
* * 对象的实体类级别的二级缓存中不能存放Customer对象
*/
Query query=session.createQuery("from Customer");
//启用查询缓存
query.setCacheable(true);
query.list();
tx.commit();
session.close();//
/*************************************************************************************************/
session=sf.openSession();
tx=session.beginTransaction();
/**
* 执行query查询session.createQuery("from Customer");
* * 到查询缓存中获取查询条件id【1 2 3】
* * 以id为条件到类级别的缓存中,获取Customer对象
* * 如果存在 不再查询数据库
* * 如何不存在 查询数据库 select * customers where id=1...
* select * customers where id=3
*/
query=session.createQuery("from Customer");
//启用查询缓存
query.setCacheable(true);
//直接从二级缓存中获取数据
query.list();
tx.commit();
session.close();//
}
}