Redis进阶(九)—— Spring Boot 整合
这是我参与8月更文挑战的第22天,活动详情查看:8月更文挑战
1、常用的 Redis 客户端介绍
在 Spring Boot 2.x 之后,对Redis连接的支持,默认采用了 lettuce。
Jedis api 在线网址:tool.oschina.net/uploads/api…
lettuce 官网地址:lettuce.io
概念:
Jedis:是老牌的Redis的Java实现客户端,提供了比较全面的Redis命令的支持;
Redisson:实现了分布式的可扩展的Java数据结构;
Lettuce:高级Redis客户端,用于线程安全同步,异步和响应使用,支持集群,Sentinel,管道和编码器。
优点:
Jedis:比较全面的提供了Redis的操作特性;
Redisson:促使使用者对Redis的关注分离,提供很多分布式相关操作服务,例如,分布式锁,分布式集合,可通过Redis支持延迟队列;
Lettuce:基于Netty框架的时间驱动的通信层,其方法调用是异步的,Lettuce的API是线程安全的,所以可以操作单个Lettuce连接来完成各种操作。
2、Spring Boot 整合 Jedis
我们在使用Spring Boot搭建微服务的时候,在很多时候还是需要 redis 的高速缓存来缓存一些数据,存储一些高频率访问的数据,如果直接使用redis的话又比较麻烦,这里使用jedis来实现redis缓存来达到高效缓存的目的。
2.1 引入 Jedis 依赖
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>
2.2 配置 application.yml
spring:
redis:
port: 6379
password: 2436
host: 112.124.1.187
jedis:
pool:
max-idle: 6 # 连接池最大空闲连接数,默认 8
max-active: 10 # 连接池最大连接数(使用负值便是没有限制),默认 8
min-idle: 2 # 连接池最小空闲连接数,默认 0
timeout: 2000
Spring Boot 没有整合 Jedis,所以需要自己写配置类,配置 JedisPool
2.3 编写Config
@Configuration
public class JedisConfig {
private Logger logger = LoggerFactory.getLogger(JedisConfig.class);
@Value("${spring.redis.port}")
private Integer port;
@Value("${spring.redis.host}")
private String host;
@Value("${spring.redis.password}")
private String password;
@Value("${spring.redis.jedis.pool.max-idle}")
private Integer maxIdle;
@Value("${spring.redis.jedis.pool.max-active}")
private Integer maxActive;
@Value("${spring.redis.jedis.pool.min-idle}")
private Integer minIdle;
@Value("${spring.redis.timeout}")
private Integer timeout;
@Bean
public JedisPool jedisPool(){
JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
jedisPoolConfig.setMaxIdle(maxIdle);
jedisPoolConfig.setMinIdle(minIdle);
jedisPoolConfig.setMaxTotal(maxActive);
JedisPool jedisPool = new JedisPool(jedisPoolConfig,host,port,timeout,password);
logger.info("Jedis连接成功:" + host + ":" + port);
return jedisPool;
}
}
2.4 测试1: String 类型
需求:用户输入一个key 先判断Redis中是否存在该数据 如果存在,在Redis中进行查询,并返回 如果不存在,在MySQL数据库查询,将结果赋给Redis,并返回
// UserService.java
public interface UserService {
/**
* 需求:用户输入一个key
* 先判断Redis中是否存在该数据
* 如果存在,在Redis中进行查询,并返回
* 如果不存在,在MySQL数据库查询,将结果赋给Redis,并返回
*
*/
public String getString(String key);
}
=======================
// UserServiceImpl.java
@Service
@Log // 相当于 Logger logger = LoggerFactory.getLogger(JedisConfig.class);
public class UserServiceImpl implements UserService {
@Autowired
JedisPool jedisPool;
@Override
public String getString(String key) {
String value = "";
// 1.得到Jedis对象
Jedis jedis = jedisPool.getResource();
// 2.判断该key在redis中是否存在
if(jedis.exists(key)){
// 2.1 Redis中存在,
value = jedis.get(key);
log.info("查询Redis中的数据!");
} else{
// 2.2 Redis中不存在,从数据库中查询,并存入Redis中
value = "MySQL中的数据";
log.info("查询MySQL中的数据: " + value);
jedis.set(key,value);
}
// 3. 关闭Jedis连接
jedis.close();
return value;
}
}
=======================
// UserController.java
@Controller
public class UserController {
@Autowired
UserService userService;
@RequestMapping("/getString")
@ResponseBody
public String getString(String key){
return userService.getString(key);
}
}
2.5 工具类
// JedisUtil.java
@Component
public class JedisUtil {
@Autowired
private JedisPool jedisPool;
/**
* 获取Jedis资源
* @return
*/
public Jedis getJedis(){
return jedisPool.getResource();
}
/**
* 释放Jedis连接
*/
public void close(Jedis jedis){
if(jedis!=null){
jedis.close();
}
}
......
}
2.6 测试2 :String类型
需求:用户输入一个redis数据,该key的有效期为 30 秒
// UserService.java
public interface UserService {
/**
* 测试String类型
* 需求:用户输入一个redis数据,该key的有效期为 30 秒
*/
public String expireStr(String key,String value);
}
=======================
// UserServiceImpl.java
@Service
@Log // 相当于 Logger logger = LoggerFactory.getLogger(JedisConfig.class);
public class UserServiceImpl implements UserService {
@Autowired
JedisPool jedisPool;
@Autowired
JedisUtil jedisUtil;
/**
* 测试String类型
* 需求:用户输入一个redis数据,该key的有效期为 30 秒
*/
public String expireStr(String key,String value){
Jedis jedis = jedisUtil.getJedis();
if(!jedis.exists(key)){
// 1.在Redis中存入数据
jedis.set(key,value);
// 2.设置该值过期时间
jedis.expire(key,30);
log.info("将" + key + "有效时间设置为:30秒。");
}
// 3.查询key的有效时间
Long time = jedis.ttl(key);
jedisUtil.close(jedis);
return "该" + key + " : " + value + "的有效时间剩余: " + time;
}
}
=======================
@RestController
public class UserController {
@Autowired
UserService userService;
@RequestMapping("/expireStr")
public String expireStr(String key,String value){
return userService.expireStr(key,value);
}
}
2.7 测试3 :Hash类型
需求:根据用户 ID 查询用户信息
先判断是否在 Redis 中存在:
如果存在,直接从 Redis 中取出;
如果不存在,从 MySQL中取出,并存入 Redis 中
// User.java
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User implements Serializable {
private String id;
private String name;
private Integer age;
}
=======================
public interface UserService {
/**
* 测试Hash类型
* 需求:根据用户ID查询用户信息
* 先判断是否在Redis中存在:
* 如果存在,直接从Redis中取出
* 如果不存在,从MySQL中取出,并存入Redis中
*/
public User findById(String id);
}
=======================
/* Hash 测试*/
@Service
@Log // 相当于 Logger logger = LoggerFactory.getLogger(JedisConfig.class);
public class UserServiceImpl implements UserService {
@Autowired
JedisPool jedisPool;
@Autowired
JedisUtil jedisUtil;
@Override
public User findById(String id) {
String key = "user:" + id; // 实体类名称:id
Jedis jedis = jedisUtil.getJedis();
User user = null;
if(jedis.exists(key)){ // 存在
user = new User();
Map<String, String> map = jedis.hgetAll(key);
user.setId(map.get("id"));
user.setName(map.get("name"));
user.setAge(Integer.parseInt(map.get("age")));
log.info("===================》从Redis中查询数据");
} else{ // 不存在
// 从MySQL中查询数据
user = new User(id,"xiaojian",22);
log.info("===================》从MySQL中查询数据" + user);
// 存入Redis
Map<String, String> map = new HashMap<>();
map.put("id",user.getId());
map.put("name",user.getName());
map.put("age",user.getAge()+"");
jedis.hmset(key,map);
log.info("===================》存入Redis中");
}
jedisUtil.close(jedis);
return user;
}
}
=======================
@RestController
public class UserController {
@Autowired
UserService userService;
@RequestMapping("/findById")
public String findById(String id){
return userService.findById(id).toString();
}
}
3、Spring Boot 2.x 整合 lettuce
Lettuce:高级Redis客户端,用于线程安全同步,异步和响应使用,支持集群,Sentinel,管道和编码器。
基于Netty框架的时间驱动的通信层,其方法调用是异步的,Lettuce的API是线程安全的,所以可以操作单个Lettuce连接来完成各种操作。
3.1 导入依赖
<!-- 默认是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>
3.2 配置文件
spring:
redis:
port: 6379
password: 2436
host: 112.124.1.187
lettuce:
pool:
max-active: 10
max-idle: 6
min-idle: 2
max-wait: 1000
shutdown-timeout: 100
3.3 配置类
// 添加使用RedisTemplate模板,不书写,使用Spring Boot 默认
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String,Object> redisTemplate(LettuceConnectionFactory factory){
RedisTemplate<String,Object> template = new RedisTemplate();
template.setConnectionFactory(factory);
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;
}
}
3.3.1 配置类问题
@Bean
public RedisTemplate<String,Object> redisTemplate(LettuceConnectionFactory factory){
RedisTemplate<String,Object> template = new RedisTemplate();
template.setConnectionFactory(factory);
// ****** 改2 ******
GenericJackson2JsonRedisSerializer genericJackson2JsonRedisSerializer = new GenericJackson2JsonRedisSerializer();
// Jackson 格式
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
// 方法过期,改 1 时注释掉这里,正常 或 改 2 时使用
// om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
// ****** 改1 ******,其他情况下注释掉
om.activateDefaultTyping(LaissezFaireSubTypeValidator.instance);
jackson2JsonRedisSerializer.setObjectMapper(om);
// String 类型格式
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
// 在使用注解@Bean返回RedisTemplate的时候,同时配置hashKey与hashValue的序列化方式
// key 采用String的序列化方式
template.setKeySerializer(stringRedisSerializer);
// value 采用jackson的序列化方式,使用 ****** 改 2 ****** 对象
template.setValueSerializer(genericJackson2JsonRedisSerializer);
// hash 的key 也采用String的序列化方式
template.setHashKeySerializer(stringRedisSerializer);
// hash 的value采用jackson的序列化方式,使用 ****** 改 2 ****** 对象
template.setHashValueSerializer(genericJackson2JsonRedisSerializer);
template.afterPropertiesSet();
return template;
}
hash 数据类型
- 原始配置文件得出结果(RedisDesktopManager显示的):
["com.xiaojian.pojo.User",{"id":"1103","name":"修心","age":22}]
- 改 1
{"id":"1105","name":"修心","age":22}
- 改 2
{"@class":"com.xiaojian.pojo.User","id":"1106","name":"修心","age":22}
3.4 测试
========
@Service
@Slf4j
public class UserServiceImpl {
@Autowired
private RedisTemplate<String,Object> redisTemplate;
public String getString(){
System.out.println(redisTemplate);
log.info("RedisTemplate--------->测试");
return null;
}
}
==========
@SpringBootTest
class BootLettuceApplicationTests {
@Autowired
private UserServiceImpl userService;
@Test
void contextLoads() {
userService.getString();
}
}
PS: linux中查询到的中文以十六进制显示,可以通过在 redis-cli 后加 --raw,登录客户端
[root@xiaojian bin]# ./redis-cli -a 2436 --raw
3.5 测试1:String 类型
需求:用户输入一个key 先判断Redis中是否存在该数据 如果存在,在Redis中进行查询,并返回 如果不存在,在MySQL数据库查询,将结果赋给Redis,并返回
@Service
@Slf4j
public class UserServiceImpl {
@Autowired
private RedisTemplate<String,Object> redisTemplate;
/**
* Lettuce --> RedisTemplate 进一步的封装
* RedisTemplate 的方法和命令不一样
*
* Redis String 类型
* 需求:用户输入一个key
* 先判断Redis中是否存在该数据
* 如果存在,在Redis中进行查询,并返回
* 如果不存在,在MySQL数据库查询,将结果赋给Redis,并返回
*
* @return
*/
public String getString(String key){
String val = "";
if(redisTemplate.hasKey(key)){ // exist
val = (String) redisTemplate.opsForValue().get(key);
log.info("-----> 从Redis中查询出数据:" + val);
} else{
val = "MYSQL中查询出来的数据";
log.info("-----> 从MySQL中查询出的数据:" + val);
redisTemplate.opsForValue().set(key,val);
log.info("-----> 把从MySQL中查询出来的数据存入Redis");
}
return val;
}
}
=======================
@SpringBootTest
class BootLettuceApplicationTests {
@Autowired
private UserServiceImpl userService;
@Test
void contextLoads() {
String result = userService.getString("lettuce");
System.out.println(result);
}
}
3.6 测试2:String 类型
需求:用户输入一个redis数据,该key的有效期为 30 秒
@Service
@Slf4j
public class UserServiceImpl {
@Autowired
private RedisTemplate<String,Object> redisTemplate;
/**
* 测试 String 类型
* 需求:用户输入一个redis数据,该key的有效期为20小时
* @return
*/
public void expireStr(String key,String value){
redisTemplate.opsForValue().set(key,value);
// 定时,可以指定单位:天,时,分,秒
redisTemplate.expire(key,20, TimeUnit.HOURS);
}
}
================
@SpringBootTest
class BootLettuceApplicationTests {
@Autowired
private UserServiceImpl userService;
@Test
void t2() {
userService.expireStr("timeout","午时已到!");
}
}
3.7 测试3:Hash类型,(id必须为字符串)
需求:根据用户 ID 查询用户信息
先判断是否在 Redis 中存在:
如果存在,直接从 Redis 中取出;
如果不存在,从 MySQL 中取出,并存入 Redis 中
// 首先,在 RedisConfig 类中添加hash的序列化配置
...
// hash 的key 也采用String的序列化方式
template.setHashKeySerializer(stringRedisSerializer);
// hash 的value采用jackson的序列化方式
template.setHashValueSerializer(jackson2JsonRedisSerializer);
...
================
@Service
@Slf4j
public class UserServiceImpl {
@Autowired
private RedisTemplate<String,Object> redisTemplate;
/**
* 测试 Hash
* @param id
* @return
*
* 根据 Id 查询用户对象信息
* 先判断Redis中是否存在该key
* 如果不存在,查询MySQL 数据库,并将结果添加到 Redis 中,并返回
* 如果存在,直接将结果在Redis查询并返回
*/
public User findById(String id){
User user = null;
if(redisTemplate.opsForHash().hasKey("user",id)){
log.info("----->从 Redis 中取出数据!");
user = (User)redisTemplate.opsForHash().get("user",id);
} else{
// 从 MySQL 中取出数据
user = new User();
user.setId(id);
user.setName("修心");
user.setAge(22);
log.info("----->从 MySQL 中取出数据");
/**
@ param h 用户实体,user
@ param hk 用户主键
@ param hv 整个对象
*/
redisTemplate.opsForHash().put("user",id,user);
log.info("----->将 map 数据存入 Redis中");
}
return user;
}
}
========================
@SpringBootTest
class BootLettuceApplicationTests {
@Autowired
private UserServiceImpl userService;
@Test
void t3() {
User user = userService.findById("1143");
System.out.println(user);
}
}
PS: 问题
问题1:出现了许多相同的字符串 ---- > 提取出来
解1:工具类
解2:实体Bean声明一个返回该本类字符串的方法
问题2:强制类型转换问题 以及 重复书写很长一段 redisTemplate.opsForHash()
解:在业务类上方声明一下变量,用变量名替换 redisTemplate.opsForHash()
@Resource(name = "redisTemplate")
private ValueOperations<String, String> redisString;
@Resource(name = "redisTemplate")
private HashOperations<String, String, User> redisHash; // K:"user"; HK:"ID"; HV: Object
4、Redis 常见应用
4.1 手机验证功能
需求:
用户在客户端输入手机号,点击发送后随即生成四位数字码,有效期60秒
输入验证码,点击验证,返回成功或者失效,且每个手机号在5分钟内只能验证3次。并给相应信息提示
4.2 限制登录功能
需求:
用户在2分钟内,仅允许输入错误密码5次;
如果超过次数,限制其登录1小时。(要求每登录失败时,都要给相应提示)
转载自:https://juejin.cn/post/6999216009596911623