0
点赞
收藏
分享

微信扫一扫

const关键字学习补充

开源分享 2024-09-10 阅读 26

缓存菜品

问题说明: 用户端小程序展示的菜品数据都是通过查询数据库获得, 如果客户端访问量较大, 数据库访问访问压力增大,容易造成响应慢, 用户体验差

解决思路: 通过redis来缓存菜品数据, 减少数据库查询操作, 提高程序运行效率

缓存逻辑分析: 每个分类下的菜品保存一份缓存数据, 数据库中菜品数据有变更时清理缓存数据

存储形式:

  1. 把分类的id作为key, 把每个分类下的菜品信息作为value,
  2. 我们把菜品信息集合看做整体, 转成redis中的string存储即可
  3. redis中的string不同于java, java中的所有数据类型都可以转成redis中的string储存, 类似与序列化的过程

改造代码: 用户端查询菜品时, 优先使用缓存数据, 没有缓存数据就要添加缓存数据

@RestController("userDishController")
@RequestMapping("/user/dish")
@Slf4j
@Api(tags = "C端-菜品浏览接口")
public class DishController {
    @Autowired
    private DishService dishService;
    @Autowired
    private RedisTemplate redisTemplate;

    /**
     * 根据分类id查询菜品
     *
     * @param categoryId
     * @return
     */
    @GetMapping("/list")
    @ApiOperation("根据分类id查询菜品")
    public Result<List<DishVO>> list(Long categoryId) {
        // 构造redis中的key, 约定规则: dish_分类id
        String key = "dish_" + categoryId;
        // 查询redis中是否存在商品数据
        List<DishVO> list =(List<DishVO>) redisTemplate.opsForValue().get(key);
        // 如果存在, 直接返回, 无需查询数据库
        if(list != null && list.size() > 0) {
            return Result.success(list);
        }


        Dish dish = new Dish();
        dish.setCategoryId(categoryId);
        dish.setStatus(StatusConstant.ENABLE);//查询起售中的菜品
        // 如果不存在, 查询数据库, 把数据存入redis
        list = dishService.listWithFlavor(dish);
        redisTemplate.opsForValue().set(key,list);

        return Result.success(list);
    }

}

清理缓存数据: 新增菜品/修改菜品/批量删除菜品/起售停售菜品时, 要删除缓存数据, 保持数据一致性

/**
 * 菜品管理
 */
@RestController
@RequestMapping("/admin/dish")
@Api(tags = "菜品相关接口")
@Slf4j
public class DishController {

    @Autowired
    private DishService dishService;
    @Autowired
    private RedisTemplate redisTemplate;

    /**
     * 新增菜品
     *
     * @param dishDTO
     * @return
     */
    @PostMapping
    @ApiOperation("新增菜品")
    public Result save(@RequestBody DishDTO dishDTO) {
        log.info("新增菜品: {}", dishDTO);
        dishService.saveWithFlavor(dishDTO);
        //删除缓存数据
        String key = "dish_" + dishDTO.getCategoryId();
        cleanCache(key);

        return Result.success();
    }


    /**
     * 菜品批量删除
     *
     * @param ids
     * @return
     */
    @DeleteMapping
    @ApiOperation("菜品批量删除")
    public Result delete(@RequestParam List<Long> ids) {
        log.info("菜品批量删除:{}", ids);
        dishService.deleteBath(ids);
        //删除缓存数据
        cleanCache("dish_*");

        return Result.success();
    }


    /**
     * 修改菜品
     *
     * @param dishDTO
     * @return
     */
    @PutMapping
    @ApiOperation("修改菜品")
    public Result update(@RequestBody DishDTO dishDTO) {
        log.info("修改菜品: {}", dishDTO);
        dishService.updateWithFlavor(dishDTO);
        //删除缓存数据
        cleanCache("dish_*");

        return Result.success();
    }


    /**
     * 菜品起售停售
     *
     * @param status
     * @param id
     * @return
     */
    @PostMapping("/status/{status}")
    @ApiOperation("菜品起售停售")
    public Result<String> startOrStop(@PathVariable Integer status, Long id) {
        dishService.startOrStop(status, id);

        //将所有的菜品缓存数据清理掉,所有以dish_开头的key
        cleanCache("dish_*");

        return Result.success();
    }


    /**
     * 清理缓存数据
     *
     * @param pattern
     */
    private void cleanCache(String pattern) {
        // 获取可以删除的key
        Set keys = redisTemplate.keys(pattern);
        // 根据key删除缓存数据
        redisTemplate.delete(keys);
    }

}

功能测试

Spring Cache

Spring Cache是一个框架, 实现了基于注解的缓存功能, 只要加上相应注解, 就能实现缓存功能

