项目中用到了redis缓存,稍微整理下,有错误还望指正。
易错点:
1.注解方式的使用,注意点是,使用注解的方式,注解@cacheable(...)等声明的一定是该类直接对外的第一层方法,不能是该类的某个方法调用的下一层方法!!!!否则, 缓存不会生效
spring boot 版本信息
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.4.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
1.基本配置,在application.yml文件添加
spring:
#redis配置
redis:
host: 127.0.0.1
port: 6379
password: 123456
jedis:
pool:
max-active: 8
max-wait: -1ms
min-idle: 0
max-idle: 8
timeout: 10000
2.配置缓存的key生成,缓存管理,异常处理等
package com.example.demo;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.Cache;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.interceptor.CacheErrorHandler;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCache;
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.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.*;
import java.lang.reflect.Method;
import java.time.Duration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
/**
* Created by He on 2018/8/23.
*/
@Configuration
@EnableCaching
@Slf4j
public class RedisConfig extends CachingConfigurerSupport {
/**
* @return 自定义策略生成的key
* @description 自定义的缓存key的生成策略
* 若想使用这个key 只需要讲注解上keyGenerator的值设置为keyGenerator即可</br>
*/
@Bean
public KeyGenerator keyGenerator() {
return (target, method, params) -> {
StringBuffer sb = new StringBuffer();
sb.append(target.getClass().getName());
sb.append(":");
sb.append(method.getName());
for (Object obj : params) {
sb.append(":" + obj.toString());
}
return sb.toString();
};
}
//缓存管理器,只使用注解方式,非注解方式使用原来的方式设置参数
@Bean
public RedisCacheManager cacheManager(RedisConnectionFactory factory) {
/*以下为Spring boot1.x的方式*/
//方式1
// RedisCacheManager rcm = new RedisCacheManager(redisTemplate);//这里的redisTemplate使用redisTemplate(factory)
// //设置缓存过期时间
// rcm.setDefaultExpiration(60);//秒
// return rcm;
//方式2
// RedisCacheManager.RedisCacheManagerBuilder builder = RedisCacheManager
// .RedisCacheManagerBuilder
// .fromConnectionFactory(factory);
// return builder.build();
//方式3
// RedisCacheWriter redisCacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(factory);
// RedisCacheConfiguration defaultCacheConfig = RedisCacheConfiguration.defaultCacheConfig();
// RedisCacheConfiguration configuration = defaultCacheConfig.entryTtl(Duration.ofSeconds(100));
// RedisCacheManager cacheManager = new RedisCacheManager(redisCacheWriter, configuration);
/***************以下为Spring boot2.x的方式*************************/
//方式1,最简单的配置
// return RedisCacheManager.create(factory);
/*方式2,需要单独配置每个具体缓存空间的过期时间时使用,注意cacheName的对应*/
// 生成一个默认配置,通过config对象即可对缓存进行自定义配置
RedisCacheConfiguration cacheConfig = RedisCacheConfiguration.defaultCacheConfig();
//需要把配置返回重新赋值才有效
cacheConfig = cacheConfig.entryTtl(Duration.ofMinutes(2)) // 设置缓存的默认过期时间,也是使用Duration设置
.disableCachingNullValues() // 不缓存空值
.serializeValuesWith(RedisSerializationContext.SerializationPair
.fromSerializer(new GenericJackson2JsonRedisSerializer()));// 设置value序列化
// 设置一个初始化的缓存空间set集合,可针对不同的数据,使用不同的缓存策略;
// 也可对所有的缓存容器使用同一的过期时间
Set<String> cacheNames = new HashSet<>();
cacheNames.add("my-redis-cache1");
cacheNames.add("my-redis-cache2");
// 对每个缓存空间应用不同的过期时间
Map<String, RedisCacheConfiguration> configMap = new HashMap<>();
configMap.put("my-redis-cache1", cacheConfig.entryTtl(Duration.ofSeconds(30)));
configMap.put("my-redis-cache2", cacheConfig.entryTtl(Duration.ofSeconds(60)));
RedisCacheManager cacheManager = RedisCacheManager.builder(factory)// 使用自定义的缓存配置初始化一个cacheManager
.initialCacheNames(cacheNames) //注意这两句的调用顺序,一定要先调用该方法设置初始化的缓存名,再初始化相关的配置
.withInitialCacheConfigurations(configMap)
.build();
return cacheManager;
//-----方式3,简单对所有缓存空间统一过期时间配置-------
// RedisCacheConfiguration cacheConfiguration =
// RedisCacheConfiguration.defaultCacheConfig()
// .disableCachingNullValues() // 不缓存空值
// .entryTtl(Duration.ofMinutes(2))
// .serializeValuesWith(RedisSerializationContext.SerializationPair
// .fromSerializer(new GenericJackson2JsonRedisSerializer()));
// RedisCacheManager cacheManager = RedisCacheManager.builder(factory)
// .cacheDefaults(cacheConfiguration).build();
// return cacheManager;
}
/**
* RedisTemplate配置,适用与非注解方式
*
* @param factory
* @return
*/
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
//设置序列化
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
//配置redisTemplate
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<String, Object>();
redisTemplate.setConnectionFactory(factory);
RedisSerializer stringSerializer = new StringRedisSerializer();
redisTemplate.setKeySerializer(stringSerializer);//key序列化
redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);//value序列化
redisTemplate.setHashKeySerializer(stringSerializer);//Hash key序列化
redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);//Hash value序列化
redisTemplate.afterPropertiesSet();
return redisTemplate;
}
@Override
@Bean
public CacheErrorHandler errorHandler() {
// 异常处理,当Redis发生异常时,打印日志,但是程序正常走
log.info("初始化 -> [{}]", "Redis CacheErrorHandler");
CacheErrorHandler cacheErrorHandler = new CacheErrorHandler() {
@Override
public void handleCacheGetError(RuntimeException e, Cache cache, Object key) {
log.error("Redis occur handleCacheGetError:key -> [{}]", key, e);
}
@Override
public void handleCachePutError(RuntimeException e, Cache cache, Object key, Object value) {
log.error("Redis occur handleCachePutError:key -> [{}];value -> [{}]", key, value, e);
}
@Override
public void handleCacheEvictError(RuntimeException e, Cache cache, Object key) {
log.error("Redis occur handleCacheEvictError:key -> [{}]", key, e);
}
@Override
public void handleCacheClearError(RuntimeException e, Cache cache) {
log.error("Redis occur handleCacheClearError:", e);
}
};
return cacheErrorHandler;
}
}
3.封装非注解方式的工具类
package com.example.demo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.*;
import org.springframework.stereotype.Component;
import java.io.Serializable;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;
/**
* redis操作CURD的工具类
* Created by hesh on 2018/8/26.
*/
@Component
@Slf4j
public class RedisUtil {
@Autowired
private RedisTemplate redisTemplate;
/*********************缓存对象,最常用,适合所有类***********************/
/**
* 写入缓存,不使用过期时间
*
* @param key key
* @param value 对象
* @return
*/
public boolean set(String key, Object value) {
try {
//因为redisTemplate已经设置了序列化,不需要再次配置
//ValueOperations<Serializable, Object> operations = redisTemplate.opsForValue();
ValueOperations operations = redisTemplate.opsForValue();
operations.set(key, value);
return true;
} catch (Exception e) {
log.error("写入value缓存异常,{}", e);
}
return false;
}
/**
* 写入缓存,使用过期时间
*
* @param key
* @param value
* @param expireTime 过期时间
* @param timeUnit 单位
* @return
*/
public boolean set(final String key, Object value, Long expireTime, TimeUnit timeUnit) {
try {
// ValueOperations<Serializable, Object> operations = redisTemplate.opsForValue();
ValueOperations operations = redisTemplate.opsForValue();
// operations.set(key, value);
// redisTemplate.expire(key, expireTime, timeUnit);
operations.set(key, value, expireTime, timeUnit);
return true;
} catch (Exception e) {
log.error("写入value缓存异常,{}", e);
}
return false;
}
/**
* 读取缓存
*
* @param key
* @return
*/
public Object get(final String key) {
Object result = null;
// ValueOperations<Serializable, Object> operations = redisTemplate.opsForValue();
ValueOperations operations = redisTemplate.opsForValue();
result = operations.get(key);
return result;
}
/**
* 判断缓存中是否有对应的value
*
* @param key
* @return
*/
public boolean exists(final String key) {
return redisTemplate.hasKey(key);
}
/**
* 删除对应的value
*
* @param key
*/
public boolean remove(final String key) {
if (exists(key)) {
return redisTemplate.delete(key);
}else {
return false;
}
}
/**
* 批量删除对应的value
*
* @param keys
*/
public void remove(final String... keys) {
for (String key : keys) {
remove(key);
}
}
/**
* 根据正则表达式,批量删除key
*
* @param pattern
*/
public void removePattern(final String pattern) {
Set<Serializable> keys = redisTemplate.keys(pattern);
if (keys.size() > 0) {
redisTemplate.delete(keys);
}
}
/*********************以下为哈希,list等类型的基本操作***********************/
public boolean hmSet(String key, Object hk, Object hv) {
try {
HashOperations<String, Object, Object> hash = redisTemplate.opsForHash();
hash.put(key, hk, hv);
return true;
} catch (Exception e) {
log.error("写入hash缓存异常,{}", e);
}
return false;
}
/**
* 哈希获取数据
*
* @param key
* @param hashKey
* @return
*/
public Object hmGet(String key, Object hashKey) {
// HashOperations<String, Object, Object> hash = redisTemplate.opsForHash();
HashOperations<String, Object, Object> hash = redisTemplate.opsForHash();
return hash.get(key, hashKey);
}
/**
* 列表添加
*
* @param k
* @param v
*/
public void listSet(String k, Object v) {
ListOperations<String, Object> list = redisTemplate.opsForList();
list.rightPush(k, v);
}
/**
* 列表获取
*
* @param k
* @param start
* @param end
* @return
*/
public List<Object> listGet(String k, long start, long end) {
ListOperations<String, Object> list = redisTemplate.opsForList();
return list.range(k, start, end);
}
/**
* 集合添加
*
* @param key
* @param value
*/
public void setAdd(String key, Object value) {
SetOperations<String, Object> set = redisTemplate.opsForSet();
set.add(key, value);
}
/**
* 集合获取
*
* @param key
* @return
*/
public Set<Object> setPop(String key) {
SetOperations<String, Object> set = redisTemplate.opsForSet();
return set.members(key);
}
/**
* 有序集合添加
*
* @param key
* @param value
* @param scoure
*/
public void zSetAdd(String key, Object value, double scoure) {
ZSetOperations<String, Object> zset = redisTemplate.opsForZSet();
zset.add(key, value, scoure);
}
/**
* 有序集合获取
*
* @param key
* @param scoure
* @param scoure1
* @return
*/
public Set<Object> zSetPop(String key, double scoure, double scoure1) {
ZSetOperations<String, Object> zset = redisTemplate.opsForZSet();
return zset.rangeByScore(key, scoure, scoure1);
}
}
4.注解方式的使用,注意点是,使用注解的方式,注解@cacheable(...)等声明的一定是该类直接对外的第一层方法,不能是该类的某个方法调用的下一层方法!!!!否则, 缓存不会生效
package com.example.demo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheConfig;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.stereotype.Service;
import java.util.*;
/**
* Created by He on 2018/8/23.
*/
@Service
@Slf4j
//@CacheConfig(cacheNames = "User",keyGenerator = "keyGenerator")
public class UserServiceImpl implements UserService {
@Override
@Cacheable(cacheNames = "my-redis-cache1",key = "'cache1-'.concat(#username)",
unless = "#result ==null || #result.get(0).photoInfo.thumbs.size()<=0")//使用过期时间1,测试OK
public List<User> getUser(String username) {
log.info("进入实现类获取数据: {}" + username);
Random random = new Random();
PhotoInfo photoInfo = new PhotoInfo();
List<String> urls = new ArrayList<>();
urls.add("http://xxx.com");
photoInfo.setThumbs(urls);
User user1 =new User(username, random.nextInt(30),photoInfo);
User user2 =new User(username, random.nextInt(30),photoInfo);
List<User> users = new ArrayList<>();
users.add(user1);
users.add(user2);
return users;
}
@Cacheable(cacheNames = "my-redis-cache2",key = "'cache2-'.concat(#username)")//使用过期时间2,测试OK
@Override
public User getUser2(String username) {
log.info("进入实现类获取数据: {}" + username);
Random random = new Random();
PhotoInfo photoInfo = new PhotoInfo();
List<String> urls = new ArrayList<>();
urls.add("http://xxx.com");
photoInfo.setThumbs(urls);
int age = random.nextInt(30);
if (age < 20) {
return null; //测试返回空,不缓存的策略
}else {
return new User(username, age,photoInfo);
}
}
}
5.非注解方式的使用
package com.example.demo;
import lombok.extern.slf4j.Slf4j;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.test.context.junit4.SpringRunner;
import java.util.*;
import java.util.concurrent.TimeUnit;
import static org.junit.Assert.*;
/**
* Created by He on 2018/8/24.
*/
@RunWith(SpringRunner.class)
@SpringBootTest
@Slf4j
public class UserServiceImplTest {
/* 测试直接使用redisTemplate使用redis操作数据 */
@Autowired
private RedisUtil redisUtil;
/*测试缓存object*/
@Test
public void setAndGet() {
redisUtil.set("test:value:setString", "value1");
Assert.assertEquals("value1", redisUtil.get("test:value:setString"));
}
@Test
//直接使用redisTemplate存取对象
public void setAndGetUser() {
PhotoInfo photoInfo = new PhotoInfo();
List<String> urls = new ArrayList<>();
urls.add("http://xxx.com");
photoInfo.setThumbs(urls);
User user = new User("Tom", 10, photoInfo);
redisUtil.set("test:value:setObject", user);
User userTest = (User) redisUtil.get("test:value:setObject");
log.info("获取对象:{}", userTest);
}
/*使用过期时间*/
@Test
public void testTimeOut() {
redisUtil.set("test:value:setTimeout", "timeout", Long.valueOf(20), TimeUnit.SECONDS);
log.info("第1次获取对象:{}", redisUtil.get("test:value:setTimeout"));
try {
Thread.sleep(20 * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.info("第2次获取对象:{}", redisUtil.get("test:value:setTimeout"));
}
/*是否存在,和删除*/
@Test
public void testDeleteExist() {
boolean isExist = redisUtil.exists("test:value:setString");
log.info("存在该值? {}", isExist);
if (isExist) {
boolean isDeleted = redisUtil.remove("test:value:setString");
log.info("删除成功? {}", isDeleted);
}
}
/**
* hash
*/
@Test
public void testHash() {
PhotoInfo photoInfo = new PhotoInfo();
List<String> urls = new ArrayList<>();
urls.add("http://xxx.com");
photoInfo.setThumbs(urls);
User user = new User("hm1", 10, photoInfo);
redisUtil.hmSet("test:hash:setObject:", "user1", user);
Assert.assertEquals("hm1", ((User) redisUtil.hmGet("test:hash:setObject:", "user1")).getUsername());
}
/**
* list
*/
@Test
public void testList() {
PhotoInfo photoInfo = new PhotoInfo();
List<String> urls = new ArrayList<>();
urls.add("http://xxx.com");
photoInfo.setThumbs(urls);
User user = new User("list1", 10, photoInfo);
List<User> users = new ArrayList<>();
users.add(user);
redisUtil.listSet("test:list:setObject:", users);
Assert.assertNotNull(redisUtil.listGet("test:list:setObject:", 0, users.size() - 1));
}
/**
* set
*/
@Test
public void testSet() {
PhotoInfo photoInfo = new PhotoInfo();
List<String> urls = new ArrayList<>();
urls.add("http://xxx.com");
photoInfo.setThumbs(urls);
User user = new User("list1", 10, photoInfo);
Set<User> users = new HashSet<>();
users.add(user);
redisUtil.setAdd("test:set:setObject:", users);
Assert.assertNotNull(redisUtil.setPop("test:set:setObject:"));
}
/**
* zSet
*/
// @Test
// public void testZSet() {
// User user1 = new User("list1", 12);
// User user2 = new User("list1", 11);
// LinkedHashSet<User> users = new LinkedHashSet<>();
// users.add(user1);
// users.add(user2);
// redisUtil.zSetAdd("test:ZSet:setObject:", users, 2);
// Assert.assertNotNull(redisUtil.zSetPop("test:ZSet:setObject:", 2, 1));
// }
}