SSM框架整合笔记(六)缓存配置Ehcache

前言

   缓存(Caching)可以存储经常会用到的信息,在需要的时候,直接返回这些信息。Spring对缓存的支持有两种方式:1)注解驱动的缓存;2)XML声明的缓存;本文使用第二种方式来实际进行配置。

本文内容

  • 缓存内容详解
  • 配置Ehcache缓存
  • 配置Mybatis Generator

缓存内容详解

   使用@EnableCachingcache:annotation-driven/ 的工作方式是相同的,都会创建一个切面并触发Spring缓存注解的切点。根据所使用的注解以及缓存的状态,这个切面会从缓存中获取数据,将数据添加到缓存之中或者从缓存中移除某个值。在启用注解驱动的缓存时,还声明了一个缓存管理器的bean。缓存管理器是Spring缓存抽象的核心,能够与多个流行的缓存实现进行集成。

如何启用缓存

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* @Description 使用@EnableCaching启用注解驱动的缓存
* @Author Mr.wang
* @Date 2019/3/9
*/
@Configuration
@EnableCaching // 启用缓存
public class CachingConfig {

@Bean
public CacheManager cacheManager() { // 声明缓存管理器
return new ConcurrentMapCacheManager();
}
}

XML方式配置方式如下

1
2
3
4
5
<!--Ehcache配置-->
<!-- 启用缓存 -->
<cache:annotation-driven />
<!-- 声明缓存管理器 -->
<bean id="cacheManager" class="org.springframework.cache.concurrent.ConcurrentMapCacheManager" />

ConcurrentMapCacheManager 使用 java.util.concurrent.ConcurrentHashMap 作为缓存存储,这个缓存存储是基于内存的,因此其生命周期是与应用关联的,不太适合于生产级别的大型企业级应用程序,因此有其他的缓存管理器方案可供使用。

配置缓存管理器

Spring中内置了如下几种缓存管理器实现:

  1. SimpleCacheManager
  2. NoOpCacheManager
  3. ConcurrentMapCacheManager
  4. CompositeCacheManager
  5. EhCacheCacheManager

SpringData提供的缓存管理器:

  1. RedisCacheManager(来自于Spring Data Redis项目)
  2. GemfireCacheManager(来自于Spring Data GemFire项目)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import net.sf.ehcache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.ehcache.EhCacheCacheManager;
import org.springframework.cache.ehcache.EhCacheManagerFactoryBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;

/**
* @Description 使用@EnableCaching启用注解驱动的缓存
* @Author Mr.wang
* @Date 2019/3/9
*/
@Configuration
@EnableCaching // 启用缓存
public class CachingConfig {

@Bean
public EhCacheCacheManager cacheManager(CacheManager cm) { // 配置EhCacheCacheManager
return new EhCacheCacheManager(cm);
}

@Bean
public EhCacheManagerFactoryBean ehcache() {
EhCacheManagerFactoryBean ehCacheFactoryBean = new EhCacheManagerFactoryBean();
ehCacheFactoryBean.setConfigLocation(new ClassPathResource(""));
return ehCacheFactoryBean;
}
}

也可以通过CompositeCacheManager使用多个缓存管理器。

一、为方法添加注解支持缓存

以下注解如果放在单独的方法上时,注解所描述的缓存行为只作用于这个方法上,如果放在类级别,那么缓存行为就会应用到这个类的所有方法上。

注解 描述
@Cacheable Spring在调用方法之前,首先应该在缓存中查找方法的返回值。如果这个值能够找到,就会返回缓存的值。否则,这个方法就会被调用,返回值就会放到缓存之中
@CachePut Spring应该将方法的返回值放到缓存中,在方法的调用前并不会检查缓存,方法始终会被调用
@CacheEvict Spring应该在缓存中清除一个或多个条目
@Caching 一个分组的注解,能够同时应用多个其他的缓存注解

填充缓存

@Cacheable和@CachePut注解都可以填充缓存。

示例:

1
2
3
4
@Cacheable(value="spittleCache")
Spittle findOne(long id){
// ...
}

自定义缓存key

默认的缓存key要基于方法的参数来确定,参数是一个map,那么其缓存的key也是这个map,因此需要把默认的key改为map的key,而不是这个map,所以需要自定义缓存key。

Spring 提供了多个用来定义缓存规则的SpEL扩展

表达式 描述
#root.args 传递给缓存方法的参数,形式为数组
#root.caches 该方法执行时所对应的缓存,形式为数组
#root.target 目标对象
#root.targetClass 目标对象的类,是#root.target.class的简写形式
#root.method 缓存方法
#root.methodName 缓存方法的名字,是#root.method.name的简写形式
#result 方法调用的返回值(不能用在@Cacheable主街上)
#Argument 任意的方法参数名(如#argName)或参数索引(如#a0或#p0)
1
2
@Cacheable(value="spittleCache" key="#result.id")
Spittle save(Spittle spittle);

条件化缓存

