关闭

二级缓存

标签: 缓存框架hibernate
229人阅读 评论(0) 收藏 举报
分类:
1.Hibernate缓存
(1) Hibernate提供了两个级别的缓存:
     一级缓存 - Session缓存 - 线程级别:只能在同一次请求有效。
     二级缓存 - SessionFactory缓存 - 进程级别,可以被多个线程共享,可以理解为内存数据库。
(2) 缓存访问过程:
    当第一个访问数据时,首先判断一级缓存中是否存在数据:
         如果存在,那么就从一级缓存中获取,返回使用;
         如果不存在,那么判断是否启用了二级缓存,如果启用了二级缓存:
             判断二级缓存中是否存在数据:
                  如果存在,那么就从二级缓存中获取,返回使用;
                  如果不存在,发送查询语句,到数据库中进行加载,将从数据库中加载的数据,存储到一级和二级缓存中,给下次访问使用。

2.Hibernate框架默认支持二级缓存
     内置缓存:自己提供了,用于管理自身的一些配置等,是通过Hashtable实现的,比较简陋。生产环境是不推荐使用的。
     外置缓存:由第三方提供缓存插件.hibernate默认支持的ehcache缓存组件。

3.对于缓存中数据存储的要求:
允许存放:
(1) 频繁查询的数据
(2) 不太重要的数据,偶尔可以出现并发访问问题的数据
(3) 数据不是经常变化的,例如:下拉列选选项(国家,地区,城市,省份)
不允许存放:
(1) 经常修改的
(2) 非常重要,要求准确性高的数据(例如:财务数据)
(3) 被第三应用修改的数据

4.缓存架构:


5.二级缓存的访问策略:


6.常用的缓存组件:


7.ehcache缓存组件使用:
(1) 拷贝ehcache缓存组件jar包
ehcache-core-2.4.3.jar
hibernate-ehcache-4.2.4.Final.jar
slf4j-api-1.6.1.jar
(2) 拷贝缓存组件的配置文件ehcache.xml
(3) 启用二级缓存(hibernate.cfg.xml)
<!-- 启用二级缓存 -->
<propertyname="hibernate.cache.use_second_level_cache">true</property>
(4) 设置二级缓存工厂类(紧跟启用二级缓存后面 )
<!-- 缓存提供者:用于构建缓存区
     参考:hibernate.properties
          org.hibernate.cache.internal.EhCacheRegionFactory
-->
<property name="hibernate.cache.region.factory_class">org.hibernate.cache.ehcache.EhCacheRegionFactory</property>
(5) 配置类级别的缓存(在主配置文件的管理映射配置后面)
<!-- 告诉框架,类级别(使用)的二级缓存 -->
<class-cache usage="read-only" class="com.pers.mapping.bean.Department"/>
(6) 配置集合级别的缓存(在主配置文件的管理映射配置后面)
<class-cache usage="read-only" class="com.pers.mapping.bean.Employee"/>
<collection-cache usage="read-only" collection="com.pers.mapping.bean.Department.empSet"/>
     注意 :如果配置集合对象的二级缓存,但没有配置集合元素的二级缓存,则对集合对象中保存的就仅仅是元素的OID。在需要使用集合对象时,Hibernate会根据每一个OID再发送SQL语句查询具体的元素对象,导致额外多出很多SQL语句。
     还需要配置集合中元素的类级别二级缓存:
<class-cache usage="read-only" class="com.pers.mapping.bean.Employee"/>
(7) 查询缓存:
HQL查询默认是不支持二级缓存的,如果希望支持二级缓存,需要配置查询缓存和在程序中启用二级缓存。
<!-- 启用查询缓存 -->
<property name="hibernate.cache.use_query_cache">true</property>
          程序中需要启用查询缓存 :query.setCacheable(true);
(8) 更新时间戳缓存:具体代码请看实验
(9) iterate():
会出现 N + 1 条语句问题:
     第一次查询时,会发送一条只查询id的语句,封装为代理对象,存放到迭代器中
     迭代时,根据OID作为where条件,再发送一条条的查询语句,封装当前迭代对象(代理);
不支持查询缓存,支持一级,二级缓存。
     至少发送一条语句。
如果是投影查询,不支持缓存的。同时也不会出现N+1条语句问题了
一般结合其他的支持缓存的方法一起使用,效率才会高。
          如果缓存中有实体对象,那么就利用缓存中对象,否则就封装为代理对象 

8. ehcache自身配置文件简单解析:
<!--
     指定一个目录:当 ehcache 把数据写到硬盘上时, 将把数据写到这个目录下.
