Spring缓存使用
缓存注解
对于Spring,缓存组件例如EhCache是可拔插的,而缓存注解是通用的。
@Cacheable
标记在方法或者类上,标识该方法或类支持缓存。Spring调用注解标识方法后会将返回值缓存到redis,以保证下次同条件调用该方法时直接从缓存中获取返回值。这样就不需要再重新执行该方法的业务处理过程,提高效率。
@Cacheable常用的三个参数如下:
cacheNames 缓存名称
key 缓存的key,需要注意key的写法哈
condition 缓存执行的条件,返回true时候执行
cacheManager 使用的cacheManager的bean名称
sync 如果多个请求同时来访问同一个key的数据,则sync表示加锁同步,等第一个请求返回数据后,其他请求直接获取缓存里的数据。
java
@Cacheable(value = "DICT", key = "'getDictById'+#id", sync = true)
@CachePut
标记在方法或者类上,标识该方法或类支持缓存。每次都会执行目标方法,并将执行结果以键值对的形式存入指定的缓存中。
java
@CachePut(value = "DICT", key = "'getDictById'+#model.getId()")
@CacheEvict
@CacheEvict是用来标注在需要清除缓存元素的方法或类上的。@CacheEvict可以指定的属性有value、key、condition、allEntries和beforeInvocation。其中value、key和condition的语义与@Cacheable对应的属性类似。即
value表示清除操作是发生在哪些Cache上的(对应Cache的名称);
key表示需要清除的是哪个key,如未指定则会使用默认策略生成的key;
condition表示清除操作发生的条件。
allEntries是boolean类型,表示是否需要清除缓存中的所有元素。默认为false,表示不需要。当指定了allEntries为true时,Spring Cache将忽略指定的key。
beforeInvocation 清除操作默认是在对应方法成功执行之后触发的,即方法如果因为抛出异常而未能成功返回时也不会触发清除操作。当我们指定该属性值为true时,Spring会在调用该方法之前清除缓存中的指定元素。
java
@CacheEvict(value = "DICT",key = "'getDictById'+#model.getId()",beforeInvocation=false)
使用Ehcache作为缓存
pom坐标
xml
<dependency>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache</artifactId>
<version>2.8.3</version>
</dependency>
<!-- Ehcache依赖此组件创建bean -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>5.1.5.RELEASE</version>
</dependency>
ehcache.xml 配置文件
xml
<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd"
updateCheck="false">
<diskStore path="java.io.tmpdir/Tmp_EhCache" />
<!-- defaultCache,是默认的缓存策略 -->
<!-- 如果你指定的缓存策略没有找到,那么就用这个默认的缓存策略 -->
<!-- external:缓存对象是否一直存在,如果设置为true的话,那么timeout就没有效果,缓存就会一直存在,一般默认就是false -->
<!-- maxElementsInMemory:内存中可以缓存多少个缓存条目 -->
<!-- overflowToDisk:如果内存不够的时候,是否溢出到磁盘 -->
<!-- diskPersistent:是否启用磁盘持久化的机制,在jvm崩溃的时候和重启之间 -->
<!-- timeToIdleSeconds:对象最大的闲置的时间,如果超出闲置的时间,可能就会过期 单位:秒 当eternal=false对象不是永久有效时使用,可选属性,默认值是0,也就是可闲置时间无穷大-->
<!-- timeToLiveSeconds:对象最多存活的时间 单位:秒 当eternal=false对象不是永久有效时使用,可选属性,默认值是0,也就是存活时间无穷大-->
<!-- memoryStoreEvictionPolicy:当缓存数量达到了最大的指定条目数的时候,需要采用一定的算法,从缓存中清除一批数据,LRU,最近最少使用算法,最近一段时间内,最少使用的那些数据,就被干掉了 -->
<defaultCache
eternal="false"
maxElementsInMemory="1000"
overflowToDisk="false"
diskPersistent="false"
timeToIdleSeconds="300"
timeToLiveSeconds="600"
memoryStoreEvictionPolicy="LRU" />
<!-- 手动指定的缓存策略 -->
<!-- 对不同的数据,缓存策略可以在这里配置多种 -->
<cache
name="local"
eternal="false"
maxElementsInMemory="1000"
overflowToDisk="false"
diskPersistent="false"
timeToIdleSeconds="300"
timeToLiveSeconds="600"
memoryStoreEvictionPolicy="LRU" />
</ehcache>
EhcacheConfiguration.java
java
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;
@Configuration
@EnableCaching
public class EhcacheConfiguration {
@Bean
public EhCacheManagerFactoryBean cacheManagerFactoryBean(){
EhCacheManagerFactoryBean bean = new EhCacheManagerFactoryBean();
bean.setConfigLocation(new ClassPathResource("ehcache.xml"));
bean.setShared(true);
return bean;
}
@Bean
public EhCacheCacheManager ehCacheCacheManager(EhCacheManagerFactoryBean bean){
return new EhCacheCacheManager(bean.getObject());
}
}
使用
java
@Service
public class EhcahceServiceImpl implements EhcahceService {
private static final String CACHE_STRATEGY = "local";
@CachePut(value=CACHE_STRATEGY,key="#root.methodName+#info.getProduct_id()")
@Override
public ProductInfo saveProductInfo(ProductInfo info) throws Exception {
return info;
}
@Cacheable(value=CACHE_STRATEGY,key="#root.methodName+#id")
@Override
public ProductInfo getProductInfoById(Long id) throws Exception {
return null;
}}
使用redis作为缓存
pom坐标
xml
<!--redis 配置-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<exclusions>
<exclusion>
<groupId>io.lettuce</groupId>
<artifactId>lettuce-core</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
yml配置
yml
spring:
# redis配置
redis:
password: 123456
# 集群模式
cluster:
nodes: 127.0.0.1:1000,127.0.0.1:2000
# 哨兵模式
#sentinel:
#master: sentinel #哨兵的名字 #下面是所有哨兵集群节点
#nodes: 11.11.11.111:26379,11.11.11.111:26380
jedis:
pool:
#最大连接数
max-active: 200
#最大阻塞等待时间(负数表示没限制)
max-wait: -1
#最大空闲
max-idle: 8
#最小空闲
min-idle: 0
#连接超时时间
expireSecond: 604800 #7天
redisCacheManage配置
此处设计了两种不同cacheName的redis缓存
java
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
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.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.cache.RedisCacheWriter;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import java.time.Duration;
import java.util.HashMap;
import java.util.Map;
@Configuration
@EnableCaching
@Slf4j
public class CacheConfig {
@Value("${spring.redis.expireSecond}")
private Integer expireSecond;
@Bean
public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
return new RedisCacheManager(
RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory),
this.getRedisCacheConfigurationWithTtl(600), // 默认策略,未配置的 key 会使用这个
this.getRedisCacheConfigurationMap() // 指定 key 策略
);
}
private Map<String, RedisCacheConfiguration> getRedisCacheConfigurationMap() {
Map<String, RedisCacheConfiguration> redisCacheConfigurationMap = new HashMap<>();
redisCacheConfigurationMap.put("Dict", this.getRedisCacheConfigurationWithTtl(600));
redisCacheConfigurationMap.put("COL_KEY", this.getRedisCacheConfigurationWithTtl(expireSecond));
return redisCacheConfigurationMap;
}
private RedisCacheConfiguration getRedisCacheConfigurationWithTtl(Integer seconds) {
RedisCacheConfiguration config = RedisCacheConfiguration
.defaultCacheConfig()
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()))
.entryTtl(Duration.ofSeconds(seconds));
return config;
}
}
使用同上
同时使用两种缓存
配置
java
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
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.context.annotation.Primary;
import org.springframework.core.io.ClassPathResource;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import java.time.Duration;
@Configuration
@EnableCaching
@Slf4j
public class CacheConfig {
@Value("${spring.redis.expireSecond}")
private Integer expireSecond;
@Bean
public EhCacheManagerFactoryBean cacheManagerFactoryBean() {
EhCacheManagerFactoryBean bean = new EhCacheManagerFactoryBean();
bean.setConfigLocation(new ClassPathResource("ehcache.xml"));
bean.setShared(true);
return bean;
}
@Bean
@Primary
public EhCacheCacheManager ehCacheCacheManager(EhCacheManagerFactoryBean bean) {
return new EhCacheCacheManager(bean.getObject());
}
@Bean
public RedisCacheManager redisCacheManager(RedisConnectionFactory redisConnectionFactory) {
// 使用缓存的默认配置
RedisCacheConfiguration config = RedisCacheConfiguration
.defaultCacheConfig()
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()))
.entryTtl(Duration.ofSeconds(expireSecond));
RedisCacheManager.RedisCacheManagerBuilder builder = RedisCacheManager.builder(redisConnectionFactory).cacheDefaults(config);
return builder.build();
}
}
指定缓存使用
java
@Cacheable(value = CACHE_NAME, key = "#root.methodName+#code",cacheManager = "redisCacheManager")
当redis出现问题时,如何禁用redis,直连数据库
java
1.yml配置文件忽略掉redis配置
spring.autoconfigure.exclude:org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration
2.根据条件加载CacheConfig类,当spring.redis.useRedis=true时加载,否则不加载
@ConditionalOnProperty(prefix = "spring.redis", value = "useRedis", havingValue = "true", matchIfMissing = true)
public class CacheConfig{}
Redis工具类
java
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.jedis.JedisConnection;
import org.springframework.data.redis.core.RedisConnectionUtils;
import org.springframework.stereotype.Service;
import redis.clients.jedis.Jedis;
import javax.annotation.Resource;
@Service
@ConditionalOnProperty(prefix = "spring.redis", value = "useRedis", havingValue = "true", matchIfMissing = true)
public class RedisService {
@Resource
private RedisConnectionFactory redisConnectionFactory;
//会出现循环依赖---Circular reference
//RedisService引用JedisPool--JedisPool在RedisService,只有创建RedisService的实例才可以获取JedisPool的bean
//所以需要单独拿出JedisPool的bean
/**
* 获取单个对象
*
* @param prefix
* @param key
* @param data
* @return
*/
public <T> T get(KeyPrefix prefix, String key, Class<T> data) {
JedisConnection jedisConnection = (JedisConnection)RedisConnectionUtils.getConnection(redisConnectionFactory, true);
Jedis jedis = null;
try {
jedis = jedisConnection.getNativeConnection();
//生成真正的key className+":"+prefix; BasePrefix:id1
String realKey = prefix.getPrefix() + key;
String sval = jedis.get(realKey);
//将String转换为Bean入后传出
T t = stringToBean(sval, data);
return t;
} finally {
returnToPool(jedisConnection);
}
}
/**
* 移除对象,删除
*
* @param prefix
* @param key
* @return
*/
public boolean delete(KeyPrefix prefix, String key) {
JedisConnection jedisConnection = (JedisConnection)RedisConnectionUtils.getConnection(redisConnectionFactory, true);
Jedis jedis = null;
try {
jedis = jedisConnection.getNativeConnection();
String realKey = prefix.getPrefix() + key;
long ret = jedis.del(realKey);
return ret > 0;//删除成功,返回大于0
} finally {
returnToPool(jedisConnection);
}
}
/**
* 设置单个、多个对象
*
* @param prefix
* @param key
* @param value
* @return
*/
public <T> boolean set(KeyPrefix prefix, String key, T value) {
JedisConnection jedisConnection = (JedisConnection)RedisConnectionUtils.getConnection(redisConnectionFactory, true);
Jedis jedis = null;
try {
jedis = jedisConnection.getNativeConnection();
String realKey = prefix.getPrefix() + key;
String s = beanToString(value);
if (s == null || s.length() <= 0) {
return false;
}
int seconds = prefix.getExpireSeconds();
if (seconds <= 0) { //永不过期
jedis.set(realKey, s);
} else {
jedis.setex(realKey, seconds, s);
}
return true;
} finally {
returnToPool(jedisConnection);
}
}
/**
* 减少值
*
* @param prefix
* @param key
* @return
*/
public <T> Long decr(KeyPrefix prefix, String key) {
JedisConnection jedisConnection = (JedisConnection)RedisConnectionUtils.getConnection(redisConnectionFactory, true);
Jedis jedis = null;
try {
jedis = jedisConnection.getNativeConnection();
String realKey = prefix.getPrefix() + key;
return jedis.decr(realKey);
} finally {
returnToPool(jedisConnection);
}
}
/**
* 增加值
*
* @param prefix
* @param key
* @return
*/
public <T> Long incr(KeyPrefix prefix, String key) {
JedisConnection jedisConnection = (JedisConnection)RedisConnectionUtils.getConnection(redisConnectionFactory, true);
Jedis jedis = null;
try {
jedis = jedisConnection.getNativeConnection();
String realKey = prefix.getPrefix() + key;
return jedis.incr(realKey);
} finally {
returnToPool(jedisConnection);
}
}
/**
* 获取key的过期时间
*/
public <T> Long ttl(KeyPrefix prefix, String key) {
JedisConnection jedisConnection = (JedisConnection)RedisConnectionUtils.getConnection(redisConnectionFactory, true);
Jedis jedis = null;
try {
jedis = jedisConnection.getNativeConnection();
String realKey = prefix.getPrefix() + key;
return jedis.ttl(realKey);
} finally {
returnToPool(jedisConnection);
}
}
/**
* 设置key的过期时间
*/
public <T> void setExpire(KeyPrefix prefix, String key, int expire) {
JedisConnection jedisConnection = (JedisConnection)RedisConnectionUtils.getConnection(redisConnectionFactory, true);
Jedis jedis = null;
try {
jedis = jedisConnection.getNativeConnection();
String realKey = prefix.getPrefix() + key;
jedis.expire(realKey, expire);
} finally {
returnToPool(jedisConnection);
}
}
/**
* 检查key是否存在
*
* @param prefix
* @param key
* @return
*/
public <T> boolean exitsKey(KeyPrefix prefix, String key) {
JedisConnection jedisConnection = (JedisConnection)RedisConnectionUtils.getConnection(redisConnectionFactory, true);
Jedis jedis = null;
try {
jedis = jedisConnection.getNativeConnection();
String realKey = prefix.getPrefix() + key;
return jedis.exists(realKey);
} finally {
returnToPool(jedisConnection);
}
}
/**
* 将字符串转换为Bean对象
* <p>
* parseInt()返回的是基本类型int 而valueOf()返回的是包装类Integer
* Integer是可以使用对象方法的 而int类型就不能和Object类型进行互相转换 。
* int a=Integer.parseInt(s);
* Integer b=Integer.valueOf(s);
*/
public static <T> T stringToBean(String s, Class<T> clazz) {
if (s == null || s.length() == 0 || clazz == null) {
return null;
}
if (clazz == int.class || clazz == Integer.class) {
return ((T)Integer.valueOf(s));
} else if (clazz == String.class) {
return (T)s;
} else if (clazz == long.class || clazz == Long.class) {
return (T)Long.valueOf(s);
} else {
JSONObject json = JSON.parseObject(s);
return JSON.toJavaObject(json, clazz);
}
}
/**
* 将Bean对象转换为字符串类型
*
* @param <T>
*/
public static <T> String beanToString(T value) {
//如果是null
if (value == null) {
return null;
}
//如果不是null
Class<?> clazz = value.getClass();
if (clazz == int.class || clazz == Integer.class) {
return "" + value;
} else if (clazz == String.class) {
return "" + value;
} else if (clazz == long.class || clazz == Long.class) {
return "" + value;
} else {
return JSON.toJSONString(value);
}
}
private void returnToPool(JedisConnection jedisConnection) {
if (jedisConnection != null) {
RedisConnectionUtils.releaseConnection(jedisConnection, redisConnectionFactory);
}
}
public <T> boolean set(String key, T value) {
JedisConnection jedisConnection = (JedisConnection)RedisConnectionUtils.getConnection(redisConnectionFactory, true);
Jedis jedis = null;
try {
jedis = jedisConnection.getNativeConnection();
//将T类型转换为String类型
String s = beanToString(value);
if (s == null) {
return false;
}
jedis.set(key, s);
return true;
} finally {
returnToPool(jedisConnection);
}
}
public <T> T get(String key, Class<T> data) {
JedisConnection jedisConnection = (JedisConnection)RedisConnectionUtils.getConnection(redisConnectionFactory, true);
Jedis jedis = null;
try {
jedis = jedisConnection.getNativeConnection();
String sval = jedis.get(key);
//将String转换为Bean入后传出
T t = stringToBean(sval, data);
return t;
} finally {
returnToPool(jedisConnection);
}
}
}