缓存实现: Spring Cache 提供了一层抽象,底层可以切换不同的缓存实现,而且无需改动业务代码

支持的缓存实现

  • EHCache
  • Caffeine
  • Redis

切换缓存实现: 切换不同的中间件坐标即可切换缓存实现, 无需改动业务代码

// Sring Cache坐标
<dependency>      
       <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-starter-cache</artifactId>
  </dependency>
// 具体的缓存中间件坐标
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
 </dependency>

常用注解

入门案例

导入资料中的springcache-dome工程, 用于学习springcache的使用

准备工作

  1. 创建cpring_cache_dome数据库

  1. 执行springcachedome.sql文件, 创建数据表
  2. 引入坐标
// Sring Cache坐标
<dependency>      
       <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-starter-cache</artifactId>
  </dependency>
// 具体的缓存中间件坐标
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
 </dependency>
  1. 在启动类上开启功能缓存注解功能
@SpringBootApplication
@Slf4j
@EnableCaching //开启缓存注解功能
public class SkyApplication {
    public static void main(String[] args) {
        SpringApplication.run(SkyApplication.class, args);
        log.info("server started");
    }
}

使用 CachePut()注解 将方法的返回值存在缓存中

@RestController
@RequestMapping("/user")
public class UserController {

    @Autowired
    private UserMapper usereMapper;

    @PostMapping
    @CachePut(cacheNames="userCache",key = "#user.id")
    public User save(@RequestBody User user) {
        User user = usereMapper.insert(user);
        return user;
    }
}
  1. 通常在Controller中操作缓存
  2. userCache属性设置缓存名称, 一般命名要与业务相关
  3. key属性用来设置每一条缓存数据的名称, 最终的值是userCache属性 + spring表达式的值
  4. 如果key的值是abc, 最终key的完整格式就是 userCache::abc
  5. spring表达式的书写非常灵活, 以下的写法都可以读取返回值, 拼接key的名称

  1. redis中的key是支持树形结构的, 通过 冒号: 分割

使用Cacheable()注解, 使用或添加缓存数据

@RestController
@RequestMapping("/user")
public class UserController {

    @Autowired
    private UserMapper usereMapper;

    @GetMapping
    @Cacheable(cacheNames="userCache",key = "#id")
    public User getById(Long id) {
        User user = usereMapper.getById(id);
        return user;
    }
}
  1. Cacheable()注解是基于代理技术实现, 在方法运行前, 生成当前方法的代理对象,
  2. 在代理对象中, 查找缓存中是否有数据, 有数据就直接返回, 不会触发controller方法
  3. 没有数据, 通过反射, 调用目标的controller方法, 把方法的返回结果存入缓存

使用CacheEvict()注解, 使用删除缓存数据

@RestController
@RequestMapping("/user")
public class UserController {

    @Autowired
    private UserMapper usereMapper;

    @DeleteMapping
    // 精准删除: 通过key的值匹配缓存数据并删除
    @CacheEvict(cacheNames="userCache",key = "#id")
    public void delById(Long id) {
        usereMapper.delById(id);
    }

    @DeleteMapping("/delAll")
    // 全部删除: 删除userCache下的所有缓存数据
    @CacheEvict(cacheNames="userCache", allEntries = true)
    public void deleteAll() {
        usereMapper.deleteAll();
    }
}

缓存套餐

导入坐标

// 导入Spring Cache 和Redis 坐标
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
 </dependency>


 <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-cache</artifactId>
  </dependency>

开启缓存注解功能: 在启动类上添加@EnableCaching注解

@SpringBootApplication
@Slf4j
@EnableCaching //开启缓存注解功能
public class SkyApplication {
    public static void main(String[] args) {
        SpringApplication.run(SkyApplication.class, args);
        log.info("server started");
    }
}

添加缓存: 在客户端的SetmealControllere 的list方法上 添加@Cacheable注解

@RestController("userSetmealController")
@RequestMapping("/user/setmeal")
@Api(tags = "C端-套餐浏览接口")
public class SetmealController {
    @Autowired
    private SetmealService setmealService;

    /**
     * 条件查询
     *
     * @param categoryId
     * @return
     */
    @GetMapping("/list")
    @ApiOperation("根据分类id查询套餐")
    @Cacheable(cacheNames = "setmealCache", key = "#categoryId")
    public Result<List<Setmeal>> list(Long categoryId) {
        Setmeal setmeal = new Setmeal();
        setmeal.setCategoryId(categoryId);
        setmeal.setStatus(StatusConstant.ENABLE);

        List<Setmeal> list = setmealService.list(setmeal);
        return Result.success(list);
    }

}

删除缓存: 在管理端的SetmealController的save,delet,update, startOrShtop等方法上添加 CacheEvict注解

