缓存(caching)可以存储经常会用到的信息,如果不使用缓存,每次查询数据时都会请求数据库,为了减小数据库的压力,可以将数据添加到缓存中,这样每次需要的时候,这些信息都是立即可用的。Spring自身并没有实现缓存的解决方案,但是它对缓存功能提供了声明式的支持,能够与多种流行的缓存实现集成。
本示例中需要的Jar包依赖(注意redis相关依赖的版本):
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.mfc.spring11</groupId>
<artifactId>SpringInAction11</artifactId>
<packaging>war</packaging>
<version>0.0.1-SNAPSHOT</version>
<name>SpringInAction11 Maven Webapp</name>
<url>http://maven.apache.org</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<spring.version>4.3.10.RELEASE</spring.version>
</properties>
<dependencies>
<!-- Java 缓存管理器依赖Start -->
<!-- https://mvnrepository.com/artifact/javax.cache/cache-api -->
<dependency>
<groupId>javax.cache</groupId>
<artifactId>cache-api</artifactId>
<version>1.1.0</version>
</dependency>
<!-- Java 缓存管理器依赖End -->
<!-- 使用EhCache缓存需要的依赖Start -->
<!-- https://mvnrepository.com/artifact/net.sf.ehcache/ehcache -->
<dependency>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache</artifactId>
<version>2.10.4</version>
</dependency>
<!-- 使用EhCache缓存需要的依赖End -->
<!-- 使用Redis缓存需要的依赖Start -->
<!-- https://mvnrepository.com/artifact/org.springframework.data/spring-data-redis -->
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-redis</artifactId>
<version>1.8.6.RELEASE</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.7.0</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.7.0</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>2.7.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/redis.clients/jedis -->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.9.0</version>
</dependency>
<!-- 使用Redis缓存需要的依赖End -->
<!-- https://mvnrepository.com/artifact/commons-dbcp/commons-dbcp -->
<dependency>
<groupId>commons-dbcp</groupId>
<artifactId>commons-dbcp</artifactId>
<version>1.4</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.oracle/ojdbc14 -->
<dependency>
<groupId>com.oracle</groupId>
<artifactId>ojdbc14</artifactId>
<version>10.2.0.1.0</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency>
<!-- springframework 4 dependencies begin -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>${spring.version}</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-aspects -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>${spring.version}</version>
</dependency>
<!-- spring data jpa 数据库持久层 -->
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-jpa</artifactId>
<version>1.10.4.RELEASE</version>
</dependency>
<!-- springframework 4 dependencies end -->
<!-- https://mvnrepository.com/artifact/org.hibernate/hibernate-validator -->
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>4.3.1.Final</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
<version>4.3.11.Final</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-entitymanager</artifactId>
<version>4.3.11.Final</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.hibernate.javax.persistence/hibernate-jpa-2.0-api -->
<dependency>
<groupId>org.hibernate.javax.persistence</groupId>
<artifactId>hibernate-jpa-2.0-api</artifactId>
<version>1.0.1.Final</version>
</dependency>
<dependency>
<groupId>javax.servlet.jsp.jstl</groupId>
<artifactId>jstl-api</artifactId>
<version>1.2</version>
<exclusions>
<exclusion>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
</exclusion>
<exclusion>
<groupId>javax.servlet.jsp</groupId>
<artifactId>jsp-api</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>taglibs</groupId>
<artifactId>standard</artifactId>
<version>1.1.2</version>
</dependency>
<!-- https://mvnrepository.com/artifact/commons-fileupload/commons-fileupload -->
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.3.1</version>
</dependency>
</dependencies>
<build>
<finalName>SpringInAction7</finalName>
</build>
</project>
一、启用对缓存的支持
Spring对缓存的支持有两种方式:
- 注解驱动的缓存
- XML声明的缓存
1、缓存管理器:
Spring3.1内置了五个缓存管理器的实现:
- SimpleCacheManager
- NoOpCacheManager
- ConcurrentMapCacheManager
- CompositeCacheManager
- EhcacheManager
Spring Data又提供了两个缓存管理器
- RedisCacheManager(来自Spring Data Redis项目)
- GemfireCacheManager(来自Spring Data Gemfire项目)
2、使用Ehcache缓存
①在Spring中注入EhCacheCacheManager:
XML方式注入:
<!-- 配置Ehcache缓存 -->
<!-- 启用注解缓存 -->
<cache:annotation-driven/>
<!-- 配置缓存管理器 -->
<bean id="cacheManager" class="org.springframework.cache.ehcache.EhCacheCacheManager">
<property name="cacheManager" ref="cacheManagerFactoryBean"></property>
</bean>
<bean id="cacheManagerFactoryBean" class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean">
<property name="configLocation" value="classpath:ehcache.xml"></property>
</bean>
Java配置方法注入:@EnableCaching启用注解驱动的缓存。
package com.mfc.config;
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;
import net.sf.ehcache.CacheManager;
@Configuration
@EnableCaching
public class CachingConfig {
@Bean
public EhCacheCacheManager cacheManager(CacheManager cm){
return new EhCacheCacheManager(cm);
}
@Bean
public EhCacheManagerFactoryBean ehcache(){
EhCacheManagerFactoryBean bean = new EhCacheManagerFactoryBean();
bean.setConfigLocation(new ClassPathResource("classpath:ehcache.xml"));
return bean;
}
}
②ehcache.xml文件:
主要看name=“tUserCache”的缓存配置,maxBytesLocalHeap=“50m”表示最大的堆储存是50M,timeToLiveSeconds="100000"表示缓存事件是100000秒。
关于Ehcache缓存的更多属性:http://www.ehcache.org/documentation/configuration
<ehcache>
<defaultCache
maxElementsInMemory="10000"
eternal="false"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
overflowToDisk="false"
diskPersistent="false"
diskExpiryThreadIntervalSeconds="120"
/>
<cache name="tUserCache"
maxBytesLocalHeap="50m"
timeToLiveSeconds="100000">
</cache>
</ehcache>
3、使用Redis缓存
注意开启Redis服务,Redis教程:https://blog.csdn.net/fancheng614/article/details/85929956
这里直接使用Java配置。
package com.mfc.config;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
@Configuration
@EnableCaching
public class CachingConfig {
@Bean
public CacheManager cacheManager(RedisTemplate redisTemplate){
return new RedisCacheManager(redisTemplate);
}
@Bean
public JedisConnectionFactory jedisConnectionFactory(){
JedisConnectionFactory connectionFactory = new JedisConnectionFactory();
connectionFactory.afterPropertiesSet();
return connectionFactory;
}
@Bean
public RedisTemplate<String, String> redisTemplate(JedisConnectionFactory connectionFactory){
RedisTemplate<String, String> redisTemplate = new RedisTemplate<String, String>();
redisTemplate.setConnectionFactory(connectionFactory);
redisTemplate.afterPropertiesSet();
return redisTemplate;
}
}
4、使用多个缓存管理器
@Bean
public CacheManager cacheManager1(net.sf.ehcache.CacheManager cm, javax.cache.CacheManager jcm){
CompositeCacheManager cacheManager = new CompositeCacheManager();
List<CacheManager> cacheManagers = new ArrayList<CacheManager>();
cacheManagers.add(new JCacheCacheManager(jcm));
cacheManagers.add(new EhCacheCacheManager(cm));
cacheManager.setCacheManagers(cacheManagers);
return cacheManager;
}
二、为方法添加注解以支持缓存
Spring的缓存抽象在很大程度上是围绕切面构建的。在Spring中启用缓存时,会创建一个切面,它触发一个或更多的Spring的缓存注解。并且所有的注解都能运用在方法或类上。当注解运用在方法上时,只对该方法起作用;注解运用在类上面时,缓存的行为会运用到这个类的所有方法上。
注解 | 描述 |
@Cacheable | 表明Spring在调用方法之前,首先应该在缓存中查找方法的返回值。如果这个值能够找到,就会返回缓存的值。否则这个方法就会被调用,返回值会放到缓存之中。 |
@CachePut | 表明Spring应该将方法的返回值放到缓存中。在方法调用前并不会检查缓存,方法始终都会被调用。 |
@CacheEvict | 表明Spring应该在缓存中清除一个或多个条目 |
@Caching | 这是一个分组的注解,能够同时应用多个其他的缓存注解 |
1、填充缓存
@Cacheable和@CachePut注解都可以填充缓存。它们有一些共有的属性:
属性 | 类型 | 描述 |
value | String[] | 要使用的缓存名称 |
condition | String | SpEL表达式,如果得到的值时false,不会将缓存应用到方法的调用上 |
key | String | SpEL表达式,用来计算自定义的缓存key |
unless | String | SpEL表达式,如果得到的值时true,返回值不会放到缓存中 |
使用@Cacheable示例:
@Cacheable("tUserCache")
public Tuser findByPrimary(String userId) {
return (Tuser) getSession().get(Tuser.class, userId);
}
当然注解也可以放在接口上,这样的话,所有这个接口的实现方法都会使用这个缓存:
@Cacheable("tUserCache")
public Tuser findByPrimary(String userId);
①将值放到缓存之中
@Cacheable注解每次调用这个方法时都会先查询缓存,但是如果我现在要调用add()方法存储一个对象,存进去之后就需要立即将这个对象放进缓存中(否则在缓存有效时间内在查询所有的user,是查不到新添加的这个user的)。所以现在调用add()方法的需求就是:改方法调用之前不比检查缓存,该方法始终都会被调用,调用之后将改方法的返回值放进缓存中。这时候就要用@CachePut注解了:
@CachePut("tUserCache")
public Tuser add(Tuser tuser);
②自定义缓存key
在① 中有一个问题:默认的缓存key要基于方法的参数来确定,而add(Tuser tuser)的唯一参数是tuser对象,所以这个对象会用作缓存的key。这样就是将tuser对象放进缓存中,而它的缓存key就是同一个tuser对象。这样就感觉怪怪的了。
解决:@Cacheable和@CachePut都有一个名为key的属性,这个属性能替换默认的key,它是通过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) |
示例:
@CachePut(value = "tUserCache", key = "#result.userId")
public Tuser add(Tuser tuser);
③条件化缓存
在有些场景下希望缓存关闭这时需要使用@Cacheable和@CachePut注解的unless和condition属性。
unless属性的值为true时只能阻止将对象放进缓存,但是在这个方法调用的时候,依然会去缓存中查找,如果找到了匹配的值,返回找到的值。
condition的表达式计算结果为false时,在这个方法调用的过程中,缓存是被禁用的。
示例一:对于message属性包含“NoCache”的Tuser对象,不对其进行缓存:
@Cacheable(value = "tUserCache", unless = "#result.message.contains('NoCache')")
public Tuser findByPrimary(String userId);
示例二:对userId等于10的Tuser关闭缓存
@Cacheable(value = "tUserCache", unless = "#result.message.contains('NoCache')", condition = "#userId == '10'")
public Tuser findByPrimary(String userId);
④移除缓存条目
如果带有@CacheEvict注解的方法被执行。就会有一个或更多的条目会在缓存中移除。
@CacheEvict("tUserCache")
public void remove();
注意:@Cacheable和@CachePut必须在非void返回值的方法上才能用,@CacheEvict可以在返回值为void的方法上使用。
属性 | 类型 | 描述 |
value | String[] | 要使用的缓存名称 |
key | String | SpEL表达式,用来计算自定义的缓存key |
condition | String | SpEL表达式,如果得到的值时false,缓存不会应用到方法调用上 |
allEntries | boolean | 如果为true,特定缓存的所有条目都会被移除掉 |
beforeInvocation | boolean | 如果为true,在方法调用之前移除条目。为false(默认值),在方法成功调用之后移除条目 |
三、使用XML声明缓存
使用XML声明缓存的原因:
- 觉得在自己的源码中添加Spring的注解不太舒服
- 需要在没有源码的bean上应用缓存功能
Spring的cache命名空间提供了使用XML声明缓存规则的方法,要结合aop命名空间使用。
元素 | 描述 | |
<cache:annotation-driven/> | 启用注解驱动缓存,等同于Java配置中的@EnableCaching | |
<cache:advice> | 定义缓存通知(advice)。结合<aop:advisor>,将通知应用到切点上 | |
<cache:caching> | 在缓存通知中,定义一组特定的缓存规则 | |
<cache:cacheable/> | 指明某个方法进行缓存,等同于@Cacheable注解 | |
<cache:cache-put/> | 指明某个方法要填充缓存,但不会考虑缓存中是否已有匹配的值,等同于@CachePut注解 | |
<cache:cache-evict/> | 指明某个方法要从缓存中移除一个或多个条目,等同于@CacheEvict注解 |
1、使用XML声明缓存示例:
<?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"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache-4.3.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd">
<!-- 将缓存通知绑定到一个切点上 -->
<aop:config>
<aop:advisor advice-ref="cacheAdvice" pointcut="execution(* com.mfc.dao.TuserDao.*(..))"/>
</aop:config>
<cache:advice id="cacheAdvice" cache-manager="cacheManager">
<cache:caching>
<!-- 配置为支持缓存 -->
<cache:cacheable cache="tUserCache" method="find"/>
<cache:cacheable cache="tUserCache" method="findByPrimary"/>
<cache:cache-put cache="tUserCache" method="add" key="#result.userId"/>
<cache:cache-evict cache="tUserCache" method="remove"/>
</cache:caching>
</cache:advice>
<bean id="cacheManager" class="org.springframework.cache.concurrent.ConcurrentMapCacheManager"></bean>
</beans>
2、注意上面的<cache:cacheable/>、<cache:cache-put/>、<cache:cache-evict/>元素都引用了同一个名为tUserCache的缓存,为了消除这种重复,可以在<cache:caching>元素上指明缓存的名字。
<?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"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache-4.3.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd">
<!-- 将缓存通知绑定到一个切点上 -->
<aop:config>
<aop:advisor advice-ref="cacheAdvice" pointcut="execution(* com.mfc.dao.TuserDao.*(..))"/>
</aop:config>
<cache:advice id="cacheAdvice" cache-manager="cacheManager">
<cache:caching cache="tUserCache">
<!-- 配置为支持缓存 -->
<cache:cacheable method="find"/>
<cache:cacheable method="findByPrimary"/>
<cache:cache-put method="add" key="#result.userId"/>
<cache:cache-evict method="remove"/>
</cache:caching>
</cache:advice>
<bean id="cacheManager" class="org.springframework.cache.concurrent.ConcurrentMapCacheManager"></bean>
</beans>
<cache:caching>元素还有几个可以供<cache:cacheable/>、<cache:cache-put/>、<cache:cache-evict/>共享的属性:
①cache:指明要存储和获取值的缓存
②condition:spEL表达式,如果计算得到的值为false,将会为这个方法禁用缓存
③key:spEL表达式,用来得到缓存的key(默认为方法的参数)
④method:要缓存的方法名
除此之外,<cache:cacheable/>、<cache:cache-put/>还有一个unless属性,这个属性是一个spEL表达式,如果计算的值时true,就会阻止将返回值放到缓存中。
<cache:cache-evict/>特性:
①all-entries:如果是true,缓存中所有的条目都会被移除掉。如果是false,只有匹配key的条目才会被移除掉。
②before-invocation:如果为true,缓存条目将会在方法调用之前被移除掉,如果为false,缓存条目在方法调用之后移除掉。
③all-entries和before-invocation的默认值都是false。