SpringBoot 2.x整合Redis使用

前言:SpringBoot 2.0已经使用Lettuce代替Jedis客户端。Spring框架的spring-data-redis的相关Jar包,不仅支持连接池自动管理,而且它还提供了使用Redis的模版RedisTemplate<K, V>接口或它的实现类StringRedisTemplate,可以支持Redis没有的缓存对象的操作。

也就是说,我们可以在SpringBoot应用中通过简单的连接池配置信息,就能访问Redis服务并进行相关缓存操作。

一、Lettuce多线程安全的Redis客户端概述

1、Lettuce的出现背景

如果你在网上搜索 Redis 的 Java 客户端,你会发现,大多数文献介绍的都是 Jedis,不可否认,Jedis 是一个优秀的基于 Java 语言的 Redis 客户端,但是,其不足也很明显:Jedis 在实现上是直接连接 Redis-Server,在多个线程间共享一个 Jedis 实例时是线程不安全的,如果想要在多线程场景下使用 Jedis,需要使用连接池,每个线程都使用自己的 Jedis 实例,当连接数量增多时,会消耗较多的物理资源。

与 Jedis 相比,Lettuce 则完全克服了其线程不安全的缺点:Lettuce 是一个可伸缩的线程安全的 Redis 客户端,支持同步、异步和响应式模式。多个线程可以共享一个连接实例,而不必担心多线程并发问题。它基于优秀 Netty NIO 框架构建,支持 Redis 的高级功能,如 Sentinel,集群,流水线,自动重新连接和 Redis 数据模型。

2、SpringBoot 2.x已经使用Lettuce代替Jedis客户端

SpringBoot是为了简化Spring应用的创建、运行、调试、部署等一系列问题而诞生的产物。自动装配的特性让我们可以更好的关注业务本身而不是外部的XML配置文件,我们只需遵循规范,引入相关的依赖就可以轻易的搭建出一个Web工程

Spring Boot除了支持常见的ORM框架外,更是对常用的中间件提供了非常好封装,随着Spring Boot2.x的到来,支持的组件越来越丰富,也越来越成熟,其中对Redis的支持不仅仅是丰富了它的API,更是替换掉底层Jedis的依赖,取而代之换成了Lettuce高级Redis客户端,用于多线程安全同步,异步和响应使用。

LettuceJedis的都是连接Redis Server的客户端程序。Jedis在实现上是直连redis server,多线程环境下非线程安全,除非使用连接池JedisPool,为每个Jedis实例增加物理连接。Lettuce基于Netty的连接实例(StatefulRedisConnection),可以在多个线程间并发访问,且线程安全,满足多线程环境下的并发访问,同时它是可伸缩的设计,一个连接实例不够的情况也可以按需增加连接实例。

为了多线程安全,以前是Jedis+JedisPool组合 ,现在在SpringBoot 2.0应用中直接使用Lettuce客户端的API封装RedisTemplate即可只要配置好连接池属性,那么SpringBoot就能自动管理连接池。

二、SpringBoot 2.X快速整合Redis进行缓存数据库查询

项目常见问题思考:项目首页每天有大量的人访问,对数据库造成很大的访问压力,甚至是瘫痪。那如何解决呢?我们通常的做法有两种:一种是数据缓存、一种是网页静态化。我们今天讨论第一种解决方案。

本篇博客Spring Boot版本:SpringBoot 2.2.0.RELEASE

<parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.0.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

Mysql数据库user表准备如下:

1、pom.xml添加redis的起步依赖spring-boot-starter-data-redis,Spring Boot2.x后底层不在是Jedis,如果做版本升级的朋友需要注意下

<!-- Spring Boot2.x后底层不在是Jedis,而是用多线程安全的Lettuce,所以配置连接池信息时得用新姿势-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

2、配置redis的连接信息

注意事项:在application.properties文件中配置如下内容,由于Spring Boot2.x的改动,连接池相关配置需要通过spring.redis.lettuce.pool而不是spring.redis.jedis.pool进行配置了。

#Redis连接信息
spring.redis.host=127.0.0.1
spring.redis.port=6379
#spring.redis.password=root根据自己的需要,没有设置Redis密码可以不管
###以下是Redis连接池相关信息###
# 连接超时时间(毫秒)
spring.redis.timeout=10000
# Redis默认情况下有16个分片,这里配置具体使用的分片,默认是0
spring.redis.database=0
# 连接池最大连接数(使用负值表示没有限制) 默认 8
spring.redis.lettuce.pool.max-active=100
# 连接池最大阻塞等待时间(使用负值表示没有限制) 默认 -1
spring.redis.lettuce.pool.max-wait=-1
# 连接池中的最大空闲连接 默认 8
spring.redis.lettuce.pool.max-idle=32
# 连接池中的最小空闲连接 默认 0
spring.redis.lettuce.pool.min-idle=10

3、在Service层中通过RedisTemplate操作Redis,使用Redis进行缓存数据库查询

Spring BootRedis的支持已经非常完善了,良好的序列化以及丰富的API足够应对日常开发

(1)新建实现Serializable接口UserEntity实体类

package com.hs.springbootdemo.dao.entity;

import org.springframework.stereotype.Component;
import java.io.Serializable;

/**
*   用户实体类
 */

@Component//把pojo类注册到spring容器中,相当于配置文件中的<bean id="" class=""/>
public class UserEntity implements Serializable {

    //属性名必须与数据库表的字段名一致,以便Mybatis直接把实体类映射成数据库记录
    private Integer uid; //用户id
    private String username;//用户名
    private String password;//用户密码

    public Integer getUid() {
        return uid;
    }