/**
 * 套餐管理
 */
@RestController
@RequestMapping("/admin/setmeal")
@Api(tags = "套餐相关接口")
@Slf4j
public class SetmealController {

    @Autowired
    private SetmealService setmealService;

    /**
     * 新增套餐
     *
     * @param setmealDTO
     * @return
     */
    @PostMapping
    @ApiOperation("新增套餐")
    // 精准删除:  根据key的名称(setmealCache::100)精确删除
    @CacheEvict(cacheNames = "setmealCache",key = "#setmealDTO.categoryId")
    public Result save(@RequestBody SetmealDTO setmealDTO) {
        setmealService.saveWithDish(setmealDTO);
        return Result.success();
    }

    ... ...
    
    /**
     * 套餐起售停售
     *
     * @param status
     * @param id
     * @return
     */
    @PostMapping("/status/{status}")
    @ApiOperation("套餐起售停售")
    // 全部删除: 删除setmealCache下面的所有缓存数据
    @CacheEvict(cacheNames = "setmealCache",allEntries = true)
    public Result startOrStop(@PathVariable Integer status, Long id) {
        setmealService.startOrStop(status, id);
        return Result.success();
    }
}
  • 注意: 使用注解时注意包的名称

功能测试: 第一次访问套餐查询数据库, 后面的访问使用缓存数据, 数据变化后会删除缓存

添加购物车接口

需求分析: 购物车就是暂时存放所选商品的地方

接口设计

购物车表(shopping_cart)的设计

需求:

  1. 体现出选择的什么商品
  2. 每个商品的数量
  3. 不同用户的购物车需要区分开
  4. 通过少量的冗余字段, 检查连接查询

设计DTO: 封装前端传递的数据

@Data
public class ShoppingCartDTO implements Serializable {

    private Long dishId;
    private Long setmealId;
    private String dishFlavor;

}

Controller: 新建 user/ShoppingCartController

@RestController
@RequestMapping("/user/shoppingCart")
@Slf4j
@Api(tags = "客户端购物车相关接口")
public class ShoppingCartController {

    @Autowired
    private ShoppingCartService shoppingCartService;
      /**
     * 添加购物车
     * @param shoppingCartDTO
     * @return
     */
    public Result add(@RequestBody ShoppingCartDTO shoppingCartDTO) {
        log.info("添加购物车,商品信息为:{}", shoppingCartDTO);
        shoppingCartService.addShoppingCart(shoppingCartDTO);
        return Result.success();
    }

}

Service: 新建ShoppingCartService接口和实现类

@Service
public interface ShoppingCartService {
    // 添加购物车
    void addShoppingCart(ShoppingCartDTO shoppingCartDTO);
}
@Service
public class ShoppingCartServiceImpl implements ShoppingCartService {

    @Autowired
    private ShoppingCartMapper shoppingCartMapper;
    @Autowired
    private DishMapper dishMapper;
    @Autowired
    private SetmealMapper setmealMapper;
    
    /**
     * 添加购物车
     *
     * @param shoppingCartDTO
     */
    public void addShoppingCart(ShoppingCartDTO shoppingCartDTO) {
        
        // 1.判断当前加入购物车的商品是否已经存在了
        ShoppingCart shoppingCart = new ShoppingCart();
        BeanUtils.copyProperties(shoppingCartDTO, shoppingCart);
        Long userId = BaseContext.getCurrentId();
        shoppingCart.setUserId(userId);

        List<ShoppingCart> list = shoppingCartMapper.list(shoppingCart);
        // 2.如果已经存在,只需要将数量加1
        if (list != null && list.size() > 0) {
            ShoppingCart cart = list.get(0);
            cart.setNumber(cart.getNumber() + 1);
            shoppingCartMapper.updateNumberById(cart);
        } else {
            // 3.如果不存在, 需要插入一条购物车数据
            // 判断本次添加到购物车的是菜品还是套餐
            Long dishId = shoppingCartDTO.getDishId();
            if(dishId != null) {
                // 本次添加到购物车的是菜品
                Dish dish = dishMapper.getById(dishId);
                shoppingCart.setName(dish.getName());
                shoppingCart.setImage(dish.getImage());
                shoppingCart.setAmount(dish.getPrice());
            } else {
                // 本次添加到购物车的是套餐
                Long setmealId = shoppingCartDTO.getSetmealId();
                Setmeal setmeal = setmealMapper.getById(setmealId);
                shoppingCart.setName(setmeal.getName());
                shoppingCart.setImage(setmeal.getImage());
                shoppingCart.setAmount(setmeal.getPrice());
            }
            shoppingCart.setNumber(1);
            shoppingCart.setCreateTime(LocalDateTime.now());
            shoppingCartMapper.insert(shoppingCart);

        }

    }
}

