Redis(lettuce)
Jedis的封装----->
RedisTemplate
Jedis的封装-----> Jedis工具类----->JedisUtil
Java代码操作Redis,需要使用Jedis,也就是Redis支持的第三方类库
注意:Jedis2.7以上的版本才支持集群的操作
文章目录
- Redis(lettuce)
- 一、使用SpringBoot连接配置Redis
- 1.1 Maven配置
- 1.2 编写连接参数的yml
- 1.3 测试连接是否成功
- 二、SpringBoot整合RedisConfig
- 2.1 opsForValue
- 2.2 RedisTemplate源码剖析
- 2.3 自定义序列化类
- 2.4 Redistemplate模拟序列化
- 2.5 OpsForHash
- 2.6 序列化Hash类型的KV
- 2.7 改造业务实现方法
- 三、案例 :限制登录功能
- 3.1 目的:防止暴力破解
- 3.2 思路
一、使用SpringBoot连接配置Redis
1.1 Maven配置
新建一个SpringBoot2.x的项目,在Maven的pom.xml的文件里加入以下依赖
<dependencies>
<!--SpringBoot的启动器-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--热部署工具-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!--RedisTemplate 默认是Lettuce客户端-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!--redis依赖于commons_pool2 这个以来也一定要加-->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
<!--测试的依赖库文件-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
1.2 编写连接参数的yml
server:
port: 8089
spring:
application:
name: redistemplate
redis:
host: 192.168.188.129
port: 6379
database: 0
jedis:
pool:
max-active: 10
max-idle: 10
min-idle: 1
注意:这里的因为采用的原生的SpringBoot操作Redis,所以不需要写配置类,因为SpringBoot1.x版本,SpringBoot已经帮我们做好了封装
1.3 测试连接是否成功
@SpringBootTest
class DhcRedisProjectLettuceApplicationTests {
@Autowired
private RedisTemplate<String, String> redisTemplate;
@Test
void contextLoads() {
System.out.println(redisTemplate);
}
}
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiI0gTMx81dsQWZ4lmZf1GLlpXazVmcvwFciV2dsQXYtJ3bm9CX9s2RkBnVHFmb1clWvB3MaVnRtp1XlBXe0xCMy81dvRWYoNHLwEzX5xCMx8FesU2cfdGLwMzX0xiRGZkRGZ0Xy9GbvNGLpZTY1EmMZVDUSFTU4VFRR9Fd4VGdsYTMfVmepNHLrJXYtJXZ0F2dvwVZnFWbp1zczV2YvJHctM3cv1Ce-cmbw5yN0czN0MmMiRTYmRmN1YzMzYzX3ITOzATMwMzLclDMyIDMy8CXn9Gbi9CXzV2Zh1WavwVbvNmLvR3YxUjLyM3Lc9CX6MHc0RHaiojIsJye.png)
连接成功,返回redisTemplate在内存中封装lettuce后的内存地址
二、SpringBoot整合RedisConfig
利用SpringBoot去整合Redis的时候,前面知道默认是使用的Lettuce来操作redispool
这是我们还需要借助一些方法去链接Redis
2.1 opsForValue
@Test
void contextLoads() {
ValueOperations<String, String> opsForValue = redisTemplate.opsForValue();
opsForValue.set("java", "Redis学习2022");
String string = opsForValue.get("java");
System.out.println(string);
}
可以看到我们借助redisTemplate完成了对Redis的数据存取操作,但是这是我们去Redis的客户端去查看
192.168.188.129:6379> keys *
1) "java"
2) "User:1001"
192.168.188.129:6379> get java
"Redis\xe5\xad\xa6\xe4\xb9\xa02022"
192.168.188.129:6379>
发现我们所存储的key-value会存在中文乱码的问题
❓思考: 为什么在使用SpringBoot的redisTemplate里操作存取Redis的key的时候不会中乱码呢?
**
😗*因为RedisTemplate里面封装有序列化的操作,实际上redisTemplate是对jedis的封装
原因
2.2 RedisTemplate源码剖析
打开RedisTempalte里面的源码查看一个叫做afterPropertiesSet的方法,里面有对redis操作的序列化操作
@Override
public void afterPropertiesSet() {
super.afterPropertiesSet();
boolean defaultUsed = false;
if (defaultSerializer == null) {
defaultSerializer = new JdkSerializationRedisSerializer(
classLoader != null ? classLoader : this.getClass().getClassLoader());
}
if (enableDefaultSerializer) {
if (keySerializer == null) {
keySerializer = defaultSerializer;
defaultUsed = true;
}
if (valueSerializer == null) {
valueSerializer = defaultSerializer;
defaultUsed = true;
}
if (hashKeySerializer == null) {
hashKeySerializer = defaultSerializer;
defaultUsed = true;
}
if (hashValueSerializer == null) {
hashValueSerializer = defaultSerializer;
defaultUsed = true;
}
}
if (enableDefaultSerializer && defaultUsed) {
Assert.notNull(defaultSerializer, "default serializer null and not all serializers initialized");
}
if (scriptExecutor == null) {
this.scriptExecutor = new DefaultScriptExecutor<>(this);
}
initialized = true;
}
编写缓存配置类RedisConfig的时候用于调优缓存默认配置,RedisTemplate<String,String>的类型兼容性更高
在Spring-data-redis里的序列化类主要有以下几个:
- **
**可将任何对象泛化为字符串并序列化GenericToStringSerializer:
- **Jackson2JsonRedisSerializer:**序列化Object对象为Json字符串,(与JacksonJsonRedisSerializer相同)
- JdkSerializationRedisSerializer:序列化对象必须实现Serializier接口,是原生的JDK的序列化类(默认的序列化方式)
-
StringRedisSerializer:
JdkSerializationRedisSerializer序列化,长度长且不易读取,被序列化的属性内容还有其他内容
内容长度格式类似如下:“Redis\xe5\xad\xa6\xe4\xb9\xa02022”–>Redis学习2022
Jackson2JsonRedisSerializer序列化,被序列化对象不需要实现Serializable接口,被序列化结果清晰,内容阅读,而且存储的字节数少,速度快
存储的内容如下:
2.3 自定义序列化类
上面我们通过查看源码发现,RedisTemplate里默认使用的是Jdk的序列化类,这种方式操作对象数据比较鸡肋,所以,这里我么来自定义redisTemplate的序列化类。
新建一个RedisConfig的类完成对RedisTempalte的序列化类的自定义操作。
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String, String> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<String, String> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory);
// 自定义Redis的序列化
StringRedisSerializer serializer = new StringRedisSerializer();
template.setKeySerializer(serializer);
template.setValueSerializer(serializer);
return template;
}
}
再次测试,我们发现这是的Redis的序列化后的中文在不会出现中文乱码,但是在其客户端内查看仍然会有乱码
**总结:**使用RedisTmeplate的自定义的序列化类也有不足之处,但是相比于使用默认的Jdk的序列化工具来说,要有一些优势。
2.4 Redistemplate模拟序列化
StringRedisTemplate
的出现就是为了解决上面所出现的问题,
但是仅仅针对于String数据类型,
@Service
@Slf4j
public class UserServiceImpl {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
/**
* 用RedisTempalte来操作String数据类型
*/
/**
* 需求与之前一样
*/
public String getUserName(String id) {
// 生成key
String key = "userName:"+id;
ValueOperations<String, Object> opsForValue = redisTemplate.opsForValue();
if (redisTemplate.hasKey(key)) {
log.info("查询的是Redis...");
String value = (String)opsForValue.get(key);
return value;
}else {
log.info("查询的是MySql数据库....");
String valueString ="赵安";
opsForValue.set(key, valueString);
return valueString;
}
}
}
测试
@Test
public void test2() {
String result = userServiceImpl.getUserName("1001");
System.out.println(result);
}
2.5 OpsForHash
OpsForHash是一个用来操作Hash数据类型的方法,其可以完成对Java对象的存取。
业务方法的实现
/**
* 用RedisTempalte来操作Hash数据类型
*/
public User selectByUserId(String id) {
// String key = "User:"+id;
// HashOperations<H, HK, HV> H:表示user;HK表示id;HV表示存储的User对象
HashOperations<String, Object, Object> hashOperations = redisTemplate.opsForHash();
if (!hashOperations.hasKey("User", id)) {
// 查询MySQL 不存在
log.info("查询的是MySQL数据库...");
User user = new User();
user.setId("1002");
user.setName("辛迪");
user.setRemark("是一个勤奋的好孩子...");
hashOperations.put("User", id, user);
return user;
}else {
log.info("查询的是Redis数据库...");
User result = (User)hashOperations.get("User", id);
return result;
}
}
测试业务方法
@Test
public void testHash() {
User user = userServiceImpl.selectByUserId("1002");
System.out.println(user);
}
但是这是我们去客户端中查看,发现我们的中文是有乱码的,所以我们继续去RedisCnofig的类里去自定义修改Hash数据类型的key-value的序列化类。
2.6 序列化Hash类型的KV
这里我们就需要使用到在上面所讲到的Jackson序列化的类,来实现对Hash数据类型的数据的序列化存取操作
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory);
// 自定义Redis的序列化
StringRedisSerializer serializer = new StringRedisSerializer();
// 自定义Hash类型的Value的序列化类
GenericJackson2JsonRedisSerializer jackson2JsonRedisSerializer = new GenericJackson2JsonRedisSerializer();
// 自定义Hash的数据类型的KEY-VALUE的序列化
// KEY可以是String类型 但是值 就不可能是String类型
template.setHashKeySerializer(serializer);
// Value 必须要使用jason格式的序列化类
template.setHashValueSerializer(jackson2JsonRedisSerializer);
return template;
}
这是我们再次运行测试类,去查看客户端的中文是否存在乱码
可以看到在,这里就可以查看到被Json序列化后的数据的值了。
2.7 改造业务实现方法
之前我们在操作Redis的数据类型的时候,我们往往不会修改其泛型的类型,但是,在实际的而业务开发中,需要根据所需要的数据类型来进行适当的修改,为了可以实现最佳的一个实现效果,这里我对UserServiceImpl的累计逆行修改
@Service
@Slf4j
public class UserServiceImpl {
@Autowired
private RedisTemplate<String, String> redisTemplate;
/**
* 用RedisTempalte来操作String数据类型
*/
@Resource(name = "redisTemplate")
ValueOperations<String, String> opsForValue;
/**
* 用RedisTempalte来操作Hash数据类型
* name 必须是redisTemplate 因为RedisTemplate里面的构造方法是RedisTemplate()
*/
@Resource(name = "redisTemplate")
HashOperations<String, String, Object> hashOperations;
/**
* 需求与之前一样
*/
public String getUserName(String id) {
// 生成key
String key = "userName:" + id;
// ValueOperations<String, String> opsForValue = redisTemplate.opsForValue();
if (redisTemplate.hasKey(key)) {
log.info("查询的是Redis...");
// String value = opsForValue.get(key);
return opsForValue.get(key);
} else {
log.info("查询的是MySql数据库....");
String valueString = "刘大黄";
opsForValue.set(key, valueString);
return valueString;
}
}
/**
* 用RedisTempalte来操作Hash数据类型
*/
public User selectByUserId(String id) {
// String key = "User:"+id;
// HashOperations<H, HK, HV> H:表示user;HK表示id;HV表示存储的User对象
// HashOperations<String, Object, Object> hashOperations = redisTemplate.opsForHash();
if (!hashOperations.hasKey("User", id)) {
// 查询MySQL 不存在
log.info("查询的是MySQL数据库...");
User user = new User();
user.setId("1003");
user.setName("于文文");
user.setRemark("是一个勤奋的好孩子...");
hashOperations.put("User", id, user);
return user;
} else {
log.info("查询的是Redis数据库...");
User result = (User) hashOperations.get("User", id);
return result;
}
}
}
注意:在这里我使用了成员变量的方式来定义操作Redis的方法。
三、案例 :限制登录功能
需求描述:
用户在2分钟内,仅允许输入错误密码五次。
如果超过五次,限制其登陆一小时(要求每登陆失败时,都要给出响应的提示)
3.1 目的:防止暴力破解
明确:只要你发现有一个网站,没有验证码,我们就可以通过计算机的方式暴力破解
验证码:
3.2 思路
- 比如希望达到的要求是这样: 在 2min 内登陆异常次数达到5次, 锁定该用户 1h
- 那么登陆请求的参数中, 会有一个参数唯一标识一个 user, 比如 邮箱/手机号/userName
- 用这个参数作为key存入redis, 对应的value为登陆错误的次数, string 类型, 并设置过期时间为 2min. 当获取到的 value == “4” , 说明当前请求为第 5 次登陆异常, 锁定.
- 所谓的锁定, 就是将对应的value设置为某个标识符, 比如"lock", 并设置过期时间为 1h