-->
<diskStore path="D:/cache/"/>

<!--
     设置缓存数据默认的过期策略
     maxElementsInMemory="10000":缓存中最大允许创建的对象数
     eternal="false":缓存中对象是否为永久的,如果是,超时设置将被忽略,对象从不过期
     timeToIdleSeconds="120":缓存数据钝化时间(设置对象在它过期之前的空闲时间)
     timeToLiveSeconds="120":缓存数据的生存时间(设置对象在它过期之前的生存时间)
     overflowToDisk="true":内存不足时,是否启用磁盘缓存
-->
<defaultCache
    maxElementsInMemory="10000"
    eternal="false"
    timeToIdleSeconds="120"
    timeToLiveSeconds="120"
    overflowToDisk="true"
/>

    <!--
   设定具体的命名缓存的数据过期策略。每个命名缓存代表一个缓存区域
   缓存区域(region):一个具有名称的缓存块,可以给每一个缓存块设置不同的缓存策略。
   如果没有设置任何的缓存区域,则所有被缓存的对象,都将使用默认的缓存策略。即:<defaultCache.../>
         Hibernate 在不同的缓存区域保存不同的类/集合。
           对于类而言,区域的名称是类名。如:com.pers.domain.Customer
           对于集合而言,区域的名称是类名加属性名。如com.pers.domain.Customer.orders
    -->

    <!--
      name:设置缓存的名字,它的取值为类的全限定名或类的集合的名字
      maxElementsInMemory:设置基于内存的缓存中可存放的对象最大数目
      eternal:设置对象是否为永久的,true表示永不过期
      此时将忽略timeToIdleSeconds 和 timeToLiveSeconds属性;默认值是false

      timeToIdleSeconds:设置对象空闲最长时间,以秒为单位,超过这个时间,对象过期。
      当对象过期时,EHCache会把它从缓存中清除。如果此值为0,表示对象可以无限期地处于空闲状态。

      timeToLiveSeconds:设置对象生存最长时间,超过这个时间,对象过期。
      如果此值为0,表示对象可以无限期地存在于缓存中。该属性值必须大于或等于timeToIdleSeconds属性值

      overflowToDisk:设置基于内存的缓存中的对象数目达到上限后,是否把溢出的对象写到基于硬盘的缓存中
    -->

<cache name="com.pers.hibernate.entities.Employee"
    maxElementsInMemory="1"
    eternal="false"
    timeToIdleSeconds="300"
    timeToLiveSeconds="600"
    overflowToDisk="true"
    />

<cache name="com.pers.hibernate.entities.Department.emps"
    maxElementsInMemory="1000"
    eternal="true"
    timeToIdleSeconds="0"
    timeToLiveSeconds="0"
    overflowToDisk="false"
/>
9.实验代码:
(1) 实体类:
(2) Department.hbm.xml
<?xml version="1.0"?>     
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="com.pers.mapping.bean">
    <class name="Department" table="DEPTS">
     <!-- <cache usage="read-only"/> -->
        <id name="deptId" type="java.lang.Integer">
            <column name="DEPT_ID" />
            <generator class="native" />
        </id>
        <property name="deptName" type="java.lang.String">
            <column name="dept_name" />
        </property>
        <set name="empSet" inverse="true">
           <!-- <cache usage="read-only"/> -->
            <key>
                <column name="dept_id_fk" />
            </key>
            <one-to-many class="Employee" />
        </set>
    </class>
</hibernate-mapping>
(3) Employee.hbm.xml
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="com.pers.mapping.bean">
    <class name="Employee" table="EMPS">

        <id name="empId" type="java.lang.Integer">
            <column name="EMP_ID" />
            <generator class="native" />
        </id>
        <property name="empName" type="java.lang.String">
            <column name="emp_name" />
        </property>
        <property name="salary" type="double">
            <column name="SALARY" />
        </property>
        <property name="birthday" type="timestamp">
            <column name="birthday" />
        </property>
        <property name="telephone" type="java.lang.String">
            <column name="TELEPHONE" />
        </property>
        <many-to-one name="department" class="Department">
            <column name="dept_id_fk" />
        </many-to-one>
    </class>

    <query name="selectEmployee">
     <![CDATA[
           FROM Employee e WHERE e.salary < :salary ORDER BY e.empName ASC
     ]]>
    </query>