Mapper: 新建mapper/ShoppingCartMapper

@Mapper
public interface ShoppingCartMapper {

    /**
     * 添加条件查询
     *
     * @param shoppingCart
     * @return
     */
    List<ShoppingCart> list(ShoppingCart shoppingCart);
    
     /**
     * 根据id修改商品数量
     *
     * @param cart
     */
    @Update("update shopping_cart set number = #{number} where id = #{id}")
    void updateNumberById(ShoppingCart cart);

    /**
     * 添加商品数据
     *
     * @param shoppingCart
     */
    @Insert("insert into shopping_cart (name,user_id,dish_id,setmeal_id,dish_flavor,number,amount,image,create_time)" +
            "values (#{name},#{userId},#{dishId},#{setmealId},#{dishFlavor},#{number},#{amount},#{image},#{createTime})")
        void insert(ShoppingCart shoppingCart);
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.sky.mapper.ShoppingCartMapper">

    <select id="list" resultType="com.sky.entity.ShoppingCart">
        select * from shopping_cart
        <where>
            <if test="userId != null">
                and user_id = #{userId}
            </if>
            <if test="setmealId != null">
                and setmeal_id = #{setmealId}
            </if>
            <if test="dishFlavor != null">
                and dish_flavor = #{dishFlavor}
            </if>
        </where>
    </select>
</mapper>

功能测试

查看购物车接口

查看原型, 分析需求

接口设计

Controller

@RestController
@RequestMapping("/user/shoppingCart")
@Slf4j
@Api(tags = "客户端购物车相关接口")
public class ShoppingCartController {

    @Autowired
    private ShoppingCartService shoppingCartService;

    /**
     * 查看购物车
     * @return
     */
    @GetMapping("/list")
    @ApiOperation("查看购物车")
    public Result<List<ShoppingCart>> list() {
        List<ShoppingCart> list = shoppingCartService.showShoppingCart();
        return Result.success(list);
    }

}

Service

public interface ShoppingCartService {

    // 查看购物车
    List<ShoppingCart> showShoppingCart();

}
@Service
public class ShoppingCartServiceImpl implements ShoppingCartService {

    @Autowired
    private ShoppingCartMapper shoppingCartMapper;

    /**
     * 查看购物车
     *
     * @return
     */
    public List<ShoppingCart> showShoppingCart() {
        // 获取当前微信用户的id
        Long id = BaseContext.getCurrentId();
        ShoppingCart shoppingCart = ShoppingCart.builder().userId(id).build();
        List<ShoppingCart> list = shoppingCartMapper.list(shoppingCart);
        return list;
    }

}

mapper

@Mapper
public interface ShoppingCartMapper {

    /**
     * 添加条件查询
     *
     * @param shoppingCart
     * @return
     */
    List<ShoppingCart> list(ShoppingCart shoppingCart);

}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.sky.mapper.ShoppingCartMapper">

    <select id="list" resultType="com.sky.entity.ShoppingCart">
        select * from shopping_cart
        <where>
            <if test="userId != null">
                and user_id = #{userId}
            </if>
            <if test="setmealId != null">
                and setmeal_id = #{setmealId}
            </if>
            <if test="dishFlavor != null">
                and dish_flavor = #{dishFlavor}
            </if>
        </where>
    </select>

</mapper>

功能测试

清空购物车接口

查看原型, 分析需求

接口设计

Controller

@RestController
@RequestMapping("/user/shoppingCart")
@Slf4j
@Api(tags = "客户端购物车相关接口")
public class ShoppingCartController {

    @Autowired
    private ShoppingCartService shoppingCartService;


    /**
     * 清空购物车
     * @return
     */
    @DeleteMapping("/clean")
    @ApiOperation("清空购物车")
    public Result clean() {
        shoppingCartService.cleanShoppingCart();
        return Result.success();
    }

}

Service

public interface ShoppingCartService {

    // 清空购物车
    void cleanShoppingCart();
}
@Service
public class ShoppingCartServiceImpl implements ShoppingCartService {

    @Autowired
    private ShoppingCartMapper shoppingCartMapper;

    /**
     * 清空购物车
     */
    public void cleanShoppingCart() {
        // 获取当前微信用户的id
        Long id = BaseContext.getCurrentId();
        shoppingCartMapper.deleteByUserId(id);
    }
}

Mapper

@Mapper
public interface ShoppingCartMapper {

    /**
     * 根据用户id删除购物车数据
     *
     * @param id
     */
    @Delete("delete from shopping_cart where user_id = #{id}")
    void deleteByUserId(Long id);

}
举报

相关推荐

0 条评论