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

hibernate集成ehcahe进行缓存管理的更多相关文章

  1. 攻城狮在路上(壹) Hibernate(十八)--- 管理Hibernate的缓存

    一般Session的缓存被称为Hibernate的第一级缓存,SessionFactory的外置缓存是一个可配置的缓存插件,称为Hibernate的第二级缓存.一.缓存的基本原理: 1.持久化层的缓存 ...

  2. 三大框架 之 Hibernate生成策略与缓存策略(主键生成策略、持久化、持久化类划分、一级缓存、事物管理)

    目录 Hibernate生成策略与缓存策略 主键生成策略 主键分类 主键的生成策略 持久化 什么是持久化 什么是持久化类 持久化类编写规则 持久化类的划分 三种状态区分 持久态对象特征 一级缓存 什么 ...

  3. Hibernate三种状态,缓存,以及update更新问题

    一. Hibernate中对象的三种状态 1. 瞬时状态(transient) 当我们通过Java的new关键字来生成一个实体对象时,这时这个实体对象就处于自由状态,此时该对象只是通过JVM获得了一块 ...

  4. Hibernate-ORM:16.Hibernate中的二级缓存Ehcache的配置

    ------------吾亦无他,唯手熟尔,谦卑若愚,好学若饥------------- 本篇博客讲述Hibernate中的二级缓存的配置,作者将使用的是ehcache缓存 一,目录 1.二级缓存的具 ...

  5. (37)Spring Boot集成EHCache实现缓存机制【从零开始学Spring Boot】

    [本文章是否对你有用以及是否有好的建议,请留言] 写后感:博主写这么一系列文章也不容易啊,请评论支持下. 如果看过我之前(35)的文章这一篇的文章就会很简单,没有什么挑战性了. 那么我们先说说这一篇文 ...

  6. (35)Spring Boot集成Redis实现缓存机制【从零开始学Spring Boot】

    [本文章是否对你有用以及是否有好的建议,请留言] 本文章牵涉到的技术点比较多:Spring Data JPA.Redis.Spring MVC,Spirng Cache,所以在看这篇文章的时候,需要对 ...

  7. SpringBoot2 整合Ehcache组件,轻量级缓存管理

    本文源码:GitHub·点这里 || GitEE·点这里 一.Ehcache缓存简介 1.基础简介 EhCache是一个纯Java的进程内缓存框架,具有快速.上手简单等特点,是Hibernate中默认 ...

  8. Spring自定义缓存管理及配置Ehcache缓存

    spring自带缓存.自建缓存管理器等都可解决项目部分性能问题.结合Ehcache后性能更优,使用也比较简单. 在进行Ehcache学习之前,最好对Spring自带的缓存管理有一个总体的认识. 这篇文 ...

  9. Hibernate的三种缓存

    一级缓存 hibernate的一级缓存是跟session绑定的,那么一级缓存的生命周期与session的生命周期一致,因此,一级缓存也叫session级缓存或者事务级缓存. 支持一级缓存的方法有: q ...

随机推荐

  1. Ubuntu Server : 自动更新

    Ubuntu(16.04/18.04) 默认会每天自动安装系统的安全更新,但是不会自动安装包的更新.本文梳理 Ubuntu 16.04/18.04 系统的自动更新机制,并介绍如何配置系统自动更新所有的 ...

  2. flask入门 七行代码讲解

    # 导包 从flask里面导入Flask这个对象.from flask import Flask # 实例化一个对象,app = Flask(__name__) # 里面的 __name__ 是为了定 ...

  3. 06 css选择器

    选择器的作用:选中标签 1.基本选择器  标签选择器 id选择器 class选择器 *通配符选择器 权重:行内样式 1000 > id选择器 100 > 类选择器10 > 标签选择器 ...

  4. Unity 自定义Inspector面板时的数据持久化问题

    自定义Inspector面板的步骤: Unity内创建自定义的Inspector需要在Asset的任意文件夹下创建一个名字是Editor的文件夹,随后这个文件夹内的cs文件就会被放在vstu生成的Ed ...

  5. Mybatis延迟加载的实现以及使用场景

    首先我们先思考一个问题,假设:在一对多中,我们有一个用户,他有100个账户. 问题1:在查询用户的时候,要不要把关联的账户查出来? 问题2:在查询账户的时候,要不要把关联的用户查出来? 解答:在查询用 ...

  6. 从技术小白到收获BAT研发offer,分享我的学习经验和感悟(赠送相关学习资料)

    去年秋季参加了校园招聘,有幸拿到了BAT.头条.网易.滴滴.亚马逊.华为等offer,经过研究生两年的学习积累,终于达成了自己的目标,期间也经历了很多,谨以此文,聊表感叹,也会分享很多我的Java学习 ...

  7. Leetcode之二分法专题-441. 排列硬币(Arranging Coins)

    Leetcode之二分法专题-441. 排列硬币(Arranging Coins) 你总共有 n 枚硬币,你需要将它们摆成一个阶梯形状,第 k 行就必须正好有 k 枚硬币. 给定一个数字 n,找出可形 ...

  8. Java内部类使用注意事项

    Java内部类使用注意事项: 1. 非静态内部类成员可以访问外部类实例成员 (如注释1),但外部类访问非静态内部类的成员 必须创建非静态内部类对象来访问其成员,如注释2 public class La ...

  9. HDU-1532 网络流裸题

    HDU-1532 题意简单的来说就是从1点到n点,最大的流量是多少. 代码: #include<bits/stdc++.h> using namespace std; #define Fo ...

  10. 杭电2018暑假多校第一场 D Distinct Values hdu6301 贪心

    Distinct Values Time Limit: 4000/2000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)T ...