Redis【8】-SpringBoot2.x中redis使用(lettuce)

Jedis 的封装 --- RedisTemplate
Jedis 的封装 --- Jedis 工具类 -- JedisUtil
java 代码操作 Redis ,需要使用 Jedis ,也就是 redis 支持 java 的第三方类库
注意 :Jedis2.7 以上的版本才支持集群操作
maven 配置
新建 SpringBoot2.0.3 WEB 工程,在 MAVEN pom.xml 文件中加入如下依赖
hibernate-->JPA-->SpringData
<dependencies>
<!--默认是lettuce客户端-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- redis依赖commons-pool 这个依赖一定要添加 -->
<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> 

配置文件配置 application.yml

spring :
redis :
port : 6379
password : guoweixin
host : 192.168.20.135
lettuce :
pool :
max-active : 8 # 连接池最大连接数(使用负值表示没有限制)
max-idle : 8 # 连接池中的最大空闲连接
min-idle : 0 # 连接池中的最小空闲连接
max-wait : 1000 # 连接池最大阻塞等待时间(使用负值表示没有限制)
shutdown-timeout : 100 # 关闭超时时间
redis 配置类
JdbcTemplate-->JDBC 进一步封装。
RedisTemplate-->redis 进行了进一步封装 (
lettuce)
简介
编写缓存配置类 RedisConfig 用于调优缓存默认配置, RedisTemplate<String, Object> 的类型兼容性更
大家可以看到在 redisTemplate() 这个方法中用 JacksonJsonRedisSerializer 更换掉了 Redis 默认的序列化方式: JdkSerializationRedisSerializer
spring-data-redis 中序列化类有以下几个:
GenericToStringSerializer :可以将任何对象泛化为字符创并序列化 Jackson2JsonRedisSerializer :序列化 Object 对象为 json 字符创(与 JacksonJsonRedisSerializer 相同)
JdkSerializationRedisSerializer :序列化 java 对象 StringRedisSerializer :简单的字符串序列化
JdkSerializationRedisSerializer 序列化被序列化对象必须实现 Serializable 接口,被序列化除属性内容
还有其他内容,长度长且不易阅读 , 默认就是采用这种序列化方式
存储内容如下:
"\xac\xed\x00\x05sr\x00!com.oreilly.springdata.redis.User\xb1\x1c
\n\xcd\xed%\xd8\x02\x00\x02I\x00\x03ageL\x00\buserNamet\x00\x12Ljava/lang/String;xp\x00\x00\x00
\x14t\x00\x05user1"
JacksonJsonRedisSerializer 序列化被序列化对象不需要实现 Serializable 接口,被序列化的结果清
晰,容易阅读,而且存储字节少,速度快存储内容如下:
"{"userName":"guoweixin","age":20}"
StringRedisSerializer 序列化 :一般如果 key value 都是 string 字符串的话,就是用这个就可以了
RedisConfig
package com.qfjy.config;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.cache.RedisCacheWriter;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import java.lang.reflect.Method;
/**
* @ClassName RedisConfig
* @Description TODO
* @Author guoweixin
* @Version 1.0
*/
@Configuration
public class RedisConfig extends CachingConfigurerSupport {
/**
* 自定义缓存key的生成策略。默认的生成策略是看不懂的(乱码内容) 通过Spring 的依赖注入特性进行自定
义的配置注入并且此类是一个配置类可以更多程度的自定义配置
*
* @return
*/
@Bean
@Override
public KeyGenerator keyGenerator() {
return new KeyGenerator() {
@Override
public Object generate(Object target, Method method, Object... params) {
StringBuilder sb = new StringBuilder();
sb.append(target.getClass().getName());
sb.append(method.getName());
for (Object obj : params) {
sb.append(obj.toString());
}
return sb.toString();
}
};
}
/**
* 缓存配置管理器
*/
@Bean
public CacheManager cacheManager(LettuceConnectionFactory factory) {
//以锁写入的方式创建RedisCacheWriter对象
RedisCacheWriter writer = RedisCacheWriter.lockingRedisCacheWriter(factory);
//创建默认缓存配置对象
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig();
RedisCacheManager cacheManager = new RedisCacheManager(writer, config);
return cacheManager;
}
@Bean
public RedisTemplate<String,Object> redisTemplate(LettuceConnectionFactory
factory){
RedisTemplate<String,Object> template = new RedisTemplate <>();
template.setConnectionFactory(factory); 

代码示例

RedisServiceImpl
测试 String 类型
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new
Jackson2JsonRedisSerializer(Object.class);
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
// 在使用注解@Bean返回RedisTemplate的时候,同时配置hashKey与hashValue的序列化方式。
// key采用String的序列化方式
template.setKeySerializer(stringRedisSerializer);
// value序列化方式采用jackson
template.setValueSerializer(jackson2JsonRedisSerializer);
// hash的key也采用String的序列化方式
template.setHashKeySerializer(stringRedisSerializer);
// hash的value序列化方式采用jackson
template.setHashValueSerializer(jackson2JsonRedisSerializer);
template.afterPropertiesSet();
return template;
}
}
@Service
@Log
public class RedisServiceImpl {
@Autowired
private RedisTemplate<String,Object> redisTemplate;
/**
* 普通缓存放入
* @param key 键
* @return true成功 false失败
*/
public String getString(String key) {
if(redisTemplate.hasKey(key)) {
log.info("Redis中查询");
return (String) redisTemplate.opsForValue().get(key);
}else{
String val="guoweixin";
redisTemplate.opsForValue().set(key, val);
log.info("数据库中查询的");
return val;
}
} 

测试Hash类型

* 普通缓存放入
* @param key 键
* @param value 值
* @param expireTime 超时时间(秒)
* @return true成功 false失败
*/
public Boolean set(String key, Object value, int expireTime) {
try {
redisTemplate.opsForValue().set(key, value, expireTime, TimeUnit.SECONDS);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
}
@Service
@Log
public class RedisServiceImpl {
@Autowired
private RedisTemplate<String,Object> redisTemplate;
@Resource(name="redisTemplate")
private HashOperations<String,String,User> hash;
/**
* 判断key是否存在,如果存在 在Redis中查询
* 如果不存在,在MYSQL中查询,并将结果得到,添加到Redis Hash中
* @param id
* @return
*/
public User selectUserById1(String id){
if(hash.hasKey("user",id)){
log.info("Redis中查询对象");
return hash.get("user",id);
}else{
User u=new User();
u.setId(id);
u.setName("guoweixin");
u.setAge(22);
log.info("mysql中查询对象");
hash.put("user",id,u);
return u;
}
} 
hash 类型代码示例
package com.qfjy.redis.demo.service.impl;
import com.qfjy.redis.demo.service.HashCacheService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.Cursor;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ScanOptions;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
@Service("hashCacheService")
public class HashCacheServiceImpl implements HashCacheService {
private final static Logger log =
LoggerFactory.getLogger(HashCacheServiceImpl.class);
@Autowired
private RedisTemplate<String, Object> redisTemplate;
/**
* 获取MAP中的某个值
* @param key 键
* @param item 项
* @return 值
*/
public Object hget(String key, String item) {
return redisTemplate.opsForHash().get(key, item);
}
/**
* 获取hashKey对应的所有键值
* @param key 键
* @return 对应的多个键值
*/
public Map <Object, Object> hmget(String key) {
return redisTemplate.opsForHash().entries(key);
}
/**
* 以map集合的形式添加键值对
* @param key 键
* @param map 对应多个键值
* @return true 成功 false 失败
*/
public boolean hmset(String key, Map <String, Object> map) {
try {
redisTemplate.opsForHash().putAll(key, map);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* HashSet 并设置时间
* @param key 键
* @param map 对应多个键值
* @param time 时间(秒)
* @return true成功 false失败
*/
public boolean hmset(String key, Map <String, Object> map, long time) {
try {
redisTemplate.opsForHash().putAll(key, map);
if (time > 0) {
expire(key, time);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 向一张hash表中放入数据,如果不存在将创建
* @param key 键
* @param item 项
* @param value 值
* @return true 成功 false失败
*/
public boolean hset(String key, String item, Object value) {
try {
redisTemplate.opsForHash().put(key, item, value);
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 向一张hash表中放入数据,如果不存在将创建
* @param key 键
* @param item 项
* @param value 值
* @param time 时间(秒) 注意:如果已存在的hash表有时间,这里将会替换原有的时间
* @return true 成功 false失败
*/
public boolean hset(String key, String item, Object value, long time) {
try {
redisTemplate.opsForHash().put(key, item, value);
if (time > 0) {
expire(key, time);
}
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
/**
* 删除hash表中的值
* @param key 键 不能为null
* @param item 项 可以使多个 不能为null
*/
public void hdel(String key, Object... item) {
redisTemplate.opsForHash().delete(key, item);
}
/**
* 判断hash表中是否有该项的值
* @param key 键 不能为null
* @param item 项 不能为null
* @return true 存在 false不存在
*/
public boolean hHasKey(String key, String item) {
return redisTemplate.opsForHash().hasKey(key, item);
}
/**
* hash递增 如果不存在,就会创建一个 并把新增后的值返回
* @param key 键
* @param item 项
* @param by 要增加几(大于0)
* @return
*/
public long hincr(String key, String item, long by) {
return redisTemplate.opsForHash().increment(key, item, by);
}
/**
* hash递减
* @param key 键
* @param item 项
* @param by 要减少记(小于0)
* @return
*/
public long hdecr(String key, String item, long by) {
return redisTemplate.opsForHash().increment(key, item, -by);
}
/**
* 获取指定变量中的hashMap值。
* @param key
* @return 返回LIST对象
*/
@Override
public List<Object> values(String key) {
return redisTemplate.opsForHash().values(key);
}
/**
* 获取变量中的键。
* @param key
* @return 返回SET集合
*/
@Override
public Set<Object> keys(String key) {
return redisTemplate.opsForHash().keys(key);
}

* 获取变量的长度。
* @param key 键
* @return 返回长度
*/
@Override
public long size(String key) {
return redisTemplate.opsForHash().size(key);
}
/**
* 以集合的方式获取变量中的值。
* @param key
* @param list
* @return 返回LIST集合值
*/
@Override
public List multiGet(String key, List list) {
return redisTemplate.opsForHash().multiGet(key,list);
}
/**
* 如果变量值存在,在变量中可以添加不存在的的键值对
* 如果变量不存在,则新增一个变量,同时将键值对添加到该变量。
* @param key
* @param hashKey
* @param value
*/
@Override
public void putIfAbsent(String key, String hashKey, Object value) {
redisTemplate.opsForHash().putIfAbsent(key,hashKey,value);
}
/**
* 匹配获取键值对,ScanOptions.NONE为获取全部键对,
ScanOptions.scanOptions().match("map1").build()
* 匹配获取键位map1的键值对,不能模糊匹配。
* @param key
* @param options
* @return
*/
@Override
public Cursor<Map.Entry<Object, Object>> scan(String key, ScanOptions options) {
return redisTemplate.opsForHash().scan(key,options);
}
/**
* 删除变量中的键值对,可以传入多个参数,删除多个键值对。
* @param key 键
* @param hashKeys MAP中的KEY
*/
@Override
public void delete(String key, String... hashKeys) {
redisTemplate.opsForHash().delete(key,hashKeys);
}
public boolean expire(String key, long seconds) {
@Override
public void delete(String key, String... hashKeys) {
redisTemplate.opsForHash().delete(key,hashKeys);
}
public boolean expire(String key, long seconds) {

return redisTemplate.expire(key,seconds,TimeUnit.SECONDS);
}
/**
* 删除
* @param keys
*/
@Override
public void del(String... keys) {
if (keys != null && keys.length > 0) {
if (keys.length == 1) {
redisTemplate.delete(keys[0]);
} else {
redisTemplate.delete(CollectionUtils.arrayToList(keys));
}
}
}
@Override
public long getExpire(String key) {
return 0;
}
}

六 作业
案例 1 :手机验证功能
需求描述:

 

用户在客户端输入手机号,点击发送后随机生成 4 位数字码。有效期为 60 秒。
输入验证码,点击验证,返回成功或者失败。
且每个 IP 地址。在 5 分钟内只能验证 3 次。并给相应信息提示。锁定这个 IP 12 小时。
流程:
后台 Redis 生成一个 KEY value: 验证码 4 位数字码。 (过期时间 60 秒) expire
前端用户输入的验证码和后台生成的 Redis key 验证码进行比较,
如果不相等: 验证码校验失败。
如果相等:恭喜你,注册成功。
防攻击:
Redis key: IP 地址 1 expire 5 分钟。 将这个 IP 地址存入一个 KEY
lock_ip 地址

案例2:限制登录功能

 

需求描述:
代码参考:
需求详细分析逻辑:
业务 Service 层:
用户在 5 分钟内,仅允许输入错误密码 5 次。
如果超过次数,限制其登录 20 分钟。(要求每次登录失败时,都要给相应提式)

案例3:防止重复提交

什么是幂等
答:多次运算结果一致。
在我们编程中常见幂等
select 查询天然幂等
delete 删除也是幂等 , 删除同一个多次效果一样
update 直接更新某个值的 , 幂等
update 更新累加操作的 , 非幂等
insert 非幂等操作 , 每次新增一条
一、有很多的应用场景都会遇到重复提交问题,
比如:
1 、点击提交按钮两次。
2 、点击刷新按钮。
3 、使用浏览器后退按钮重复之前的操作,导致重复提交表 单。
4 、使用浏览器历史记录重复提交表单。
5 、浏览器重复的 HTTP 请求。
利用 Redis 解决用户重复提交问题。
解决方案
前端 js 提交禁止按钮可以用一些 js 组件
使用 Post/Redirect/Get 模式
在提交后执行页面重定向,这就是所谓的 Post-Redirect-Get (PRG) 模式。简言之,当用户提交了表单后,你去执行一个客户端的重定向,转到提交成功信息页面。这能避免用户按 F5 导致的重复提交,而其也不会出现浏览器表单重复提交的警告,也能消除按浏览器前进和后退按导致的同样问题。
session 中存放一个特殊标志
在服务器端,生成一个唯一的标识符,将它存入 session ,同时将它写入表单的隐藏字段中,然后将表单 页面发给浏览器,用户录入信息后点击提交,在服务器端,获取表单中隐藏字段的值,与 session 中的唯 一标识符比较,相等说明是首次提交,就处理本次请求,然后将 session 中的唯一标识符移除;不相等说 明是重复提交,就不再处理。
借助数据库
insert 使用唯一索引 update 使用 乐观锁 version 版本法
这种在大数据量和高并发下效率依赖数据库硬件能力 , 可针对非核心业务
借助悲观锁
使用 select … for update , 这种和 synchronized 锁住先查再 insert or update 一样 , 但要避免死锁 , 效率较差
Redis 实现防止重复提交
session 中存放一个特殊标志
在服务器端,生成一个唯一的标识符,将它存入 session ,同时将它写入表单的隐藏字段中,然后 将表单页面发给浏览器,用户录入信息后点击提交,在服务器端,获取表单中隐藏字段的值,与 session 中的唯一标识符比较,相等说明是首次提交,就处理本次请求,然后将 session 中的唯一标 识符移除;不相等说明是重复提交,就不再处理。
Redis 生成对应 KEY (并设置过期时间),页面前端传入生成的值写入表单隐藏字段中,
在服务器端进行校验,如果相等说明首次提交,处理本次请求, Redis 移除该 KEY ,不相等说明重复,就不在处理。

这个错误提示表明在序列化过程找不到io.lettuce.core.resource.DefaultClientResources的序列化器。这可能是因为DefaultClientResources类没有默认的无参构造函数或者缺少相关的Jackson序列化注解。 要解决这个问题,你可以尝试以下几种方法: 1. 确保DefaultClientResources类具有默认的无参构造函数。如果没有,可以添加一个无参构造函数: ```java public DefaultClientResources() { // 构造函数逻辑 } ``` 2. 如果DefaultClientResources类有一些不需要序列化的属性,可以使用Jackson的注解`@JsonIgnore`将其排除: ```java @JsonIgnore private SomeType someProperty; ``` 3. 如果DefaultClientResources类的访问修饰符不是`public`,请确保提供了相应的getter和setter方法。 4. 如果你有权修改DefaultClientResources类的源代码,可以尝试为它添加Jackson的序列化注解,如`@JsonSerialize`或`@JsonDeserialize`。这些注解可以帮助指定自定义的序列化逻辑。 ```java @JsonSerialize(using = CustomSerializer.class) public class DefaultClientResources { // 类定义 } ``` 5. 如果你无法修改DefaultClientResources类的源代码,可以尝试在序列化过程禁用Jackson的`SerializationFeature.FAIL_ON_EMPTY_BEANS`特性。这可以通过设置ObjectMapper的相应配置来完成: ```java ObjectMapper objectMapper = new ObjectMapper(); objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false); ``` 这些方法的一种或多种可能会解决你遇到的问题。根据你的具体情况选择适合的方法。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值