前面说的是通过为方法添加Spring的缓存注解,Spring就会围绕着这个方法创建一个缓存切面。条件化缓存是说将缓存功能关闭的一些场景。@Cacheable和@CachePut提供两个属性用以实现条件化缓存:unless和condition。如果unless属性的SpEL表达式计算结果为true,缓存方法返回的数据不会放到缓存中。如果condition属性的SpEL表达式计算结果为false,对于这个方法缓存就会被禁用掉。

unless属性只能阻止将对象放进缓存,但是在这个方法调用的时候,依然会去缓存中进行查找。如果condition的表达式计算结果为false,这个方法调用时,缓存是被禁用的,也就不会去缓存中查找,返回值也不会放进缓存中。

例:

1
2
3
4
5
6
7
8
9
10

// unless用法,对于属性中包含NoCache的Spittle对象,不对其进行缓存
@Cacheable(value="spittleCache" unless="#result.message.contains('NoCache')")
Spittle findOne(long id);

// condition用法,如果findOne方法调用时,参数值小于10,不会在缓存中进行查找,返回的数据也不会放进缓存中
@Cacheable(value="spittleCache"
unless="#result.message.contains('NoCache')"
condition="#id >= 10")
Spittle findOne(long id);

移除缓存条目

@CacheEvict不会往缓存中添加任何东西,且如果带有该注解的方法被调用的话,缓存中的一个或更多的条目会被移除。

1
2
@CacheEvict("spittleCache")
void remove(long spittleId);

注意:@Cacheable和@CacheEvict必须应用在返回值非void的方法上,@CacheEvict可以放在任意的方法上,甚至void方法

@Cacheable注解的属性,指定哪些缓存条目应该被移除掉

属性 类型 描述
value String[] 要使用的缓存名称
key String SpEL表达式,用来计算自定义的缓存key
condition String SpEL表达式,如果得到的值是false的话,缓存不会应用到方法调用上
allEntries boolean 如果为true的话,特定缓存的所有条目都会被移除掉
beforeInvoication boolean 如果为true的话,在方法调用之前移除条目,如果为false(默认值)的话,在方法成功调用之后再移除条目

二、使用XML声明缓存

元素 描述
<cache:annotation-driven> 启用注解驱动的缓存,等同于Java配置中的@EnableCaching
<cache:advice> 定义缓存通知。结合<aop:advisor>,将通知应用到切点上
<cache:caching> 在缓存通知中定义缓存规则
<cache:cacheable> 指明某个方法要进行缓存。等同于@Cacheable注解
<cache:cache-put> 指明某个方法要填充缓存,但不会考虑缓存中时候已有匹配的值,等同于@CachePut注解
<cache:cache-evict> 指明某个方法要从缓存中移除一项或多项,等同于@CacheEvict注解

填充缓存

1
2
3
<cache:cacheable>
<!--示例-->
<cache:cacheable cache="spittleCache" method="findRecent" />

自定义缓存key

1
<cache:cache-put cache="spittleCache" method="save" key="#result.id"/>

移除缓存条目

1
<cache:cache-evict cache="spittleCache" method="remove" />

配置Ehcache缓存

Ehcache的主要特性有:

  1. 快速、精干,简单;
  2. 多种缓存策略;
  3. 缓存数据有两级:内存和磁盘,因此无需担心容量问题;
  4. 缓存数据会在虚拟机重启的过程中写入磁盘;
  5. 可以通过RMI、可插入API等方式进行分布式缓存;
  6. 具有缓存和缓存管理器的侦听借口;
  7. 支持多缓存管理器实例,以及一个实例的多个缓存区域;
  8. 提供Hibernate 的缓存实现;

上面的声明应该改为以下方式

pom.xml添加配置

1
2
3
4
5
6
7
8
9
10
11
<!-- Ehcache -->
<dependency>
<groupId>org.ehcache</groupId>
<artifactId>ehcache</artifactId>
<version>3.6.3</version>
</dependency>
<dependency>
<groupId>org.mybatis.caches</groupId>
<artifactId>mybatis-ehcache</artifactId>
<version>1.3.5</version>
</dependency>

添加spring-ehcache.xml并配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:cache="http://www.springframework.org/schema/cache"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/cache
http://www.springframework.org/schema/cache/spring-cache.xsd">

<description>Ehcache缓存配置文件</description>

<!--Ehcache配置-->
<!-- 启用缓存 -->
<cache:annotation-driven cache-manager="cacheManager"/>
<!-- 声明缓存管理器 -->
<bean id="ehcache" class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean">
<property name="configLocation" value="classpath:config/ehcache.xml"/>
<!-- true:一个cacheManager对象共享,false:多个对象独立 -->
<property name="shared" value="true"/> <!-- 这里是关键!!!没有必错 -->
</bean>

<bean id="cacheManager" class="org.springframework.cache.ehcache.EhCacheCacheManager">
<property name="cacheManager" ref="ehcache"/>
</bean>
</beans>

spring-config.xml中添加配置

1
2
<!-- 加载Ehcache缓存配置文件 -->
<import resource="classpath:config/spring-ehcache.xml"/>