</hibernate-mapping>
(4) 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.driver_class">com.mysql.jdbc.Driver</property>
     <property name="hibernate.connection.url">jdbc:mysql://localhost:3306/hibernate1109</property>
     <property name="hibernate.connection.username">root</property>
     <property name="hibernate.connection.password">123456</property>
     <property name="hibernate.dialect">org.hibernate.dialect.MySQLInnoDBDialect</property>

     <property name="hibernate.show_sql">true</property>
     <property name="hibernate.format_sql">true</property>
     <property name="hibernate.hbm2ddl.auto">update</property>

           <!-- 修改每次请求连接的事务隔离级别 -->
           <property name="hibernate.connection.isolation">2</property>

           <property name="hibernate.use_identifier_rollback">true</property>

           <!-- 启用二级缓存 -->
           <property name="hibernate.cache.use_second_level_cache">true</property>

           <!-- 缓存提供者:用于构建缓存区
                参考:hibernate.properties
                     org.hibernate.cache.internal.EhCacheRegionFactory
            -->
           <property name="hibernate.cache.region.factory_class">org.hibernate.cache.ehcache.EhCacheRegionFactory</property>

           <!-- 启用查询缓存 -->
           <property name="hibernate.cache.use_query_cache">true</property>

           <!--
                表示从当前Session上下文中获取Session :
                     thread : 表示框架为当前线程绑定上一个Session对象,然后,通过sessionFactory.getCurrentSession()获取线程中绑定的Session.
            -->
           <property name="hibernate.current_session_context_class">thread</property>

           <mapping resource="com/pers/mapping/bean/Department.hbm.xml"/>
           <mapping resource="com/pers/mapping/bean/Employee.hbm.xml"/>

           <!-- 告诉框架,哪些类级别对象需要存放到二级缓存区中 -->
           <!-- 设置类级别缓存对象 -->
           <class-cache usage="read-write" class="com.pers.mapping.bean.Department"/>
           <class-cache usage="read-only" class="com.pers.mapping.bean.Employee"/>

           <!-- 设置集合级别缓存对象 -->
           <collection-cache usage="read-only" collection="com.pers.mapping.bean.Department.empSet"/>

    </session-factory>
</hibernate-configuration>
(5) 实验代码:
package junit.test;
import java.util.Iterator;
import java.util.Set;
import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;
import org.hibernate.service.ServiceRegistry;
import org.hibernate.service.ServiceRegistryBuilder;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import com.pers.mapping.bean.Department;
import com.pers.mapping.bean.Employee;

public class TestLevel2Cache {

     SessionFactory sessionFactory;
     Session session;

     @Before
     public void setUp() {
           Configuration cfg = new Configuration();
           cfg.configure();

           ServiceRegistryBuilder srb = new ServiceRegistryBuilder();
           srb.applySettings(cfg.getProperties());

           ServiceRegistry serviceRegistry = srb.buildServiceRegistry();

           sessionFactory = cfg.buildSessionFactory(serviceRegistry);
           session = sessionFactory.openSession();
           session.beginTransaction();
     }

     @After
     public void tearDown() {
           session.getTransaction().commit();
           session.close();
     }

     @Test
     public void testCreateTable() {
     }

     @Test
     public void testCache01(){
           //实验1:测试类级别的二级缓存
           //判断一级缓存,二级缓存中是否存在,如果存在,那么就从缓存中获取,一级,二级中都没有,查询数据库
           //查询到的对象会存放到一级,二级缓存中。给下次访问利用
           Department dept = (Department)session.get(Department.class, 1); //发送SQL
           System.out.println(dept.getDeptName());

           System.out.println("---------------------------");
           session.getTransaction().commit();
           session.close(); //session关闭,一级缓存失效了,二级缓存没有失效

           session = sessionFactory.openSession();//新session对象,一级缓存中没有
           session.beginTransaction();

           dept = (Department)session.get(Department.class, 1); //二级缓存中存在,就直接利用二级缓存获取数据,会将对象存放到一级缓存中。
           System.out.println(dept.getDeptName());

     }

     @Test
     public void testCache02(){
           //实验2:测试集合级别的二级缓存
           Department dept = (Department)session.get(Department.class, 1);
           Set<Employee> empSet = dept.getEmpSet();
           for (Employee employee : empSet) {
                System.out.println(employee.getEmpName());
           }

           dept.setDeptName("xxxx"); //持久化状态,被session管理,提交事务会做更新操作,会报错。

           System.out.println("---------------------------");
           session.getTransaction().commit();
           session.close(); //session关闭,一级缓存失效了,二级缓存没有失效

           session = sessionFactory.openSession();
           session.beginTransaction();

           dept = (Department)session.get(Department.class, 1);
           empSet = dept.getEmpSet();
           for (Employee employee : empSet) {
                System.out.println(employee.getEmpName());
           }
     }


