天天看點

瑞吉外賣之 redis優化緩存

前言

👏作者簡介:我是笑霸final,一名熱愛技術的在校學生。

📝個人首頁:​​個人首頁1​​​ || 笑霸final的首頁2

📕系列專欄::本文寫在java專欄

📧如果文章知識點有錯誤的地方,請指正!和大家一起學習,一起進步👀

🔥如果感覺部落客的文章還不錯的話,👍點贊👍 + 👀關注👀 + 🤏收藏🤏

🐉擷取代碼 通路gitee:​​gitee連結​​

文章目錄

  • ​​一、環境搭建​​
  • ​​二、緩存驗證碼​​
  • ​​三、緩存菜品資料​​
  • ​​四、用springCache 優化 套餐資料​​
  • ​​springCache的使用步驟​​
  • ​​緩存套餐資料​​

一、環境搭建

  • 加入maven坐标
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-data-redis -->
<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>      
  • 配置檔案
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      
  • 配置類(不是必須的)

    用來設定序列化的

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;
    }

}      

二、緩存驗證碼

思路:
  • 在服務端​

    ​UserController​

    ​ 中注入 RedisTemplate對象,用來操作redis
@Autowired
    private RedisTemplate redisTemplate;      
  • 在服務端​

    ​UserController​

    ​ 的sendMsg方法中 講随機生成的驗證碼緩存到redis中,并設定有效期5分鐘。
//将生成的驗證碼緩存到redis中,并設定有效期5分鐘
                redisTemplate.opsForValue().set(phone,code,5, TimeUnit.MINUTES);      
  • 在服務端​

    ​UserController​

    ​的login方法中,從redis擷取驗證碼。登陸成功就删除redis中的驗證碼
//從redis中擷取緩存的驗證碼
        Object codeInsession = redisTemplate.opsForValue().get(phone);      
//如果登陸成功就删除驗證碼
            redisTemplate.delete(phone);      
  • 代碼
@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<String> 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("驗證碼和手機不比對");
    }
}      

三、緩存菜品資料

思路
  • 當然 還是要中注入​

    ​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);
    }      
注意 :使用緩存的過程中要保證資料庫和redis中的資料一緻。當資料庫發生變化時,需要及時清理緩存資料,在​

​save和update方法​

​添加如下代碼
  • 清理所有菜品的緩存
//清理所有菜品的緩存
       final Set keys = redisTemplate.keys("dish_*");
       redisTemplate.delete(keys);      
  • 精确清理
//精确清理
        String key="dish"+dishDto.getCategoryId()+"_"+dishDto.getStatus();
        redisTemplate.delete(key);      

四、用springCache 優化 套餐資料

springCache的使用步驟

  • 第一步:導入springCache的maven坐标
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-cache -->
<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-cache</artifactId>
   <version>2.7.0</version>
</dependency>      
  • 第二步:配置yml檔案
spring:
  cache:
    redis:
      time-to-live: 1800000 #設定緩存的有效期
    type:      
  • 第三步:在啟動類上加入​

    ​@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是互斥的

緩存套餐資料

public class R<T> implements Serializable      
  • 在​

    ​SetmealController​

    ​​的list方法上加入​

    ​@Cacheable​

  • 在​

    ​SetmealController​

    ​​的save和delete、saveUpdate、stop方法上加入​

    ​@CacheEvict​

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