• 瑞吉外卖之 redis优化缓存


    前言

    👏作者简介:我是笑霸final,一名热爱技术的在校学生。


    📝个人主页:个人主页1 || 笑霸final的主页2


    📕系列专栏::本文写在java专栏


    📧如果文章知识点有错误的地方,请指正!和大家一起学习,一起进步👀


    🔥如果感觉博主的文章还不错的话,👍点赞👍 + 👀关注👀 + 🤏收藏🤏


    🐉获取代码 访问gitee:gitee链接

    在这里插入图片描述

    一、环境搭建

    • 加入maven坐标
    
    <dependency>
       <groupId>org.springframework.bootgroupId>
       <artifactId>spring-boot-starter-data-redisartifactId>
    dependency>
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 配置文件
    spring:
      redis:
        host: **************** #ip地址
        port: 6379 #端口号
        password: ***********  #密码
        database: 0
        jedis: #连接池
          pool:
            max-active: 20  #最大连接数,负值表示没有限制,默认8
            max-wait: -1    #最大阻塞等待时间,负值表示没限制,默认-1
            max-idle: 4     #最大空闲连接,默认8
            min-idle: 0     #最小空闲连接,默认0
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 配置类(不是必须的)
      用来设置序列化的
    package com.xbfinal.reggie.config;
    
    import org.springframework.context.annotation.Bean;
    import org.springframework.data.redis.connection.RedisConnectionFactory;
    import org.springframework.data.redis.core.RedisTemplate;
    import org.springframework.data.redis.serializer.StringRedisSerializer;
    
    /**
     * @autor 笑霸fianl~
     * 欢迎访问GitHub:https://github.com/XBfinal
     * 欢迎访问Gitee:https://gitee.com/XBfianl
     * 欢迎访问CSDN:https://blog.csdn.net/weixin_52062043
     */
    public class RedisConfig {
        @Bean
        public RedisTemplate redisTemplate(RedisConnectionFactory connectionFactory) {
            RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>();
    
            //设置键的序列化方式
            redisTemplate.setKeySerializer(new StringRedisSerializer());
            //设置值的序列化方式
            redisTemplate.setValueSerializer(new StringRedisSerializer());
    
            redisTemplate.setConnectionFactory(connectionFactory);
            return redisTemplate;
        }
    
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    二、缓存验证码

    思路:

    • 在服务端UserController 中注入 RedisTemplate对象,用来操作redis
    	@Autowired
        private RedisTemplate redisTemplate;
    
    • 1
    • 2
    • 在服务端UserControllersendMsg 方法中 讲随机生成的验证码缓存到redis中,并设置有效期5分钟。
     //将生成的验证码缓存到redis中,并设置有效期5分钟
                    redisTemplate.opsForValue().set(phone,code,5, TimeUnit.MINUTES);
    
    • 1
    • 2
    • 在服务端UserControllerlogin 方法中,从redis获取验证码。登陆成功就删除redis中的验证码
    		//从redis中获取缓存的验证码
            Object codeInsession = redisTemplate.opsForValue().get(phone);
    
    • 1
    • 2
    //如果登陆成功就删除验证码
                redisTemplate.delete(phone);
    
    • 1
    • 2
    • 代码
    @RestController
    @RequestMapping("/user")
    @Slf4j
    public class Usercontroller {
    
        @Autowired
        private UserSerice userSerice;
    
        @Autowired
        private JavaMailSender mailSender;
    
        @Autowired
        private RedisTemplate redisTemplate;
    
        @Value("${spring.mail.username}")
        private String MyFrom;
    
    
        /**
         * 发送验证码
         * @param user
         * @return
         */
        @PostMapping("/sendMsg")
        public R<String> sendMsg(@RequestBody User user,
                                 HttpSession session){
            log.info("R sendMsg()进来了");
            //获取手机号
            final String phone = user.getPhone();//就不修改实体类了把邮件写进 Phone
    
            //判断手机号不为空
            if(StringUtils.isNotEmpty(phone)) {
                //生成4位验证码
                final String code =
                        ValidateCodeUtils.generateValidateCode(4).toString();
                System.out.println("==========");
                System.out.println(code);//验证码
                System.out.println("==========");
                //***************************************************/
                //创建简单邮件消息
                SimpleMailMessage message = new SimpleMailMessage();
                message.setFrom(MyFrom);//用自己的邮件发
                //谁要接收
                message.setTo(phone);
                //邮件标题
                message.setSubject("验证码");
                //邮件内容
                message.setText("【笑霸final】你的验证码为:"+code);//
                try {
                    mailSender.send(message);
                    //需要保存一下验证码,后面用来验证
                    //session.setAttribute(phone, code);
    
                    //将生成的验证码缓存到redis中,并设置有效期5分钟
                    redisTemplate.opsForValue().set(phone,code,1, TimeUnit.MINUTES);
    
                    return R.success("发送成功");
                } catch (MailException e) {
                    e.printStackTrace();
                    return R.error("短信发送失败");
                }
            }
            return R.error("短信发送失败");
        }
    
        /**
         * 登陆
         * @param
         * @param session
         * @return
         */
        @PostMapping("login")
        public R<User> login(@RequestBody Map map,
                               HttpSession session){
    
            //获取邮箱和验证码
            final String phone = map.get("phone").toString();
            final String code = map.get("code").toString();
            //获取session的验证码并比对
            //final Object codeInsession = session.getAttribute(phone);
    
            //从redis中获取缓存的验证码
            Object codeInsession = redisTemplate.opsForValue().get(phone);
    
            if(codeInsession!=null && codeInsession.equals(code)){
                //登陆成功
                //查数据库,没有就存入数据库
                LambdaQueryWrapper<User> queryWrapper
                        =new LambdaQueryWrapper<>();
                queryWrapper.eq(User::getPhone,phone);
                 User user = userSerice.getOne(queryWrapper);
                if(user == null){
                    //新用户 入库
                     user = new User();
                     user.setPhone(phone);
                     user.setStatus(1);
                    userSerice.save(user);
                }else if(user.getStatus()==0){
                    //账号被禁用
                    return R.error("账号被禁用");
                }
                //成功存入session
                session.setAttribute("user",user.getId());
    
                //如果登陆成功就删除验证码
                redisTemplate.delete(phone);
    
                return R.success(user);
            }
            return R.error("验证码和手机不匹配");
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113

    三、缓存菜品数据

    思路

    • 当然 还是要中注入 RedisTemplate对象
    • 改造dishController的list方法,先从redis中获取菜品数据,如果有则直接返回,无需查询数据库;如果没有数据,则查询数据库,并将结果放入redis中
    • 改造dishController的save和update方法,加入清理缓存逻辑(保证数据一致
     @GetMapping("/list")
        public R<List<DishDto>> listR(Dish dish){
            List<DishDto> dtoList=null;
            //先构造key
            String key="dish"+dish.getCategoryId()+"_"+dish.getStatus();
    
            //先从redis获取缓存数据,如果存在直接返回
            dtoList= (List<DishDto>)redisTemplate.opsForValue().get(key);
            if(dtoList!=null){
                    //说明存在
                log.info("在redis中查询的数据");
                return R.success(dtoList);
    
            }
    
    
            //查询条件对象
            LambdaQueryWrapper<Dish> lambdaQueryWrapper
                    = new LambdaQueryWrapper();
            lambdaQueryWrapper.eq(dish.getCategoryId()!=null,Dish::getCategoryId,dish.getCategoryId());
            //添加起售状态的条件
            lambdaQueryWrapper.eq(Dish::getStatus,1);
            //添加排序
            lambdaQueryWrapper.orderByAsc(Dish::getSort).orderByAsc(Dish::getUpdateTime);
    
            final List<Dish> list = dishService.list(lambdaQueryWrapper);
            dtoList  = list.stream().map((item) -> {
                DishDto dishDto = new DishDto();
    
                BeanUtils.copyProperties(item,dishDto);
    
                Long categoryId = item.getCategoryId();//分类id
                //根据id查询分类对象
                Category category = categoryService.getById(categoryId);
    
                if(category != null){
                    String categoryName = category.getName();
                    dishDto.setCategoryName(categoryName);
                }
                final Long id = item.getId();//当前菜品id
                LambdaQueryWrapper<DishFlavor>lambdaQueryWrapper1
                        =new LambdaQueryWrapper<>();
                lambdaQueryWrapper1.eq(DishFlavor::getDishId,id);
                final List<DishFlavor> dishFlavorList = dishFlavorService.list(lambdaQueryWrapper1);
                dishDto.setFlavors(dishFlavorList);
    
                return dishDto;
            }).collect(Collectors.toList());
    
            //redis不存在,则查询数据库并加入缓存,设置1小时的缓存时间
            redisTemplate.opsForValue().set(key,dtoList,1, TimeUnit.HOURS);
    
            return R.success(dtoList);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54

    注意 :使用缓存的过程中要保证数据库和redis中的数据一致。当数据库发生变化时,需要及时清理缓存数据,在save和update方法添加如下代码

    • 清理所有菜品的缓存
    //清理所有菜品的缓存
           final Set keys = redisTemplate.keys("dish_*");
           redisTemplate.delete(keys);
    
    • 1
    • 2
    • 3
    • 精确清理
     //精确清理
            String key="dish"+dishDto.getCategoryId()+"_"+dishDto.getStatus();
            redisTemplate.delete(key);
            
    
    • 1
    • 2
    • 3
    • 4

    四、用springCache 优化 套餐数据

    springCache的使用步骤

    • 第一步:导入springCache的maven坐标
    
    <dependency>
       <groupId>org.springframework.bootgroupId>
       <artifactId>spring-boot-starter-cacheartifactId>
       <version>2.7.0version>
    dependency>
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 第二步:配置yml文件
    spring:
      cache:
        redis:
          time-to-live: 1800000 #设置缓存的有效期
        type: redis #缓存类型redis
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 第三步:在启动类上加入@EnableCaching注解,开启缓存功能
    • 第四步:在controller的方法上加入相对应的注解进行缓存操作

    常用注解
    @CacheEvict :应用到移除数据的方法上,如删除方法,调用方法时会从缓存中移除相应的数据
    @Cacheable:应用到读取数据的方法上,即可缓存的方法,如查找方法,先从缓存中读取,如果没有再调用相应方法获取数据,然后把数据添加到缓存中
    @CachePut :应用到写数据的方法上,如新增/修改方法,调用方法时会自动把相应的数据放入缓存

    Cacheable的参数如下(其他的注解也大同小异)

    • value、cacheNames:两个等同的参数(cacheNames为Spring 4新增,作为value的别名),用于指定缓存存储的集合名。由于Spring 4中新增了@CacheConfig,因此在Spring 3中原本必须有的value属性,也成为非必需项了
    • key:缓存对象存储在Map集合中的key值,非必需,缺省按照函数的所有参数组合作为key值,若自己配置需使用SpEL表达式,比如:@Cacheable(key = “#p0”):使用函数第一个参数作为缓存的key值,更多关于SpEL表达式的详细内容可参考官方文档
    • condition:缓存对象的条件,非必需,也需使用SpEL表达式,只有满足表达式条件的内容才会被缓存,比如:@Cacheable(key = “#p0”, condition = “#p0.length() < 3”),表示只有当第一个参数的长度小于3的时候才会被缓存。
    • unless:另外一个缓存条件参数,非必需,需使用SpEL表达式。它不同于condition参数的地方在于它的判断时机,该条件是在函数被调用之后才做判断的,所以它可以通过对result进行判断。
    • keyGenerator:用于指定key生成器,非必需。若需要指定一个自定义的key生成器,我们需要去实现org.springframework.cache.interceptor.KeyGenerator接口,并使用该参数来指定。需要注意的是:该参数与key是互斥的

    缓存套餐数据

    思路:
    注意返回类型为R的要实现序列化接口Serializable

    public class R<T> implements Serializable
    
    • 1
    • SetmealController的list方法上加入@Cacheable
    • SetmealController的save和delete、saveUpdate、stop方法上加入@CacheEvict

    加入以下代码即可

      @CacheEvict(value = "setmealCache" ,allEntries = true)//allEntries = true清理setmealCache下所有的缓存
      
      @Cacheable(value = "setmealCache",key = "#setmeal.categoryId")
    
    • 1
    • 2
    • 3

    在这里插入图片描述

    是否忘记了springboot如何操作redis??
    点此跳转 《springDataRedis操作》的复习吧

  • 相关阅读:
    Java Math.sqrt()具有什么功能呢?
    【附源码】Python计算机毕业设计数字资源交流平台
    羊了个羊的模式浅薄认知
    Elasticsearch系列-安装部署
    opengl灯光基础:2.1 光照基础知识
    现学现用的 10 个 Python 技巧
    Jmeter发送webService请求并压测
    Git获取本地仓库及基本操作指令
    uboot启动流程-涉及_main汇编函数
    uniapp实现微信小程序全局可分享功能
  • 原文地址:https://blog.csdn.net/weixin_52062043/article/details/127149215