     @Test
     public void testCache03(){
           //实验3:测试查询缓存
           Query query = session.createQuery("From Department");
           query.setCacheable(true);
           //list()特点: 支持缓存的,但是自己不利用缓存。
           //list()会将查询结果存放到一级,二级缓存中,但是自己不去利用,给别的方法利用的。
           // 如果希望list()方法也可以利用二级缓存,可以配置查询缓存。
           query.list();

           session.getTransaction().commit();
           session.close();

           session = sessionFactory.openSession();
           session.beginTransaction();
           System.out.println("------------------");

           query = session.createQuery("From Department");
           query.setCacheable(true);
           query.list();
     }


     @Test
     public void testCache04(){
           //实验4:投影查询
           Query query = session.createQuery("SELECT d.deptName From Department d where d.deptId < 5");
           query.setCacheable(true);
           query.list();

           System.out.println("------------------");
           //投影查询的数据,会存放到查询缓存区,不会放置到一级,二级缓存区
           //查询条件一旦变化(不管是实体查询,还是投影查询),那么,就不再利用查询缓存区;
           query = session.createQuery("SELECT d.deptName From Department d where d.deptId < 3");
           query.setCacheable(true);
           query.list();

           System.out.println("------------------");
           query = session.createQuery("SELECT d.deptName From Department d where d.deptId < 5");
           query.setCacheable(true);
           query.list();
     }


     @Test
     public void testCache05(){
           //实验5:测试更新时间戳缓存
           // T1 : 查询数据库时,存放到缓存中数据的时刻
           Query query = session.createQuery("From Department");
           query.setCacheable(true);
           query.list();

           // T2 : 将缓存中数据进行了修改,修改数据的时刻,将T2时刻(修改数据当前时间)存放到更新时间戳缓存区中。
           Department dept = (Department)session.get(Department.class,1);
           //dept.setDeptName("yyyy");

           // 是否利用缓存中数据?
           // T2 > T1 : 说明缓存中数据被修改过,那么,就不再利用缓存来获取数据,而是从数据库中获取数据
           // T2 < T1 : 说明缓存中数据没有被修改过,那么,就利用缓存获取数据。
           query = session.createQuery("From Department");
           query.setCacheable(true);
           query.list();
     }

     @Test
     public void test06(){
           //实验6:测试Query.iterate()方法
           // N + 1 条语句问题:
           //第一次查询时,会发送一条只查询id的语句,封装为代理对象,存放到迭代器中
           //迭代时,根据OID作为where条件,再发送一条条的查询语句,封装当前迭代对象;
           Query query = session.createQuery("From Department");
           Iterator<Department> iterate = query.iterate();
           while(iterate.hasNext()){
                Department dept = iterate.next();
                //System.out.println(dept.getClass());
                System.out.println(dept.getDeptId());
                System.out.println(dept.getDeptName());
           }
           System.out.println("----------------------------");
           //不支持查询缓存,支持一级,二级缓存,至少发送一条语句。
           query = session.createQuery("From Department");
           iterate = query.iterate();
           while(iterate.hasNext()){
                Department dept = iterate.next();
                System.out.println(dept.getDeptId());
                System.out.println(dept.getDeptName());
           }
     }

     @Test
     public void test07(){
            //实验7:iterate()
           Query query = session.createQuery("SELECT d.deptName From Department d");
           Iterator<String> iterate = query.iterate();
           while(iterate.hasNext()){
                String deptName = iterate.next();
                System.out.println(deptName);
           }
           System.out.println("----------------------------");
           //如果是投影查询,不支持缓存的。同时也不会出现N+1条语句问题了
           query = session.createQuery("SELECT d.deptName From Department d");
           iterate = query.iterate();
           while(iterate.hasNext()){
                String deptName = iterate.next();
                System.out.println(deptName);
           }
     }

     @Test
     public void test08(){
           //iterate()一般结合其他的支持缓存的方法一起使用,效率才会高。
           Query query = session.createQuery("From Department d");
           //query.setCacheable(true);
           query.list();

           query = session.createQuery("From Department d");
           Iterator<Department> iterate = query.iterate();
           while(iterate.hasNext()){
                Department dept = iterate.next();
                System.out.println(dept.getClass()); //如果缓存中有实体对象,那么就利用缓存中对象,否则就封装为代理对象
                System.out.println(dept.getDeptName());
           }
     }
}

1
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:15765次
    • 积分:1292
    • 等级:
    • 排名:千里之外
    • 原创:110篇
    • 转载:4篇
    • 译文:0篇
    • 评论:3条
    最新评论