如何在SpringBoot中集成Redis(RedisTemplate)

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);
    }
    
}

        总结:上述只是SpringBoot集成redis的方式,但在实际工作开发过程中,并不是像上述那样使用的,上面只是结合例子记录一下这个过程中遇到的问题

评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值