zoukankan      html  css  js  c++  java
  • hibernate集成ehcahe进行缓存管理

    ehcace是现在非常流行的缓存框架,有轻量、灵活、可扩展、支持集群/分布式等优点。

    在项目中,使用ehcace可以对数据进行缓存(一般使用、基于注解、基于aop),使用filter可以对页面进行缓存(SimplePageCachingFilter过滤器),与hibernate整合可以对对象进行缓存(二级缓存、查询缓存)。

    简单的说使用缓存的方式主要分为数据层缓存、服务层缓存和页面缓存三种,它们一层比一层高效,实现也越来越复杂,在实际应用中最好能在尽量靠近用户的地方缓存,减少之后各层处理的压力,提高响应速度。

    这篇文章先介绍hibernate的部分:二级缓存和查询缓存。

    一、二级缓存

    hibernate是自带一级缓存(session级别、事务级缓存)的,在一次请求中查询出的对象会被缓存,之后使用这个对象的时候会从缓存中取(不必多次访问数据库了)。

    不过在这次请求处理结束、session关闭后,缓存中的数据就被清除了,第二次请求里用到的话还是需要再查一次。

    如果想缓存一次还可以共享给之后的请求,就需要hibernate开启二级缓存了(sessionFactory级别、应用级缓存),它是跨session的,由sessionFactroy管理。

    不过hibernate没有提供相应的二级缓存组件,需要加入额外的二级缓存包,常用的就是ehcache了,下面是hibernate集成ehcache进行二级缓存的配置方法(用一个较早的demo版本作为基础):

    1、添加jar包,修改pom.xml文件,加入:

    <dependency>
        <groupId>org.hibernate</groupId>
        <artifactId>hibernate-ehcache</artifactId>
        <version>4.2.21.Final</version>
    </dependency>
    <dependency>
        <groupId>net.sf.ehcache</groupId>
        <artifactId>ehcache-core</artifactId>
        <version>2.6.11</version>
    </dependency>
    

    2、修改spring-context-hibernate.xml,在hibernateProperties里增加3行配置:

    <property name="hibernateProperties">
        <props>
            <prop key="hibernate.dialect">org.hibernate.dialect.MySQLDialect</prop>
            <prop key="hibernate.hbm2ddl.auto">none</prop>
            <prop key="hibernate.show_sql">${hibernate.show_sql}</prop>
            <prop key="hibernate.format_sql">false</prop>
            <!-- 开启二级缓存 -->
            <prop key="hibernate.cache.use_second_level_cache">true</prop>
            <!-- 二级缓存的提供类 -->
            <prop key="hibernate.cache.region.factory_class">org.hibernate.cache.ehcache.EhCacheRegionFactory</prop>
            <!-- 二级缓存配置文件的位置 -->
            <prop key="net.sf.ehcache.configurationResourceName">ehcache-hibernate.xml</prop>
        </props>
    </property>
    

    3、在"src/main/resources"代码文件夹中新建文件"ehcache-hibernate.xml",内容为:

    <?xml version="1.0" encoding="UTF-8"?>
    <ehcache updateCheck="false">
        <!-- Cache配置项说明
            必须项
            name                                  非默认Cache配置的名称,唯一的
            maxEntriesLocalHeap                   在内存中缓存的最大对象数(默认值为0,表示不限制)
            maxEntriesLocalDisk                   在磁盘中缓存的最大对象数(默认值为0,表示不限制)
            overflowToDisk                        如果对象数量超过内存中最大的数,是否将其保存到磁盘中
            eternal                               缓存是否永远不过期(如果为false,还需要根据timeToIdleSeconds、timeToLiveSeconds判断)
            timeToIdleSeconds                     对象的空闲时间(默认值为0秒,表示一直可以访问)
            timeToLiveSeconds                     对象的存活时间(默认值为0秒,表示一直可以访问)
                                                      1、如果仅设置了timeToLiveSeconds,则该对象的超时时间=创建时间+timeToLiveSeconds,假设为A;
                                                      2、如果没设置timeToLiveSeconds,则该对象的超时时间=max(创建时间,最近访问时间)+timeToIdleSeconds,假设为B;
                                                      3、如果两者都设置了,则取出A、B最少的值,即min(A,B),表示只要有一个超时成立即算超时。                                                                   
            可选项
            maxBytesLocalHeap                     在内存中缓存的最大字节数(与maxEntriesLocalHeap属性不能同时指定,值可以加单位(K、M、G))
            maxBytesLocalDisk                     在磁盘中缓存的最大字节数(与maxEntriesLocalDisk属性不能同时指定,值可以加单位(CacheManager指定后可以加百分比)
                                                      指定后会隐式让当前cache的overflowToDisk为true)
            diskExpiryThreadIntervalSeconds       清理保存在磁盘上的过期缓存项目线程的启动时间间隔(默认值为120秒)
            diskSpoolBufferSizeMB                 写入磁盘的缓冲区大小(默认为30MB,如果遇到OutOfMemory可以减小这个值)
            clearOnFlush                          Cache的flush()方法调用时,是否清空MemoryStore(默认为true)
            statistics                            是否收集统计信息(默认为false,如果要监控缓存使用情况就开启,会影响性能)
            memoryStoreEvictionPolicy             当内存中缓存的对象数或字节数达到设定的上限时,如果overflowToDisk=false,就采用淘汰策略替换对象(默认为LRU,可选FIFO、LFU)
                                                      1、FIFO(first in first out 先进先出):淘汰最先进入的数据
                                                      2、LFU(Less Frequently Used 最少使用):淘汰最长时间没有被访问的数据
                                                      3、LRU(Least Recently Used 最近最少使用):淘汰一段时间内使用次数最少的数据
            copyOnRead                            当缓存被读出时,是否返回一份它的拷贝(默认为false)
            copyOnWrite                           当缓存被写入时,是否写入一份它的拷贝(默认为false)
        -->
         
        <!--默认的缓存配置(可以给每个实体类指定一个对应的缓存,如果没有匹配到该类,则使用这个默认的缓存配置)-->
        <defaultCache 
            maxEntriesLocalHeap="10000"
            maxEntriesLocalDisk="100000"
            overflowToDisk="true"
            eternal="false"
            timeToIdleSeconds="300"
            timeToLiveSeconds="600"
        />
         
        <!-- <cache name="org.xs.techblog.modules.blog.entity.Daily" maxEntriesLocalHeap="1000" eternal="false" /> -->
         
        <!-- 指定缓存存放在磁盘上的位置 -->
        <diskStore path="java.io.tmpdir/demo1/ehcache/hibernate" />
    </ehcache>
    

      

    4、在实体类中增加1行@Cache注释

    @Entity
    @Table(name="test")
    @Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
    public class testInfo {
    

    其中CacheConcurrencyStrategy有5种并发性策略:

    CacheConcurrencyStrategy.NONE                  不使用缓存,默认的策略
    CacheConcurrencyStrategy.READ_ONLY             只读模式,如果对数据更新了会报异常,适合不改动的数据
    CacheConcurrencyStrategy.READ_WRITE            读写模式,更新缓存时会对缓存数据加锁,其他事务如果去取,发现被锁了,直接就去数据库查询
    CacheConcurrencyStrategy.NONSTRICT_READ_WRITE  不严格的读写模式,更新缓存时不会加锁
    CacheConcurrencyStrategy.TRANSACTIONAL         事务模式,支持回滚,当事务回滚时,缓存也能回滚
    

    通常都是配置成只读模式的,读写模式的就具有事务隔离性了,而事务模式的事务隔离性最高。如果某些实体的数据经常修改、经常需要对缓存进行更新,性能就会变差,缓存也就失去了意义,这时就不如不用

    5、增加相关方法、页面进行测试

    testDao.java中增加方法:

    public testInfo getInfo(String id) {        
        return (testInfo) sessionFactory.getCurrentSession().get(testInfo.class, id);
    }
    

     

    HelloController.java中增加方法:

    @RequestMapping("list")
    public String list(HttpServletRequest request) {
             
        List<testInfo> list = testDao.getList();
        request.setAttribute("testList", list);
             
        return "list";
    }
         
    @RequestMapping("view/{id}")
    public String view(@PathVariable("id") String id, HttpServletRequest request) {
             
        testInfo info = testDao.getInfo(id);     
        request.setAttribute("testInfo", info);
             
        return "view";
    }
    

    views中增加list.jsp、view.jsp页面:

    list.jsp:

    <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
    <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
    <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
    <html>
        <head>
            <meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
            <title>Insert title here</title>
            <%
                /* 当前基础url地址 */
                String path = request.getContextPath();
                request.setAttribute("path", path);
            %>
        </head>
        <body>
            <c:if test="${!empty testList}">
                <table border="1" width="100px">
                    <tr>
                        <th>列1</th>
                        <th>列2</th>
                    </tr>
                    <c:forEach items="${testList}" var="item">
                        <tr>
                            <td>${item.id}</td>
                            <td><a href="${path}/hello/view/${item.id}" target="_blank">${item.name}</a></td>
                        </tr>
                    </c:forEach>
                </table>
            </c:if>
        </body>
    </html>
    

    view.jsp:

    <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
    <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
    <html>
        <head>
            <meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
            <title>Insert title here</title>
        </head>
        <body>
            <table border="1" width="100px">
                <tr>
                    <th>列1</th>
                    <td>${testInfo.id}</td>
                </tr>
                <tr>
                    <th>列2</th>
                    <td>${testInfo.name}</td>
                </tr>
            </table>
        </body>
    </html>
    

    (需要在pom.xml中增加jstl、standard,来开启对标签的支持,这里略)

    运行测试,访问"http://localhost:8080/demo1/hello/list":

    接着点击"666",弹出"http://localhost:8080/demo1/hello/view/2"页面:

    Console信息没有变化,还是:

    说明二级缓存生效了,第二次请求访问对象"666"的时候,已经是从之前缓存的数据里取了,没有再访问数据库

    注:二级缓存缓存的是完整的对象,所以如果查询的是对象的某个属性,就不会添加添加到缓存里

    二、查询缓存

    二级缓存和查询缓存都是sessionFactory级别的,它们都相当于一个map,不同的是:

    二级缓存的map是<对象id, 对象实体>的集合
    查询缓存的map是<sql语句, 结果集合>的集合
    

    二级缓存适用于单个对象重复使用的情况,不能缓存集合,如果是某个hql语句的结果集合要重复使用,就需要再开启查询缓存了(一般二级缓存都是和查询缓存搭配使用)。

    在一次list查询后,查询缓存会将hql转换后的sql语句作为key,然后将查询的结果作为value缓存起来,下面是配置方法:

    1、修改spring-context-hibernate.xml,在hibernateProperties里增加1行配置:

    <property name="hibernateProperties">
        <props>
            ...
            ...
            <!-- 开启查询缓存 -->
            <prop key="hibernate.cache.use_query_cache">true</prop>
        </props>
    </property>
    

    在use_query_cache设置为true后,ehcache将会创建两个缓存区域:默认用StandardQueryCache保存查询结果集,UpdateTimestampsCache保存查询缓存的时间戳,所以可以在ehcache-hibernate.xml中增加这两项,属于可选配置:

    <cache name="net.sf.hibernate.cache.StandardQueryCache" 
        maxEntriesLocalHeap="10000"
        maxEntriesLocalDisk="100000"
        overflowToDisk="true"
        eternal="false"
        timeToIdleSeconds="300"
        timeToLiveSeconds="600"
    /> 
    <cache name="net.sf.hibernate.cache.UpdateTimestampsCache" 
        ...
        ...
    />
    

    2、在Dao层的查询条件设置"setCacheable(true)"

    String hql = "from testInfo";
    Query query = sessionFactory.getCurrentSession().createQuery(hql);
    query.setCacheable(true); //开启查询缓存
    return query.list();
    

    有一个可选设置:

    query.setCacheRegion("myCacheRegion"); //单独指定缓存名称,在ehcache-hibernate.xml中配置,替代StandardQueryCache
    

    3、运行测试,访问"http://localhost:8080/demo1/hello/list":

    Console信息:

    之后这个地址重复刷新多次,Console信息中始终只有1条sql语句,说明查询缓存开启成功了

    注:只有当hql查询语句完全相同、参数的值也完全相同时,查询缓存才有效,所以查询缓存的命中率是比较低的

    当数据表中的任意数据发生一点修改时,整个表相关的查询缓存就失效了

    (因为表数据修改后,时间戳更新,UpdateTimestampsCache里的时间戳不再是最新了,无法匹配所以缓存失效)

    只有通过hibernate的hql修改数据才会刷新时间戳,如果直接使用sql或者使用其他应用程序修改数据库就无法监测到了

    (因此query接口提供一个补救方法直接清除查询缓存:query.setForceCacheRefresh(true))

    实例代码地址:https://github.com/ctxsdhy/cnblogs-example

  • 相关阅读:
    Python常用内置函数整理(lambda,reduce,zip,filter,map)
    C#中Hashtable容器的了解与使用
    关于ref与out的区别
    关于多线程学习总结(五) 线程池
    关于多线程学习总结(四) 锁
    关于多线程学习总结(三) 线程简单基本操作
    关于多线程学习总结(二) 了解线程的属性及方法
    关于多线程学习总结(一) 基本概念了解
    CSS文件和Javascript文件的压缩
    看几道JQuery试题后总结(下篇)
  • 原文地址:https://www.cnblogs.com/ctxsdhy/p/6403104.html
Copyright © 2011-2022 走看看