【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">

    <!-- 由于pms_spu表的id不是自动编号的,在插入数据时,需要显式指定此字段的值 -->
	<!-- 所以,不需要配置useGeneratedKeys和keyProperty -->
    <!-- int insert(Spu spu); -->
    <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">

    <!-- int insert(SpuDetail spuDetail); -->
    <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详情
    • 特别说明:只需要完成后端,不需要实现前端页面
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值