1、导入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
如图:
2、在application.yml中配置Redis
spring:
redis:
host: localhost
password:
port: 6379
3、普通存放与获取
经过1、2两个步骤,其实Redis已经配置结束,SpringBoot会自动把RedisTeplate(操作Redis的类)装配到Bean容器中,我们在哪里使用,就在哪里用@Autowired注解注入即可(在这里我踩了坑,后面讲到)。示例:
@Service
@Slf4j
public class BlogServiceImpl implements BlogService {
@Autowired
private BlogMapper blogMapper;
@Autowired
private RedisTemplate<Object, Object> redisTemplate;
@Override
public ResultVO getByPrimaryKey(Integer id) {
Blog blog = (Blog) redisTemplate.opsForValue().get(id.toString());
if (blog == null) {
log.info("缓存中没有,需要从数据库中读取");
Blog blogTemp = blogMapper.selectByPrimaryKey(id);
redisTemplate.opsForValue().set(id.toString(), blogTemp);
log.info("已放入缓存,下次可直接从缓存获取");
return ResultVO.success(blogTemp);
}
return ResultVO.success(blog);
}
}
上面代码可看到,我们在查询时先去redis中查询看是否缓存,如果有直接返回,如果没有,去数据库中读取,读取完了放入redis中(此处先不谈缓存失效时间),下次直接从缓存拿,但此时如果启动并访问接口后会报错,错误如下:
错误说Blog这个对象没办法序列化,为什么需要序列化?
在SpringBoot2.x以后,集成spring-data-redis底层不再是jedis,而变成了lettuce,jedis采用的直连,多线程操作不安全,更像BIO,而lettue采用netty(Dubbo也采用netty),线程安全,更像NIO,而这种NIO异步的对象传输一定是需要序列化的
那好,将Blog实现Serializable接口,生成序列ID(不生成在本案例中可行,但最好生成),再次运行即可成功。
有以下几个点需要注意:
(1)、上面的步骤已经可以完成在java中存放与获取了,但是我们在redis-cli.exe中看到的却是这个样子
其实这个对于存取没有影响,如果我们为了在redis-cli.exe中更直观看到key,可以在存放之前设置一下redisTemplate对key的序列化方式,代码如下:
@Service
@Slf4j
public class BlogServiceImpl implements BlogService {
@Autowired
private BlogMapper blogMapper;
@Autowired
private RedisTemplate<Object, Object> redisTemplate;
@Override
public ResultVO getByPrimaryKey(Integer id) {
Blog blog = (Blog) redisTemplate.opsForValue().get(id.toString());
if (blog == null) {
log.info("缓存中没有,需要从数据库中读取");
Blog blogTemp = blogMapper.selectByPrimaryKey(id);
// 设置key为字符串序列化方式
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.opsForValue().set(id.toString(), blogTemp);
log.info("已放入缓存,下次可直接从缓存获取");
return ResultVO.success(blogTemp);
}
return ResultVO.success(blog);
}
}
此时存入时需要注意Integer类型的需要toString,否则会报类强转异常,这时的key如下所示:
如果想要在redis-cli.exe直观地看到对象,可以将RedisTemplate对value的序列化方式设置为json,然后直接存入Object即可,也可以将RedisTemplate采用<String, String>泛型,然后将对象转为json格式存入即可 ,这种将Object通过json的方式序列化并存入redis在实际开发中是最常见的
(2)、自动注入时踩的坑(当然了,在实际开发中,总会定义自己的RedisTemplate)
我第一次自动注入时是这么写的@Autowired private RedisTemplate<String, Object> redisTemplate; 这么写,启动会报错,说在IoC容器中找不到redisTemplate,这是因为RedisTemplate的泛型只能是<String, String>或者<Object, Object>,其他泛型会注入失败,或者你不写泛型也可以自动注入成功
4、高并发模式缓存失去意义(缓存穿透)
请求发来会先去redis中查看有没有,如果没有再去数据库查,那么当多个用户同时发送请求时,例如A、B同时从redis拿,都没拿到,那么A、B都会去数据库读取,这样,redis就失去了意义,那么应当如何解决?
首先想到的是synchronized关键字,如下:
···将synchronized关键字加在方法上,那么不管当前用户能否从缓存中取到数据,都会与其他线程互斥,例如,A、B两个用户(线程),A要取的数据在redis缓存中,B要取的数据没有在,此时B稍稍快一点访问了getByPrimarykey方法,那么A只能等待,但是明明A只需要从缓存中取出即可,这种方式效率太低,所以我们可以采用给局部代码加synchronized关键字并且进行优化(双重检测),看代码立马明白:
@Service
@Slf4j
public class BlogServiceImpl implements BlogService {
@Autowired
private BlogMapper blogMapper;
@Autowired
private RedisTemplate<Object, Object> redisTemplate;
@Override
public ResultVO getByPrimaryKey(Integer id) {
// 设置序列化字符串方式
redisTemplate.setKeySerializer(new StringRedisSerializer());
// 如果能从缓存中获取,那么直接返回
Blog blog = (Blog) redisTemplate.opsForValue().get(id.toString());
// 如果获取不到,那么接下来的逻辑线程之间互斥
if (blog == null) {
synchronized (this) {
blog = (Blog) redisTemplate.opsForValue().get(id.toString());
if (blog == null) {
log.info("缓存中没有,需要从数据库中读取");
Blog blogTemp = blogMapper.selectByPrimaryKey(id);
redisTemplate.opsForValue().set(id.toString(), blogTemp);
log.info("已放入缓存,下次可直接从缓存获取");
return ResultVO.success(blogTemp);
}
}
}
return ResultVO.success(blog);
}
}