前言: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客户端,用于多线程安全同步,异步和响应使用。
Lettuce
和Jedis
的都是连接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 Boot
对Redis
的支持已经非常完善了,良好的序列化以及丰富的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接口。
-
以二进制数组方式存储,内容没有可读性。