spring cache 总结
作用: 保证数据时效性的前提下, 提高查询效率; 减少查询数据库的次数, 减轻数据库压力;
原理: 基于Proxy/AspectJ动态代理技术的AOP思想(面向切面编程), 在不改变目标方法的前提下在其切面进行缓存的增删改查(在服务方法执行前查询缓存, 在服务方法执行后添加缓存, 或者清除缓存);
用法:
1. spring cache实现有基于XML/注解实现AOP;
2. CacheManager负责对缓存的增删改查, CacheManager的缓存的介质可配置, 如: ConcurrentMap/EhCache/Redis等;
spring cache 实例
1. 导入jar包
<!-- redis架包 -->
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-redis</artifactId>
<version>1.6.2.RELEASE</version>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.9.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-core -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.9.2</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.2</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-annotations -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>2.9.2</version>
</dependency>
<!-- https://mvnrepository.com/artifact/commons-logging/commons-logging -->
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>
<!-- https://mvnrepository.com/artifact/log4j/log4j -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
2. 配置spring-cache.xml
<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:p="http://www.springframework.org/schema/p"
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">
<!-- 开启缓存注解: 使用 @Cacheable, @CachePut, @CacheEvict, @Caching, @CacheConfig 等注解引入缓存 -->
<cache:annotation-driven/>
<!-- generic cache manager -->
<bean id="cacheManager" class="org.springframework.cache.support.SimpleCacheManager">
<property name="caches">
<set>
<bean class="org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean" p:name="default"/>
<bean class="org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean" p:name="accountCache"/>
</set>
</property>
</bean>
<!-- 注入实体类 -->
<bean id="accountServiceBean" class="com.csdn.learn.springcache.AccountService"/>
</beans>
3. 使用及测试
package com.csdn.learn.springcache;
import com.csdn.learn.annocache.Account;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
/**
* 使用@Cacheable, @CachePut, @CacheEvict, @CacheConfig spring通过AOP对方法进行缓存管理
* @author liudong
* @date 2018-04-27
*/
public class AccountService {
/**
* 使用了一个缓存名叫 accountCache
* key condition ,前面的 # 号代表这是一个 SpEL 表达式
* @param userName
* @return
*/
@Cacheable(value = "accountCache", key = "#userName", condition = "#userName.length() <= 4")
public Account getAccountByName(String userName) {
// 方法内部实现不考虑缓存逻辑,直接实现业务
System.out.println("real querying db..." + acctName + "+" + password);
return new Account(acctName);
}
/**
* 更新 accountCache 缓存
* @param account dd
*/
@CachePut(value = "accountCache", key = "#account.getName()")
public void updateAccount(Account account) {
System.out.println("real update db..." + account.getName());
}
/**
* reload 清空 accountCache 缓存
*/
@CacheEvict(value = "accountCache", allEntries = true)
public void reload() {
}
}
package com.csdn.learn.annocache;
import java.io.Serializable;
/**
* 首先定义一个实体类:账号类,具备基本的 id 和 name 属性,且具备 getter 和 setter 方法
* @author liudong
* @date 2018-04-27
*/
public class Account implements Serializable {
private static final long serialVersionUID = 1L;
private int id;
private String name;
private String password;
public Account() {
super();
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public Account(String name) {
this.name = name;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Account{" +
"id=" + id +
", name='" + name + '\'' +
", password='" + password + '\'' +
'}';
}
}
package com.csdn.learn.rediscacheanno;
import com.csdn.learn.annocache.Account;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.data.redis.core.RedisTemplate;
public class Main {
public static void main(String[] args) {
// 加载 spring 配置文件
ApplicationContext context = new ClassPathXmlApplicationContext("spring-cache.xml");
AccountService s = (AccountService) context.getBean("accountServiceBean");
// 第一次查询,应该走数据库
System.out.print("first query...");
s.getAccountByName("somebody");
// 第二次查询,应该不查数据库,直接返回缓存的值
System.out.print("second query...");
s.getAccountByName("somebody");
System.out.println();
// TODO 自行测试@CachePut及@CacheEvict
// 测试redisTemplate
RedisTemplate redisTemplate = (RedisTemplate) context.getBean("redisTemplate");
// redis中存取字符串
redisTemplate.opsForValue().set("redis2", "ceshiRedis222");
System.out.println(redisTemplate.opsForValue().get("redis2"));
// redis中存取实体类
redisTemplate.opsForValue().set("account", new Account("ceshi 123"));
System.out.println(redisTemplate.opsForValue().get("account"));
}
}
内存式普通缓存使用大功告成, 由于spring整合Redis的做缓存时配置最难搞定, 所以仅介绍配置
方式一. spring整合Redis的xml配置
#============================#
#==== Redis settings ====#
#============================#
#redis 服务器 IP
redis.host=127.0.0.1
#redis 服务器端口
redis.port=6379
#redis 密码
redis.pass=
#redis 支持16个数据库(相当于不同用户)可以使不同的应用程序数据彼此分开同时又存储在相同的实例上
redis.dbIndex=1
#redis 缓存数据过期时间单位秒
redis.expiration=3000
#控制一个 pool 最多有多少个状态为 idle 的jedis实例
redis.maxIdle=300
#控制一个 pool 可分配多少个jedis实例
redis.maxActive=600
#当borrow一个jedis实例时,最大的等待时间,如果超过等待时间,则直接抛出JedisConnectionException;
redis.maxWait=1000
#在borrow一个jedis实例时,是否提前进行alidate操作;如果为true,则得到的jedis实例均是可用的;
redis.testOnBorrow=true
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:cache="http://www.springframework.org/schema/cache"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/cache
http://www.springframework.org/schema/cache/spring-cache.xsd">
<!-- **************************************************redis********************************************************** -->
<!-- 配置文件加载 -->
<context:property-placeholder location="classpath:*.properties"/>
<!-- 配置 JedisPoolConfig 实例 -->
<bean id="poolConfig" class="redis.clients.jedis.JedisPoolConfig">
<property name="maxIdle" value="${redis.maxIdle}"/>
<property name="maxTotal" value="${redis.maxActive}"/>
<property name="maxWaitMillis" value="${redis.maxWait}"/>
<property name="testOnBorrow" value="${redis.testOnBorrow}"/>
</bean>
<!-- 配置JedisConnectionFactory -->
<bean id="jedisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
<property name="hostName" value="${redis.host}"/>
<property name="port" value="${redis.port}"/>
<property name="password" value="${redis.pass}"/>
<property name="database" value="${redis.dbIndex}"/>
<property name="poolConfig" ref="poolConfig"/>
</bean>
<!-- 配置RedisTemplate -->
<!-- redis 序列化策略 ,通常情况下key值采用String序列化策略, -->
<!-- 如果不指定序列化策略,StringRedisTemplate的key和value都将采用String序列化策略; -->
<!-- 但是RedisTemplate的key和value都将采用JDK序列化 这样就会出现采用不同template保存的数据不能用同一个template删除的问题 -->
<bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate"
p:connectionFactory-ref="jedisConnectionFactory"
p:keySerializer-ref="stringRedisSerializer"
p:hashKeySerializer-ref="stringRedisSerializer"
p:valueSerializer-ref="genericJackson2JsonRedisSerializer"
p:hashValueSerializer-ref="genericJackson2JsonRedisSerializer"
/>
<!--JDK序列化,速度快但占用空间较大,对象必须实现java.io.Serializable接口 -->
<bean id="jdkSerializationRedisSerializer" class="org.springframework.data.redis.serializer.JdkSerializationRedisSerializer" />
<bean id="stringRedisSerializer" class="org.springframework.data.redis.serializer.StringRedisSerializer" />
<!-- JSON序列化,空间占用小,无需指明但对象类型
SerializationException:
Could not read JSON: Cannot construct instance of `com.learn.cache.model.Account` (although at least one Creator exists):
cannot deserialize from Object value (no delegate- or property-based Creator)
解决办法: 给实体类com.learn.cache.model.Account添加默认的构造方法 public Account(){}
-->
<bean id="genericJackson2JsonRedisSerializer" class="org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer" />
<!-- JSON序列化,空间占用小,需指明对象类型,不知道怎么在xml配置中注入参数类型为Class<?>的值,可在代码中指定 -->
<!-- <bean id="jackson2JsonRedisSerializer" class="org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer"/> -->
<!-- 配置RedisCacheManager -->
<bean id="redisCacheManager" class="org.springframework.data.redis.cache.RedisCacheManager">
<constructor-arg name="redisOperations" ref="redisTemplate"/>
<property name="defaultExpiration" value="${redis.expiration}"/>
</bean>
<!-- 开启缓存注解: 使用 @Cacheable, @CachePut, @CacheEvict, @Caching, @CacheConfig 等注解引入缓存 -->
<cache:annotation-driven/>
<!-- 开启注解扫描 -->
<!--<context:annotation-config />-->
<context:component-scan base-package="com.learn"/>
<bean id="testServiceBean" class="com.learn.cache.service.TestService"/>
<bean id="accountServiceBean" class="com.learn.cache.service.AccountService"/>
</beans>
方式二. spring整合Redis的纯注解配置
<?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:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.2.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.2.xsd">
<!-- 配置文件加载 -->
<context:property-placeholder location="classpath:*.properties"/>
<!-- redis连接池 -->
<bean id="poolConfig" class="redis.clients.jedis.JedisPoolConfig">
<property name="maxIdle" value="${redis.maxIdle}"/>
<property name="maxWaitMillis" value="${redis.maxWait}"/>
<property name="testOnBorrow" value="${redis.testOnBorrow}"/>
</bean>
<!-- 连接工厂 -->
<bean id="redisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory"
p:host-name="${redis.host}" p:port="${redis.port}" p:password="${redis.pass}" p:database="2" p:pool-config-ref="poolConfig"/>
<!-- 开启注解扫描 -->
<!--<context:annotation-config />-->
<context:component-scan base-package="com.csdn.learn" />
<bean id="accountServiceBean" class="com.csdn.learn.rediscacheanno.AccountService"/>
</beans>
package com.csdn.learn.rediscacheanno;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurerSupport;
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.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
@Configuration
@EnableCaching
public class RedisConfig extends CachingConfigurerSupport {
@Bean
public CacheManager cacheManager(RedisTemplate redisTemplate) {
RedisCacheManager cacheManager = new RedisCacheManager(redisTemplate);
// cacheManager.setDefaultExpiration(3600);//默认缓存一小时
return cacheManager;
}
/**
* redisTemplate 序列化使用的jdkSerializeable, 存储二进制字节码, 所以自定义序列化类
* @param redisConnectionFactory
* @return
*/
@Bean
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(redisConnectionFactory);
// 使用Jackson2JsonRedisSerialize 替换默认序列化
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(objectMapper);
// 设置value的序列化规则和 key的序列化规则
redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.afterPropertiesSet();
return redisTemplate;
}
}
最后, SpringBoot整合Redis做缓存Cache技术配置非常简单, 可以试着玩一玩