Hibernate -- 二级缓存
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缓存)
•name:设置缓存的名字,它的取值为类的全限定名或类的集合的名字
•maxElementsInMemory:设置基于内存的缓存中可存放的对象最大数目
•eternal:设置对象是否为永久的,true表示永不过期,此时将忽略timeToIdleSeconds和
timeToLiveSeconds属性;默认值是false
•timeToIdleSeconds:设置对象空闲最长时间,以秒为单位,超过这个时间,对象过期。当对象过期时,EHCache会把它从缓存中清除。如果此值为0,表示对象可以无限期地处于空闲状态。
•timeToLiveSeconds:设置对象生存最长时间,超过这个时间,对象过期。
如果此值为0,表示对象可以无限期地存在于缓存中.该属性值必须大于或等于 timeToIdleSeconds属性值
如果此值为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();// } }