在resources/config文件夹下添加ehcache.xml并添加配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
<?xml version="1.0" encoding="UTF-8"?>
<!-- 这个name和updateCheck 都按这样配置最好,不然启动时会在前台报错403,IOException-->
<ehcache name="ehcache" updateCheck="false">
<!-- 缓存位置 java.io.tmpdir:Java临时目录 -->
<diskStore path="java.io.tmpdir/ehcache"/>

<!-- 默认缓存 -->
<defaultCache
maxEntriesLocalHeap="10000"
eternal="false"
overflowToDisk="true"
timeToIdleSeconds="100"
timeToLiveSeconds="120"
diskPersistent="false"
maxEntriesLocalDisk="10000000"
diskExpiryThreadIntervalSeconds="120"
memoryStoreEvictionPolicy="LRU">
</defaultCache>

<!--name:Cache的唯一标识
maxElementsInMemory:内存中最大缓存对象数。
maxElementsOnDisk:磁盘中最大缓存对象数,若是0表示无穷大。
eternal:Element是否永久有效,一但设置了,timeout将不起作用。
overflowToDisk:配置此属性,当内存中Element数量达到maxElementsInMemory时,Ehcache将会Element写到磁盘中。
timeToIdleSeconds:设置Element在失效前的允许闲置时间。仅当element不是永久有效时使用,可选属性,默认值是0,也就是可闲置时间无穷大。
timeToLiveSeconds:设置Element在失效前允许存活时间。最大时间介于创建时间和失效时间之间。仅当element不是永久有效时使用,默认是0.,也就是element存活时间无穷大。
diskPersistent:是否缓存虚拟机重启期数据。(Server重启时将缓存序列化到本地,后再加载,保证缓存在重启后依然有效)。
diskExpiryThreadIntervalSeconds:磁盘失效线程运行时间间隔,默认是120秒。
diskSpoolBufferSizeMB:这个参数设置DiskStore(磁盘缓存)的缓存区大小。默认是30MB。每个Cache都应该有自己的一个缓冲区。
memoryStoreEvictionPolicy:当达到maxElementsInMemory限制时,Ehcache将会根据指定的策略去清理内存。默认策略是LRU(最近最少使用)。你可以设置为FIFO(先进先出)或是LFU(较少使用)。这里比较遗憾,Ehcache并没有提供一个用户定制策略的接口,仅仅支持三种指定策略,感觉做的不够理想。-->

<!-- 系统缓存,用来缓存菜单、字典等 -->
<cache name="systemCatch"
maxElementsInMemory="1000"
eternal="false"
timeToIdleSeconds="10"
timeToLiveSeconds="30"
overflowToDisk="true"
diskPersistent="false"
memoryStoreEvictionPolicy="LRU"/>
</ehcache>

这三个文件配置完了之后,Ehcache的配置也就完成了,下面我们说一下测试的方法。

测试缓存配置

SysMenuServiceImpl.java 中添加如下代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Service
public class SysMenuServiceImpl extends BaseService<SysMenu> implements SysMenuService {

private static final Logger logger = Logger.getLogger(SysMenuServiceImpl.class);

@Autowired
SysMenuDao sysMenuDao;
// value对应ehcache.xml中的cache name,key就是这个缓存条目的key
@Cacheable(value="systemCatch", key="'SysMenuServiceImpl.systemCatch'")
@Override
public List<SysMenu> findMenuList(SysMenu sysMenu) {
List<SysMenu> sysMenus = sysMenuDao.queryMenuList(sysMenu);
Long timestamp = System.currentTimeMillis();
logger.error("timestamp>>>>>>>>>>>>>>>>>>>>>>>>>" + timestamp);
return sysMenus;
}
}

SysMenuController.java 中添加如下代码,观察start 和 end方法之间会不会打印sql以及中间打印时间的日志。

1
2
3
4
5
6
7
8
9
10
@RequestMapping(value = "sysMenuList")
@ResponseBody
public List<SysMenu> getSysMenuList() {
SysMenu sysMenu = new SysMenu();
sysMenu.setMenuId("01");
logger.info("-----------------------------------------------findMenuList start-----------------------------------");
List<SysMenu> menuList = sysMenuService.findMenuList(sysMenu);
logger.info("-----------------------------------------------findMenuList end-----------------------------------");
return menuList;
}

具体效果如下:

ehcache-1-20193103302

问题处理

问题一

1
[ContainerBackgroundProcessor[StandardEngine[Catalina]]] org.apache.catalina.webresources.Cache.backgroundProcess The background cache eviction process was unable to free [10] percent of the cache for Context [/lx_note] - consider increasing the maximum size of the cache. After eviction approximately [9,686] KB of data remained in the cache.

并没有报错,只是Console打印出来信息,提示不能自动释放10%的缓存,需要调大cache的最大容量;

解决方法:
Tomcat8 的conf/目录下的context.xml添加配置,默认大小是10240即10M,这边给调大了10倍。

1
<Resources cachingAllowed="true" cacheMaxSize="102400" />

相关文章推荐

源码下载

   该项目持续更新中,会在代码以及该文档里面详细注释和介绍。项目托管在码云开源平台上,持续更新项目源码链接:
https://gitee.com/nelucifer/ssm-note,点击克隆/下载获取该项目。