背景:缓存框架有很多,可为什么偏偏选择redis作为缓存方案呢?严格来说,redis是一个key-value型数据库,也就是NOSQL数据库,但是它是将数据放在内存当中的,所以读写速度非常快,因此 redis 被广泛应用于缓存方向。
1.修改pom.xml,添加依赖
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!--使用默认的Lettuce时,若配置spring.redis.lettuce.pool则必须配置该依赖-->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.7.3</version>
</dependency>
<!--解决jdk1.8中新时间API的序列化时出现com.fasterxml.jackson.databind.exc.InvalidDefinitionException的问题-->
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.10</version>
</dependency>
</dependencies>
2.修改application.properties
spring.redis.host=127.0.0.1
spring.redis.port=6379
spring.redis.timeout=3600ms
spring.redis.lettuce.pool.max-active=8
spring.redis.lettuce.pool.max-idle=8
3.定义redis配置类(RedisConfig)
package com.config;
import java.net.UnknownHostException;
import java.time.Duration;
import javax.annotation.Resource;
import org.springframework.beans.factory.annotation.Qualifier;
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.context.annotation.Import;
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.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.util.RedisUtil;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
@Configuration
@EnableCaching // 开启缓存支持(无此注解,可能无法读取redis配置文件)
public class RedisConfig {
@Value("${spring.redis.timeout}")
private Duration timeToLive = Duration.ZERO;
@Resource
private LettuceConnectionFactory lettuceConnectionFactory;
/**
* 解决jdk1.8中新时间API的序列化时出现com.fasterxml.jackson.databind.exc.InvalidDefinitionException的问题
*/
@Bean(name = "objectMapper")
public ObjectMapper serializingObjectMapper() {
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
objectMapper.registerModule(new JavaTimeModule());
return objectMapper;
}
/**
* 由于原生的redis自动装配,在存储key和value时,没有设置序列化方式,故自己创建redisTemplate实例
*/
@Bean(name = "redisTemplate")
public RedisTemplate<String, Object> redisTemplate(@Qualifier("objectMapper")ObjectMapper objectMapper) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
// 配置连接工厂
template.setConnectionFactory(lettuceConnectionFactory);
//使用Jackson2JsonRedisSerializer来序列化和反序列化redis的value值(默认使用JDK的序列化方式)
Jackson2JsonRedisSerializer jacksonSeial = new Jackson2JsonRedisSerializer(Object.class);
// ObjectMapper om = new ObjectMapper();
// 指定要序列化的域,field,get和set,以及修饰符范围,ANY是都有包括private和public
objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
// 指定序列化输入的类型,类必须是非final修饰的,final修饰的类,比如String,Integer等会跑出异常
objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jacksonSeial.setObjectMapper(objectMapper);
// 值采用json序列化
template.setValueSerializer(jacksonSeial);
//使用StringRedisSerializer来序列化和反序列化redis的key值
template.setKeySerializer(new StringRedisSerializer());
// 设置hash key 和value序列化模式
template.setHashKeySerializer(new StringRedisSerializer());
template.setHashValueSerializer(jacksonSeial);
template.afterPropertiesSet();
return template;
}
@Bean(name = "cacheManager")
public CacheManager cacheManager(RedisConnectionFactory factory) {
RedisSerializer<String> redisSerializer = new StringRedisSerializer();
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);
// 配置序列化(解决乱码的问题)
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(timeToLive)
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer))
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer))
.disableCachingNullValues();
return RedisCacheManager.builder(factory)
.cacheDefaults(config)
.build();
}
}
4.编写redisUtil工具类
这里有个经验:使用static关键字的变量就无法正常自动注入,所以实际开发中,就尽量不要用static关键字了,否则出现很多问题。博主就是在注入redisTemplate的时候,使用了static,导致代码一直报空指针错误(java.lang.NullPointerException)。静态变量/类变量不是对象的属性,而是一个类的属性,spring则是基于对象层面上的依赖注入。 我看见别人的代码用@PostConstruct注解去解决这个问题,但是这里我就直接删掉static,不做过多的分析,等学习较为深入的时候,再回头考虑这个问题
package com.util;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import java.util.concurrent.TimeUnit;
@Slf4j
@Component
public class RedisUtil {
@Autowired
@Qualifier("redisTemplate")
RedisTemplate<String, Object> redisTemplate;
/**********************************************************************************
* redis-公共操作
**********************************************************************************/
/**
* 指定缓存失效时间
*
* @param key 键
* @param time 时间(秒)
* @return
*/
public boolean expire(String key, long time) {
try {
if (time > 0) {
redisTemplate.expire(key, time, TimeUnit.SECONDS);
}
return true;
} catch (Exception e) {
log.error("【redis:指定缓存失效时间-异常】", e);
return false;
}
}
/**
* 根据key 获取过期时间
*
* @param key 键 不能为null
* @return 时间(秒) 返回0代表为永久有效;如果该key已经过期,将返回"-2";
*/
public long getExpire(String key) {
return redisTemplate.getExpire(key, TimeUnit.SECONDS);
}
/**
* 判断key是否存在
*
* @param key 键
* @return true 存在 false不存在
*/
public boolean exists(String key) {
try {
return redisTemplate.hasKey(key);
} catch (Exception e) {
log.error("【redis:判断{}是否存在-异常】", key, e);
return false;
}
}
/**********************************************************************************
* redis-String类型的操作
**********************************************************************************/
/**
* 普通缓存放入
*
* @param key 键
* @param value 值
* @return true成功 false失败
*/
public boolean set(String key, Object value) {
try {
redisTemplate.opsForValue().set(key, value);
return true;
} catch (Exception e) {
log.error("【redis:普通缓存放入-异常】", e);
return false;
}
}
/**
* 普通缓存放入并设置时间
*
* @param key 键
* @param value 值
* @param time 时间(秒) time要大于0 如果time小于等于0 将设置无限期
* @return true成功 false 失败
*/
public boolean set(String key, Object value, long time) {
try {
if (time > 0) {
redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
} else {
set(key, value);
}
return true;
} catch (Exception e) {
log.error("【redis:普通缓存放入并设置时间-异常】", e);
return false;
}
}
/**
* 递增
*
* @param key 键
* @param delta 要增加几(大于0)
* @return
*/
public long incr(String key, long delta) {
if (delta < 0) {
throw new RuntimeException("递增因子必须大于0");
}
return redisTemplate.opsForValue().increment(key, delta);
}
/**
* 递减
*
* @param key 键
* @param delta 要减少几(小于0)
* @return
*/
public long decr(String key, long delta) {
if (delta < 0) {
throw new RuntimeException("递减因子必须大于0");
}
return redisTemplate.opsForValue().increment(key, -delta);
}
/**
* 删除缓存
*
* @param key 可以传一个值 或多个
*/
@SuppressWarnings("unchecked")
public void del(String... key) {
if (key != null && key.length > 0) {
if (key.length == 1) {
redisTemplate.delete(key[0]);
} else {
redisTemplate.delete(CollectionUtils.arrayToList(key));
}
}
}
/**
* 获取缓存
*
* @param key redis的key
* @param clazz value的class类型
* @param <T>
* @return value的实际对象
*/
public <T> T get(String key, Class<T> clazz) {
Object obj = key == null ? null : redisTemplate.opsForValue().get(key);
if (!obj.getClass().isAssignableFrom(clazz)) {
throw new ClassCastException("类转化异常");
}
return (T) obj;
}
/**
* 获取泛型
*
* @param key 键
* @return 值
*/
public Object get(String key) {
return key == null ? null : redisTemplate.opsForValue().get(key);
}
}
5.编写redisService类
package com.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
import com.util.RedisUtil;
@Service
public class RedisService {
@Autowired
@Qualifier("redisUtil")
private RedisUtil redisUtil;
public void setObj(String key, Object obj, long timeout) {
redisUtil.set(key,obj,timeout);
}
public Object getObj(String key) {
return redisUtil.get(key);
}
}
6.编写controller类
package com.controller;
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.UUID;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import com.service.RedisService;
import com.service.UserService;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@Controller
public class HelloWorldController {
@Autowired
private RedisService redisService;
@RequestMapping("/user")
@ResponseBody
public Map<String,Object> findUserById() {
Object temp = redisService.getObj("temp" + "01");
if (temp == null) {
temp = userService.selectOne("8");
redisService.setObj("temp" + "01", temp, 1000 * 60 * 2);
return (Map<String,Object>)temp;
}
return (Map<String,Object>)temp;
}
}
测试截图