Redis进阶(八)—— Spring Boot整合

8 篇文章 0 订阅
5 篇文章 0 订阅

1、常用的 Redis 客户端介绍

在 Spring Boot 2.x 之后,对Redis连接的支持,默认采用了 lettuce。

​ Jedis api 在线网址:http://tool.oschina.net/uploads/apidocs/redis/clients/jedis/Jedis.html

​ lettuce 官网地址:https://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 中redis使用 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 数据类型

  1. 原始配置文件得出结果(RedisDesktopManager显示的):
["com.xiaojian.pojo.User",{"id":"1103","name":"修心","age":22}]
  1. **** 改 1 ****
{"id":"1105","name":"修心","age":22}
  1. **** 改 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小时。(要求每登录失败时,都要给相应提示)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值