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
hibernate集成ehcahe进行缓存管理的更多相关文章
- 攻城狮在路上(壹) Hibernate(十八)--- 管理Hibernate的缓存
一般Session的缓存被称为Hibernate的第一级缓存,SessionFactory的外置缓存是一个可配置的缓存插件,称为Hibernate的第二级缓存.一.缓存的基本原理: 1.持久化层的缓存 ...
- 三大框架 之 Hibernate生成策略与缓存策略(主键生成策略、持久化、持久化类划分、一级缓存、事物管理)
目录 Hibernate生成策略与缓存策略 主键生成策略 主键分类 主键的生成策略 持久化 什么是持久化 什么是持久化类 持久化类编写规则 持久化类的划分 三种状态区分 持久态对象特征 一级缓存 什么 ...
- Hibernate三种状态,缓存,以及update更新问题
一. Hibernate中对象的三种状态 1. 瞬时状态(transient) 当我们通过Java的new关键字来生成一个实体对象时,这时这个实体对象就处于自由状态,此时该对象只是通过JVM获得了一块 ...
- Hibernate-ORM:16.Hibernate中的二级缓存Ehcache的配置
------------吾亦无他,唯手熟尔,谦卑若愚,好学若饥------------- 本篇博客讲述Hibernate中的二级缓存的配置,作者将使用的是ehcache缓存 一,目录 1.二级缓存的具 ...
- (37)Spring Boot集成EHCache实现缓存机制【从零开始学Spring Boot】
[本文章是否对你有用以及是否有好的建议,请留言] 写后感:博主写这么一系列文章也不容易啊,请评论支持下. 如果看过我之前(35)的文章这一篇的文章就会很简单,没有什么挑战性了. 那么我们先说说这一篇文 ...
- (35)Spring Boot集成Redis实现缓存机制【从零开始学Spring Boot】
[本文章是否对你有用以及是否有好的建议,请留言] 本文章牵涉到的技术点比较多:Spring Data JPA.Redis.Spring MVC,Spirng Cache,所以在看这篇文章的时候,需要对 ...
- SpringBoot2 整合Ehcache组件,轻量级缓存管理
本文源码:GitHub·点这里 || GitEE·点这里 一.Ehcache缓存简介 1.基础简介 EhCache是一个纯Java的进程内缓存框架,具有快速.上手简单等特点,是Hibernate中默认 ...
- Spring自定义缓存管理及配置Ehcache缓存
spring自带缓存.自建缓存管理器等都可解决项目部分性能问题.结合Ehcache后性能更优,使用也比较简单. 在进行Ehcache学习之前,最好对Spring自带的缓存管理有一个总体的认识. 这篇文 ...
- Hibernate的三种缓存
一级缓存 hibernate的一级缓存是跟session绑定的,那么一级缓存的生命周期与session的生命周期一致,因此,一级缓存也叫session级缓存或者事务级缓存. 支持一级缓存的方法有: q ...
随机推荐
- maven学习(1)下载和安装和初步使用(手动构建项目和自动构建项目)
1:背景 关于项目的搭建,有些人使用开发工具搭建项目,然后将项目所依赖第三方jar 复制到类路径下面,上述搭建方式没有第三方类库的依赖关系,在导入一个jar包的时候,这个jar包还可能依赖其他jar包 ...
- 你是否真的了解全局解析锁(GIL)
关于我 一个有思想的程序猿,终身学习实践者,目前在一个创业团队任team lead,技术栈涉及Android.Python.Java和Go,这个也是我们团队的主要技术栈. Github:https:/ ...
- 打包一沓开源的 C/C++ 包管理工具送给你!
本文作者:HelloGitHub-ChungZH 博客地址:https://chungzh.cn/ 包管理器可以帮助你更方便地安装依赖关系,并决定所安装的版本,提高你的开发幸福感.许多语言都有自己的包 ...
- Scala 系列(二)—— 基本数据类型和运算符
一.数据类型 1.1 类型支持 Scala 拥有下表所示的数据类型,其中 Byte.Short.Int.Long 和 Char 类型统称为整数类型,整数类型加上 Float 和 Double 统称为数 ...
- 随笔编号-15 重构--改善既有代码的设计--Day01--学习笔记
最近公司开发的系统在进行大批量数据查询的时候发现响应速度变得让人无法忍受,so 老大安排我进行代码重构的工作,主要目的就是为提高代码的执行效率.减小方法之间的响应时间.降低方法之间的耦合度.= =! ...
- STM32F0系列芯片SPI发送一字节数据却输出16个CLK时钟的解决办法
问题 上一个项目在用寄存器操作STM32F0芯片的SPI_DR寄存器的时候,发现一个问题: 我给DR寄存器赋值一个uint8_t一字节大小的数据,SPI引脚能正确输出数据和时钟,但前面八位正确的数据输 ...
- python 07 数据类型
目录 1. 基础数据类型填充 1.str:(不可变) 2. list: 3. tuple: 4. dict: 5. set: 6. bool: 7. 数据类型之间转换 2.删除列表/字典的代码坑: 3 ...
- 使用wait/notify/notifyAll实现线程间通信的几点重要说明
在Java中,可以通过配合调用Object对象的wait()方法和notify()方法或notifyAll()方法来实现线程间的通信.在线程中调用wait()方法,将阻塞等待其他线程的通知(其他线程调 ...
- Mybatis与SQL Server类型转换遇到的坑
一. MyBatis SQL语句遇到的性能问题 1. 场景还原 假设我们有一张User表,其中包含userId.userName.gender字段,其中userId的数据类型为char(20),此时我 ...
- JavaScript Array 数组方法汇总
JavaScript Array 数组方法汇总 1. arr.push() 从后面添加元素,返回值为添加完后的数组的长度 var arr = [1,2,3,4,5] console.log(arr.p ...