    public void setUid(Integer uid) {
        this.uid = uid;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    @Override
    public String toString() {
        return "用户ID:"+uid+"\t用户名"+username+"\t密码:"+password;
    }
}

(2)Dao层新建Mybatis动态代理的Mapper接口

package com.hs.springbootdemo.dao.mapper;

import com.hs.springbootdemo.dao.entity.UserEntity;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;

import java.util.List;

/**
 * Mybatis通过@Mapper注解实现动态代理,mybatis会自动创建Dao接口的实现类代理对象注入IOC容器进行管理,这样就不用编写Dao层的实现类
 *
 */

@Mapper
public interface UserMapper {

 @Select("SELECT * FROM user WHERE uid=#{uid}")
    UserEntity getUserById(Integer uid);

}

#{}与${}的区别:

#{}在引用时,如果发现目标是一个字符串,则会将其值作为一个字符串拼接在sql上,即拼接时自动包裹引号;
${}在引用时,即使发现目标是一个字符串,也不会作为字符串处理,拼接在sql时不会自动包裹引号,Sql语句会报错。

并且#{}能够很大程度上防止sql注入,所以能用#{}时尽量用#{},不过在Mybaties排序时使用order by 动态参数时需要注意,使用${}而不用#{}。

(3)Service层使用默认就有的RedisTemplate对Redis进行缓存数据库查询

package com.hs.springbootdemo.service;

import com.hs.springbootdemo.SpringbootdemoApplication;
import com.hs.springbootdemo.dao.entity.UserEntity;
import com.hs.springbootdemo.dao.mapper.UserMapper;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.test.context.junit4.SpringRunner;
import javax.annotation.Resource;

@RunWith(SpringRunner.class)//SpringBoot 2.X 默认使用Junit4
@SpringBootTest(classes = SpringbootdemoApplication.class)
public class UserServiceTest {

    @Resource//@Resource的作用相当于@Autowired,都可以用来自动装配bean
    private UserMapper userDao;
    @Resource
    private RedisTemplate redisTemplate;//自带的字符串模板类,用于存储字符串
    @Resource
    private StringRedisTemplate stringRedisTemplate;//自带的对象模板类,用于存储对象用于缓存字符串

    @Test
    public void getUserById()
    {
        Logger logger = LoggerFactory.getLogger(UserServiceTest.class);
        int uid = 2;//为了测试随便指定一个数据库中已有的ID

        // 从redis缓存中提取数据
        UserEntity userEntity = (UserEntity)redisTemplate.opsForValue().get("user" + uid);

        // 如果redis缓存中没有,则从Mysql数据库中查询并放入Redis缓存中
        if(userEntity == null)
        {
            logger.warn("Redis中没有命中");
            userEntity = userDao.getUserById(uid);
            redisTemplate.opsForValue().set("user" + uid, userEntity);
        }

        logger.warn(userEntity.toString());
    }

}

(4)打开Redis目录下的redis-server,启动Redis

(5)Idea中运行单元测试类UserServiceTest

测试结果:

第一次执行

第二次执行:可以看到Redis缓存中已经有了,直接取出来

4、下列的就是Redis其它类型所对应的操作方式

  • **opsForValue:**对应 String(字符串)

  • **opsForZSet:**对应 ZSet(有序集合)

  • **opsForHash:**对应 Hash(哈希)

  • **opsForList:**对应 List(列表)

  • **opsForSet:**对应 Set(集合)

  • **opsForGeo:**对应 GEO(地理位置)

5、自定义Template

SpringBoot默认提供了两个使用Redis的类StringRedisTemplate和RedisTemplate,其中RedisTemplate可以支持Redis没有的缓存对象的操作;而StringRedisTemplate用来存储字符串,其实是RedisTemplate<String, String>的实现类。

StringRedisTemplate只能存入字符串,这在开发中是不友好的,所以自定义模板注入IOC容器是很有必要的。

    //泛型接口随便自定义不同RedisTemplate
    @Resource
    RedisTemplate<String,Integer>  template;//可以缓存整数了

6、缓存过期设置过期时间

Redis的过期时间使用场景很广泛,当需要设置缓存令某个值仅在一段时间内有效(如优惠券、验证码等)、设置最短访问间隔(防止爬虫太多导致服务器宕机),则都需要设置过期时间。

有时候我们并不希望Redis的key一直存在。例如单点登录中我们需要随机生成一个token作为key,将用户的信息转为json串作为value保存在redis中。我们希望它们能在一定时间内自动的被销毁。Redis提供了一些命令,能够让我们对key设置过期时间,并且让key过期之后被自动删除。

// 设置缓存过期时间为1天
redisTemplate.opsForValue().set("article_" + id, article, 1, TimeUnit.DAYS);

参考链接:Java操作Redis缓存设置过期时间

三、RedisTemplate和StringRedisTemplate对比

RedisTemplate看这个类的名字后缀是Template,如果了解过Spring如何连接关系型数据库的,大概不会难猜出这个类是做什么的 ,它跟JdbcTemplate一样封装了对Redis的一些常用的操作,当然StringRedisTemplate跟RedisTemplate功能类似那么肯定就会有人问,为什么会需要两个Template呢,一个不就够了吗?其实他们两者之间的区别主要在于他们使用的序列化类。

RedisTemplate使用的是 JdkSerializationRedisSerializer 序列化对象;
StringRedisTemplate使用的是 StringRedisSerializer 序列化String。

1、StringRedisTemplate

  • 主要用来存储字符串,StringRedisSerializer的泛型指定的是String。当存入对象时,会报错 :can not cast into String。

  • 可见性强,更易维护。如果过都是字符串存储可考虑用StringRedisTemplate。

2、RedisTemplate

  • 可以用来存储对象,但是要实现Serializable接口。

  • 以二进制数组方式存储,内容没有可读性。

猜你喜欢

转载自blog.csdn.net/CSDN2497242041/article/details/102675473