• 【Spring。。】Day18


    1. 新增SPU

    1.1. 分析

    为了保证数据表的查询效率,SPU数据中的“详情”(通过富文本编辑器输入的内容)被设计在pms_spu_detail表中,而其它的一般数据在pms_spu表中,当“新增SPU”时,本质上需要同时对这2张表进行“插入数据”的操作!

    需要执行的SQL语句大致是:

    insert into pms_spu (字段列表) values (值列表);
    insert into pms_spu_detail (字段列表) values (值列表);
    

    注意:在pms_spu表中,主键(id)并不是自动编号的(要考虑分库分表时,相关的数据表的主键都不允许使用自动编号)!

    另外,在插入数据之前,相关数据需要进行检查,包括:检查品牌、类别、相册的id是否存在、是否有效等,这些功能此前已经完成!需要注意:关于品牌、类别、相册的名称,前端是可以提交这些数据的,但是,服务器端不应该直接使用这些数据写入到数据表,只使用前端提交的品牌id、类别id、相册id,至于品牌名称、类别名称、相册名称,应该是在检查时一并查出来,并使用查询到的数据写入到数据表中!

    1.2. 关于Mapper

    关于插入Spu数据

    在根包下创建pojo.entity.Spu实体类。

    在根包下创建mapper.SpuMapper接口,添加抽象方法:

    @Repository
    public interface SpuMapper {
    
        /**
         * 插入SPU数据
         *
         * @param spu SPU数据
         * @return 受影响的行数
         */
        int insert(Spu spu);
        
    }
    

    src/main/resources/mapper下粘贴得到SpuMapper.xml文件,配置SQL语句:

    <mapper namespace="cn.tedu.csmall.product.mapper.SpuMapper">
    
        
    	
        
        <insert id="insert">
            INSERT INTO pms_spu (
                id, name, type_number, title, description,
                list_price, stock, stock_threshold, unit, brand_id,
                brand_name, category_id, category_name, attribute_template_id, album_id,
                pictures, keywords, tags, sort, is_deleted,
                is_published, is_new_arrival, is_recommend, is_checked, gmt_check
            ) VALUES (
                #{id}, #{name}, #{typeNumber}, #{title}, #{description},
                #{listPrice}, #{stock}, #{stockThreshold}, #{unit}, #{brandId},
                #{brandName}, #{categoryId}, #{categoryName}, #{attributeTemplateId}, #{albumId},
                #{pictures}, #{keywords}, #{tags}, #{sort}, #{isDeleted},
                #{isPublished}, #{isNewArrival}, #{isRecommend}, #{isChecked}, #{gmtCheck}
             )
        insert>
        
    mapper>
    

    src/test/java的根包下创建SpuMapperTests测试类,测试以上抽象方法:

    @Slf4j
    @SpringBootTest
    public class SpuMapperTests {
    
        @Autowired
        SpuMapper mapper;
    
        @Test
        public void testInsert() {
            Spu spu = new Spu();
            spu.setId(11000L); // 重要,必须
            spu.setName("小米13");
    
            log.debug("插入数据之前,参数={}", spu);
            int rows = mapper.insert(spu);
            log.debug("rows = {}", rows);
            log.debug("插入数据之后,参数={}", spu);
        }
        
    }
    

    关于插入SpuDetail数据

    在根包下创建pojo.entity.SpuDetail实体类。

    在根包下创建mapper.SpuDetailMapper接口,添加抽象方法:

    @Repository
    public interface SpuDetailMapper {
    
        /**
         * 插入SPU详情数据
         *
         * @param spuDetail SPU详情数据
         * @return 受影响的行数
         */
        int insert(SpuDetail spuDetail);
        
    }
    

    src/main/resources/mapper下粘贴得到SpuDetailMapper.xml文件,配置SQL语句:

    <mapper namespace="cn.tedu.csmall.product.mapper.SpuDetailMapper">
    
        
        <insert id="insert" useGeneratedKeys="true" keyProperty="id">
            INSERT INTO pms_spu_detail (
                spu_id,detail
            ) VALUES (
                #{spuId},#{detail}
            )
        insert>
        
    mapper>
    

    src/test/java的根包下创建SpuDetailMapperTests测试类,测试以上抽象方法:

    @Slf4j
    @SpringBootTest
    public class SpuDetailMapperTests {
    
        @Autowired
        SpuDetailMapper mapper;
    
        @Test
        public void testInsert() {
            SpuDetail spuDetail = new SpuDetail();
            spuDetail.setSpuId(10000L);
            spuDetail.setDetail("这是1号Spu的详情");
    
            log.debug("插入数据之前,参数={}", spuDetail);
            int rows = mapper.insert(spuDetail);
            log.debug("rows = {}", rows);
            log.debug("插入数据之后,参数={}", spuDetail);
        }
        
    }
    

    1.3. 关于Service层

    为了保证Spu的id的唯一性,且基于“不会高频率新增Spu”,可以使用时间加随机数字作为id值。

    考虑到后续可能调整生成id的策略,则将生成id的代码写在专门的工具类中,不写在业务层。

    在根包下创建util.IdUtils类,定义生成id的静态方法:

    package cn.tedu.csmall.product.util;
    
    import java.time.LocalDateTime;
    import java.time.format.DateTimeFormatter;
    import java.util.Random;
    
    /**
     * Id工具类
     *
     * @author java@tedu.cn
     * @version 0.0.1
     */
    public final class IdUtils {
    
        private IdUtils() {}
    
        private static DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyyMMddHHmmssSSS");
    
        private static Random random = new Random();
    
        // 临时策略:使用“年月日时分秒毫秒”加2位随机数作为id
        public static Long getId() {
            LocalDateTime now = LocalDateTime.now();
            String dateTimeString = dateTimeFormatter.format(now);
            int randomNumber = random.nextInt(89) + 10;
            Long id = Long.valueOf(dateTimeString + randomNumber);
            return id;
        }
    
    }
    

    在根包下创建pojo.dto.SpuAddNewDTO类(注意:相对于实体类,需要删除不由客户端提交的数据,并且,需要补充detail属性,表示“Spu详情”):

    package cn.tedu.csmall.product.pojo.dto;
    
    import lombok.Data;
    
    import java.io.Serializable;
    import java.math.BigDecimal;
    
    /**
     * SPU(Standard Product Unit)
     *
     * @author java@tedu.cn
     * @version 0.0.1
     */
    @Data
    public class SpuAddNewDTO implements Serializable {
    
        /**
         * SPU名称
         */
        private String name;
    
        /**
         * SPU编号
         */
        private String typeNumber;
    
        /**
         * 标题
         */
        private String title;
    
        /**
         * 简介
         */
        private String description;
    
        /**
         * 价格(显示在列表中)
         */
        private BigDecimal listPrice;
    
        /**
         * 当前库存(冗余)
         */
        private Integer stock;
    
        /**
         * 库存预警阈值(冗余)
         */
        private Integer stockThreshold;
    
        /**
         * 计件单位
         */
        private String unit;
    
        /**
         * 品牌id
         */
        private Long brandId;
    
        /**
         * 类别id
         */
        private Long categoryId;
    
        /**
         * 属性模板id
         */
        // private Long attributeTemplateId;
    
        /**
         * 相册id
         */
        private Long albumId;
    
        /**
         * 组图URLs,使⽤JSON格式表示
         */
        // private String pictures;
    
        /**
         * 关键词列表,各关键词使⽤英⽂的逗号分隔
         */
        private String keywords;
    
        /**
         * 标签列表,各标签使⽤英⽂的逗号分隔,原则上最多3个
         */
        private String tags;
    
        /**
         * ⾃定义排序序号
         */
        private Integer sort;
    
        /**
         * Spu详情
         */
        private String detail;
    
    }
    

    在根包下创建ISpuService接口,并在接口中添加“新增SPU”的抽象方法:

    @Transactional
    public interface ISpuService {
        void addNew(SpuAddNewDTO spuAddNewDTO);
    }
    

    在根包下创建SpuServiceImpl类,是组件类,实现以上接口:

    @Slf4j
    @Service
    public class SpuServiceImpl implements ISpuService {
        
        @Autowired
        private SpuMapper spuMapper;
        @Autowired
        private SpuDetailMapper spuDetailMapper;
        @Autowired
        private BrandMapper brandMapper;
        @Autowired
        private CategoryMapper categoryMapper;
        @Autowired
        private AlbumMapper albumMapper;
        
        @Override
        public void addNew(SpuAddNewDTO spuAddNewDTO) {
            // 从参数spuAddNewDTO中取出brandId
            // 调用brandMapper的getDetailsById()方法查询品牌
            // 判断查询结果是否为null
            // 是:抛出异常:选择的品牌不存在
            
            // 判断查询到的品牌的enable是否为0
            // 是:抛出异常
            
            // 从参数spuAddNewDTO中取出categoryId
            // 调用categoryMapper的getDetailsById()方法查询类别
            // 判断查询结果是否为null
            // 是:抛出异常:选择的类别不存在
            
            // 判断查询到的类别的enable是否为0
            // 是:抛出异常
            
            // 判断查询到的类别的isParent是否为1
            // 是:抛出异常
            
            // 从参数spuAddNewDTO中取出albumId
            // 调用albumMapper的getDetailsById()方法查询相册
            // 判断查询结果是否为null
            // 是:抛出异常:选择的相册不存在
            
            // 创建Spu对象
            // 将参数spuAddNewDTO的属性值复制到Spu对象中
            // 补全Spu对象的属性值:id >>> 自行决定
            // 补全Spu对象的属性值:brandName >>> 前序查询品牌的结果中取出
            // 补全Spu对象的属性值:categoryName >>> 前序查询类别的结果中取出
            // 补全Spu对象的属性值:sales / commentCount / positiveCommentCount >>> 0
            // 补全Spu对象的属性值:isDelete / isPublished >>> 0
            // 补全Spu对象的属性值:isNewArrival / isRecommend >>> 自行决定
            // 补全Spu对象的属性值:isChecked >>> 0
            // 补全Spu对象的属性值:checkUser / gmtCheck >>> null
            // 调用spuMapper的int insert(Spu spu)方法插入Spu数据,并获取返回值
            // 判断返回值是否不为1
            // 是:抛出异常
            
            // 创建SpuDetail对象
            // 补全SpuDetail对象的属性值:spuId >>> 同以上Spu对象的id
            // 补全SpuDetail对象的属性值:detail >>> 来自spuAddNewDTO参数
            // 调用spuDetailMapper的int insert(SpuDetail spuDetail)方法插入SpuDetail数据,并获取返回值
            // 判断返回值是否不为1
            // 是:抛出异常
        }
        
    }
    

    具体实现为:

    package cn.tedu.csmall.product.service.impl;
    
    import cn.tedu.csmall.product.ex.ServiceCode;
    import cn.tedu.csmall.product.ex.ServiceException;
    import cn.tedu.csmall.product.mapper.*;
    import cn.tedu.csmall.product.pojo.dto.SpuAddNewDTO;
    import cn.tedu.csmall.product.pojo.entity.Spu;
    import cn.tedu.csmall.product.pojo.entity.SpuDetail;
    import cn.tedu.csmall.product.pojo.vo.AlbumStandardVO;
    import cn.tedu.csmall.product.pojo.vo.BrandStandardVO;
    import cn.tedu.csmall.product.pojo.vo.CategoryStandardVO;
    import cn.tedu.csmall.product.service.ISpuService;
    import cn.tedu.csmall.product.util.IdUtils;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.beans.BeanUtils;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    
    /**
     * 处理Spu业务的实现类
     *
     * @author java@tedu.cn
     * @version 0.0.1
     */
    @Service
    @Slf4j
    public class SpuServiceImpl implements ISpuService {
    
        @Autowired
        private SpuMapper spuMapper;
        @Autowired
        private SpuDetailMapper spuDetailMapper;
        @Autowired
        private BrandMapper brandMapper;
        @Autowired
        private CategoryMapper categoryMapper;
        @Autowired
        private AlbumMapper albumMapper;
    
        @Override
        public void addNew(SpuAddNewDTO spuAddNewDTO) {
            // 从参数spuAddNewDTO中取出brandId
            Long brandId = spuAddNewDTO.getBrandId();
            // 调用brandMapper的getDetailsById()方法查询品牌
            BrandStandardVO brand = brandMapper.getStandardById(brandId);
            // 判断查询结果是否为null
            if (brand == null) {
                // 是:抛出异常:选择的品牌不存在
                String message = "新增Spu失败,尝试绑定的品牌数据不存在!";
                log.warn(message);
                throw new ServiceException(ServiceCode.ERR_NOT_FOUND, message);
            }
    
            // 判断查询到的品牌的enable是否为0
            if (brand.getEnable() == 0) {
                // 是:抛出异常
                String message = "新增Spu失败,尝试绑定的品牌已经被禁用!";
                log.warn(message);
                throw new ServiceException(ServiceCode.ERR_CONFLICT, message);
            }
    
            // 从参数spuAddNewDTO中取出categoryId
            Long categoryId = spuAddNewDTO.getCategoryId();
            // 调用categoryMapper的getDetailsById()方法查询类别
            CategoryStandardVO category = categoryMapper.getStandardById(categoryId);
            // 判断查询结果是否为null
            if (category == null) {
                // 是:抛出异常:选择的类别不存在
                String message = "新增Spu失败,尝试绑定的类别数据不存在!";
                log.warn(message);
                throw new ServiceException(ServiceCode.ERR_NOT_FOUND, message);
            }
    
            // 判断查询到的类别的enable是否为0
            if (category.getEnable() == 0) {
                // 是:抛出异常
                String message = "新增Spu失败,尝试绑定的类别已经被禁用!";
                log.warn(message);
                throw new ServiceException(ServiceCode.ERR_CONFLICT, message);
            }
    
            // 判断查询到的类别的isParent是否为1
            if (category.getIsParent() == 1) {
                // 是:抛出异常
                String message = "新增Spu失败,尝试绑定的类别包含子级类别,不允许使用此类别!";
                log.warn(message);
                throw new ServiceException(ServiceCode.ERR_CONFLICT, message);
            }
    
            // 从参数spuAddNewDTO中取出albumId
            Long albumId = spuAddNewDTO.getAlbumId();
            // 调用albumMapper的getDetailsById()方法查询相册
            AlbumStandardVO album = albumMapper.getStandardById(albumId);
            // 判断查询结果是否为null
            if (album == null) {
                // 是:抛出异常:选择的相册不存在
                String message = "新增Spu失败,尝试绑定的相册数据不存在!";
                log.warn(message);
                throw new ServiceException(ServiceCode.ERR_NOT_FOUND, message);
            }
    
            // 获取id(由别处生成)
            Long id = IdUtils.getId();
    
            // 创建Spu对象
            Spu spu = new Spu();
            // 将参数spuAddNewDTO的属性值复制到Spu对象中
            BeanUtils.copyProperties(spuAddNewDTO, spu);
            // 补全Spu对象的属性值:id >>> 自行决定
            spu.setId(id);
            // 补全Spu对象的属性值:brandName >>> 前序查询品牌的结果中取出
            spu.setBrandName(brand.getName());
            // 补全Spu对象的属性值:categoryName >>> 前序查询类别的结果中取出
            spu.setCategoryName(category.getName());
            // 补全Spu对象的属性值:sales / commentCount / positiveCommentCount >>> 0
            spu.setSales(0);
            spu.setCommentCount(0);
            spu.setPositiveCommentCount(0);
            // 补全Spu对象的属性值:isDelete / isPublished >>> 0
            spu.setIsDeleted(0);
            spu.setIsPublished(0);
            // 补全Spu对象的属性值:isNewArrival / isRecommend >>> 自行决定
            spu.setIsNewArrival(0);
            spu.setIsRecommend(0);
            // 补全Spu对象的属性值:isChecked >>> 0
            spu.setIsChecked(0);
            // 补全Spu对象的属性值:checkUser / gmtCheck >>> null
            // 调用spuMapper的int insert(Spu spu)方法插入Spu数据,并获取返回值
            int rows = spuMapper.insert(spu);
            // 判断返回值是否不为1
            if (rows != 1) {
                // 是:抛出异常
                String message = "新增Spu失败!服务器忙,请稍后再次尝试![错误代码:1]";
                log.warn(message);
                throw new ServiceException(ServiceCode.ERR_INSERT, message);
            }
    
            // 创建SpuDetail对象
            SpuDetail spuDetail = new SpuDetail();
            // 补全SpuDetail对象的属性值:spuId >>> 同以上Spu对象的id
            spuDetail.setSpuId(id);
            // 补全SpuDetail对象的属性值:detail >>> 来自spuAddNewDTO参数
            spuDetail.setDetail(spuAddNewDTO.getDetail());
            // 调用spuDetailMapper的int insert(SpuDetail spuDetail)方法插入SpuDetail数据,并获取返回值
            rows = spuDetailMapper.insert(spuDetail);
            // 判断返回值是否不为1
            if (rows != 1) {
                // 是:抛出异常
                String message = "新增Spu失败!服务器忙,请稍后再次尝试![错误代码:2]";
                log.warn(message);
                throw new ServiceException(ServiceCode.ERR_INSERT, message);
            }
        }
    
    }
    

    src/test/java的根包下创建service.SpuServiceTests测试类,编写并执行测试:

    @Slf4j
    @SpringBootTest
    public class SpuServiceTests {
    
        @Autowired
        ISpuService service;
    
        @Test
        void testAddNew() {
            try {
                SpuAddNewDTO spuAddNewDTO = new SpuAddNewDTO();
                spuAddNewDTO.setBrandId(2L);
                spuAddNewDTO.setCategoryId(3L);
                spuAddNewDTO.setAlbumId(2L);
                spuAddNewDTO.setName("测试Spu-001");
                service.addNew(spuAddNewDTO);
                log.debug("新增Spu成功!");
            } catch (ServiceException e) {
                log.debug("serviceCode : " + e.getServiceCode());
                log.debug("message : " + e.getMessage());
            }
        }
    
    }
    

    1.4. 关于Controller层

    在根包下创建SpuController控制器类,并处理请求:

    package cn.tedu.csmall.product.controller;
    
    /**
     * 处理Spu相关请求的控制器
     *
     * @author java@tedu.cn
     * @version 0.0.1
     */
    @Slf4j
    @RestController
    @RequestMapping("/spu")
    @Api(tags = "08. SPU管理模块")
    public class SpuController {
    
        @Autowired
        private ISpuService spuService;
    
        public SpuController() {
            log.info("创建控制器:SpuController");
        }
    
        // 添加SPU
        // http://localhost:9080/spu/add-new
        @ApiOperation("新增SPU")
        @ApiOperationSupport(order = 100)
        @PostMapping("/add-new")
        public JsonResult<Void> addNew(@Validated SpuAddNewDTO spuAddNewDTO) {
            log.debug("开始处理【新增SPU】的请求:{}", spuAddNewDTO);
            spuService.addNew(spuAddNewDTO);
            return JsonResult.ok();
        }
    
    }
    

    关于Redis

    Redis是一款使用K-V结构的、基于内存实现数据存取的NoSQL非关系型数据库

    使用Redis的主要目的是“缓存数据”,以提高查询数据的效率,对数据库也有一定的保护作用。

    需要注意:缓存的数据可能存在“不一致”的问题,因为,如果修改了数据库(例如MySQL)中的数据,而缓存(例如Redis)中的数据没有一并更新,则缓存中的数据是不准确的!但是,并不是所有的场景都要求数据非常准确!

    所以,使用Redis的前提条件:

    • 对数据的准确性要求不高
      • 例如:新浪微博的热搜排名、某个热门视频的播放量
    • 数据的修改频率不高
      • 例如:电商平台中的类别、电商平台中的品牌

    反之,某些情况下是不应该使用Redis的,例如:在秒杀商品时,使用Redis记录一些频繁修改的数据!

    在操作系统的终端下,通过redis-cli命令即可登录Redis控制台:

    redis-cli
    

    在Redis控制台中,使用setget命令就可以存取基本类型的数据:

    set name liucangsong
    
    get name
    

    在Redis中,有5种典型数据类型:字符串、Hash、Set、zSet、List,在结合编程时,通常,只需要使用“字符串”即可,在程序中,非字符串类型的数据(例如对象、集合等)都会通过工具转换成JSON格式的字符串再存入到Redis中,后续,从Redis中取出的也是字符串,再通过工具转换成原本的类型(例如对象、集合等)即可。

    作业

    实现以下功能(含前端页面与后端服务)

    • 显示SPU列表
    • 删除SPU
      • 业务规则:数据必须存在
      • 注意:需删除pms_spupms_spu_detail这2张表中的相关数据
    • 逻辑删除SPU
      • 业务规则:数据必须存在
      • 注意:本质上是执行UPDATE操作,将is_delete改为1
    • 恢复SPU
      • 业务规则:数据必须存在
      • 注意:本质上是执行UPDATE操作,将is_delete改为0
    • 根据id查询SPU详情
      • 特别说明:只需要完成后端,不需要实现前端页面
  • 相关阅读:
    【软考软件评测师】2012年下案例分析历年真题
    二、Redis分布式锁
    圆心科技,很焦虑?
    大家都会的Docker
    Java岗大厂面试百日冲刺 - 日积月累,每日三题【Day5】 —— 基础篇2
    使用CSS实现多种Noise噪点效果
    CDH大数据平台 27Cloudera Manager Console之superset之Python相关包安装(markdown新版一)
    K8s小白?应用部署太难?看这篇就够了!
    最长的斐波那契子序列的长度
    Python 详解计算 Median Absolute Deviation(绝对中位偏差)| 还可用于异常值检测
  • 原文地址:https://blog.csdn.net/shortcutsuccess/article/details/126962915