2-2 SPU和SKU详解

  商城系统中的商品信息肯定避免不了SPU和SKU这两个概念,本节就给大家详细介绍下这块的内容

1、掌握SKU和SPU关系

SPU = Standard Product Unit (标准化产品单元)

SPU是商品信息聚合的最小单位,是一组可复用、易检索的标准化信息的集合,该集合描述了一个产品的特性。通俗点讲,属性值、特性相同的商品就可以称为一个SPU。

SKU=stock keeping unit(库存量单位)

SKU即库存进出计量的单位, 可以是以件、盒、托盘等为单位。

SKU是物理上不可分割的最小存货单元。在使用时要根据不同业态,不同管理模式来处理。在服装、鞋类商品中使用最多最普遍。

举个例子:

购买手机的时候,你可以选择华为Mate40系列手机,Mate40系列手机的生产制造商是华为,品牌是华为,手机分类也是华为,不过Mate40系列手机有多款,比如 Mate40 、Mate40 Pro 、 Mate40 Pro ,每款手机的架构也不一样,颜色也不一定一样,那么这个例子中哪些是Spu哪些是Sku呢?

Spu:

手机系列:Mate40系列 厂家:华为 品牌:华为 分类:手机

Sku:

价格 颜色 网络格式

mybatis自动生成方法介绍(SPU和SKU详解及MyBatisPlus自动生成)(1)

2、表结构设计2.1 Spu和Sku

spu:

CREATE TABLE `spu` ( `id` varchar(60) NOT NULL COMMENT '主键', `name` varchar(100) DEFAULT NULL COMMENT 'SPU名', `intro` varchar(200) DEFAULT NULL COMMENT '简介', `brand_id` int(11) DEFAULT NULL COMMENT '品牌ID', `category_one_id` int(20) DEFAULT NULL COMMENT '一级分类', `category_two_id` int(10) DEFAULT NULL COMMENT '二级分类', `category_three_id` int(10) DEFAULT NULL COMMENT '三级分类', `images` varchar(1000) DEFAULT NULL COMMENT '图片列表', `after_sales_Service` varchar(50) DEFAULT NULL COMMENT '售后服务', `content` longtext COMMENT '介绍', `attribute_list` varchar(3000) DEFAULT NULL COMMENT '规格列表', `is_marketable` int(1) DEFAULT '0' COMMENT '是否上架,0已下架,1已上架', `is_delete` int(1) DEFAULT '0' COMMENT '是否删除,0:未删除,1:已删除', `status` int(1) DEFAULT '0' COMMENT '审核状态,0:未审核,1:已审核,2:审核不通过', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;

sku:

CREATE TABLE `sku` ( `id` varchar(60) NOT NULL COMMENT '商品id', `name` varchar(200) NOT NULL COMMENT 'SKU名称', `price` int(20) NOT NULL DEFAULT '1' COMMENT '价格(分)', `num` int(10) DEFAULT '100' COMMENT '库存数量', `image` varchar(200) DEFAULT NULL COMMENT '商品图片', `images` varchar(2000) DEFAULT NULL COMMENT '商品图片列表', `create_time` datetime DEFAULT NULL COMMENT '创建时间', `update_time` datetime DEFAULT NULL COMMENT '更新时间', `spu_id` varchar(60) DEFAULT NULL COMMENT 'SPUID', `category_id` int(10) DEFAULT NULL COMMENT '类目ID', `category_name` varchar(200) DEFAULT NULL COMMENT '类目名称', `Brand_id` int(11) DEFAULT NULL COMMENT '品牌id', `brand_name` varchar(100) DEFAULT NULL COMMENT '品牌名称', `sku_attribute` varchar(200) DEFAULT NULL COMMENT '规格', `status` int(1) DEFAULT '1' COMMENT '商品状态 1-正常,2-下架,3-删除', PRIMARY KEY (`id`), KEY `cid` (`category_id`), KEY `status` (`status`), KEY `updated` (`update_time`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='商品表';

2.2 商品发布流程分析

商品发布流程如下:

mybatis自动生成方法介绍(SPU和SKU详解及MyBatisPlus自动生成)(2)

1)分类选择

mybatis自动生成方法介绍(SPU和SKU详解及MyBatisPlus自动生成)(3)

发布商品前,需要先选择发布商品所属分类,分类严格定义为3级分类。

分类表:

CREATE TABLE `category` ( `id` int(20) NOT NULL AUTO_INCREMENT COMMENT '分类ID', `name` varchar(50) DEFAULT NULL COMMENT '分类名称', `sort` int(11) DEFAULT NULL COMMENT '排序', `parent_id` int(20) DEFAULT NULL COMMENT '上级ID', PRIMARY KEY (`id`), KEY `parent_id` (`parent_id`) ) ENGINE=InnoDB AUTO_INCREMENT=11182 DEFAULT CHARSET=utf8 COMMENT='商品类目';

2)选择品牌

mybatis自动生成方法介绍(SPU和SKU详解及MyBatisPlus自动生成)(4)

分类选择完成后,需要加载品牌,品牌加载并非一次性加载完成,而是根据选择的分类进行加载。

分类品牌关系表:

CREATE TABLE `category_brand` ( `category_id` int(11) NOT NULL COMMENT '分类ID', `brand_id` int(11) NOT NULL COMMENT '品牌ID', PRIMARY KEY (`brand_id`,`category_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;

品牌表:

CREATE TABLE `brand` ( `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '品牌id', `name` varchar(100) NOT NULL COMMENT '品牌名称', `image` varchar(1000) DEFAULT '' COMMENT '品牌图片地址', `initial` varchar(1) DEFAULT '' COMMENT '品牌的首字母', `sort` int(11) DEFAULT NULL COMMENT '排序', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=14 DEFAULT CHARSET=utf8 COMMENT='品牌表';

3)属性加载

mybatis自动生成方法介绍(SPU和SKU详解及MyBatisPlus自动生成)(5)

当选择分类后,加载分类对应的属性。

分类属性表:

CREATE TABLE `category_attr` ( `category_id` int(11) NOT NULL, `attr_id` int(11) NOT NULL COMMENT '属性分类表', PRIMARY KEY (`category_id`,`attr_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;

属性表:

CREATE TABLE `sku_attribute` ( `id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'ID', `name` varchar(50) DEFAULT NULL COMMENT '属性名称', `options` varchar(2000) DEFAULT NULL COMMENT '属性选项', `sort` int(11) DEFAULT NULL COMMENT '排序', PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=13 DEFAULT CHARSET=utf8;

3、 商品发布加载功能

对应的Bean我们已经提前写好,如下

package com.bobo.vip.mall.goods.model; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import java.io.Serializable; /***** * @Author: 波波 * @Description: 云商城 ****/ @Data @AllArgsConstructor @NoArgsConstructor //MyBatisPlus表映射注解 @TableName(value = "category") public class Category implements Serializable { @TableId(type = IdType.AUTO) private Integer id; private String name; private Integer sort; private Integer parentId; }

package com.bobo.vip.mall.goods.model; import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableName; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; /***** * @Author: 波波 * @Description: 云商城 ****/ @Data @AllArgsConstructor @NoArgsConstructor //MyBatisPlus表映射注解 @TableName(value = "category_attr") public class CategoryAttr { @TableField private Integer categoryId; @TableField private Integer attrId; }

/***** * @Author: 波波 * @Description: 云商城 ****/ @Data @AllArgsConstructor @NoArgsConstructor //MyBatisPlus表映射注解 @TableName(value = "category_brand") public class CategoryBrand { @TableField private Integer categoryId; @TableField private Integer brandId; }

/***** * @Author: 波波 * @Description: 云商城 ****/ @Data @NoArgsConstructor @AllArgsConstructor public class Product { // Spu private Spu spu; // Sku private List<Sku> skus; } 1234567891011121314

@Data @AllArgsConstructor @NoArgsConstructor //MyBatisPlus表映射注解 @TableName(value = "sku") public class Sku { @TableId(type = IdType.ASSIGN_ID) private String id; private String name; private Integer price; private Integer num; private String image; private String images; private Date createTime; private Date updateTime; private String spuId; private Integer categoryId; private String categoryName; private Integer brandId; private String brandName; private String skuAttribute; private Integer status; }

/***** * @Author: 波波 * @Description: 云商城 ****/ @Data @AllArgsConstructor @NoArgsConstructor //MyBatisPlus表映射注解 @TableName(value = "sku_attribute") public class SkuAttribute implements Serializable { @TableId(type = IdType.AUTO) private Integer id; private String name; private String options; private Integer sort; //对应分类 @TableField(exist = false) private List<Category> categories; } 12345678910111213141516171819202122

@Data @AllArgsConstructor @NoArgsConstructor //MyBatisPlus表映射注解 @TableName(value = "spu") public class Spu { @TableId(type = IdType.ASSIGN_ID) private String id; private String name; private String intro; private Integer brandId; private Integer categoryOneId; private Integer categoryTwoId; private Integer categoryThreeId; private String images; private String afterSalesservice; private String content; private String attributeList; private Integer isMarketable; private Integer isDelete; private Integer status; }

3.1 分类加载

分类功能需要实现按照父ID查询,最开始初始化加载的是顶级父类,parent_id=0,后面每次点击的时候都根据传入的id查询子分类。

1)Mapper

创建com.bobo.vip.mall.goods.mapper.CategoryMapper,代码如下:

public interface CategoryMapper extends BaseMapper<Category> { } 12

2)Service

接口:com.bobo.vip.mall.goods.service.CategoryService代码如下:

public interface CategoryService extends IService<Category> { /** * 根据父ID查询子分类 * @param pid * @return */ List<Category> queryByParentId(Integer pid); } 123456789

实现类:com.bobo.vip.mall.goods.service.impl.CategoryServiceImpl代码如下:

@Service public class CategoryServiceImpl extends ServiceImpl<CategoryMapper,Category> implements CategoryService { @Autowired private CategoryMapper categoryMapper; /*** * 根据父ID查询子分类 * @param pid * @return */ @Override public List<Category> queryByParentId(Integer pid) { //条件封装 QueryWrapper<Category> queryWrapper = new QueryWrapper<Category>(); queryWrapper.eq("parent_id",pid); return categoryMapper.selectList(queryWrapper); } } 12345678910111213141516171819

3)Controller

创建com.bobo.vip.mall.goods.controller.CategoryController代码如下;

@RestController @RequestMapping(value = "/category") @CrossOrigin public class CategoryController { @Autowired private CategoryService categoryService; /**** * 根据父ID查询子分类 */ @GetMapping(value = "/parent/{pid}") public RespResult<List<Category>> list(@PathVariable(value = "pid")Integer pid){ List<Category> categories = categoryService.queryByParentId(pid); return RespResult.ok(categories); } } 1234567891011121314151617

3.2 品牌加载

品牌需要根据分类进行加载,当用户选择第3级分类的时候,加载品牌,品牌数据需要经过category_brand表关联查询。

我们可以按照如下步骤实现:

1、查询category_brand中指定分类对应的品牌ID集合 2、从brand查出品牌集合

1)Mapper

修改com.bobo.vip.mall.goods.mapper.BrandMapper,添加根据分类ID查询品牌ID集合:

//根据分类ID查询品牌集合 @Select("select brand_id from category_brand where category_id=#{id}") List<Integer> queryBrandIds(Integer id); 123

2)Service

接口:com.bobo.vip.mall.goods.service.BrandService中添加根据分类ID查询品牌集合方法

//根据分类ID查询品牌 List<Brand> queryByCategoryId(Integer id); 12

实现类:com.bobo.vip.mall.goods.service.impl.BrandServiceImpl

/*** * 根据分类ID查询品牌 * @param id * @return */ @Override public List<Brand> queryByCategoryId(Integer id) { //查询分类ID对应的品牌集合 List<Integer> brandIds = brandMapper.queryBrandIds(id); //根据品牌ID集合查询品牌信息 List<Brand> brands = brandMapper.selectBatchIds(brandIds); return brands; } 12345678910111213

3)Controller

修改com.bobo.vip.mall.goods.controller.BrandController添加根据分类ID查询品牌集合

/**** * 根据分类ID查询品牌 */ @GetMapping(value = "/category/{id}") public RespResult<List<Brand>> categoryBrands(@PathVariable(value = "id")Integer id){ List<Brand> brands = brandService.queryByCategoryId(id); return RespResult.ok(brands); } 12345678

mybatis自动生成方法介绍(SPU和SKU详解及MyBatisPlus自动生成)(6)

3.3 属性加载

属性也称为规格,属性也需要根据分类查询,我们可以按照如下思路实现:

1、先从category_attr根据分类ID查询出当前分类拥有的属性ID集合 2、从sku_attribute中查询属性集合

1)Mapper

创建com.bobo.vip.mall.goods.mapper.SkuAttributeMapper实现根据分类ID查询属性信息。

/*** * 根据分类ID查询属性集合 * @param id * @return */ @Select("SELECT * FROM sku_attribute WHERE id IN(SELECT attr_id FROM category_attr WHERE category_id=#{id})") List<SkuAttribute> queryByCategoryId(Integer id); 1234567

2)Service

接口:com.bobo.vip.mall.goods.service.SkuAttributeService添加根据分类ID查询属性集合方法

//根据分类ID查询属性集合 List<SkuAttribute> queryList(Integer id); 12

实现类:com.bobo.vip.mall.goods.service.impl.SkuAttributeServiceImpl添加如下实现方法

/*** * 根据分类ID查询属性集合 * @param id * @return */ @Override public List<SkuAttribute> queryList(Integer id) { return skuAttributeMapper.queryByCategoryId(id); } 123456789

3)Controller

创建com.bobo.vip.mall.goods.controller.SkuAttributeController,添加如下方法

/*** * 根据分类ID查询 */ @GetMapping(value = "/category/{id}") public RespResult<SkuAttribute> categoryAttributeList(@PathVariable(value = "id")Integer id){ //根据分类ID查询属性参数 List<SkuAttribute> skuAttributes = skuAttributeService.queryList(id); return RespResult.ok(skuAttributes); } 123456789

4、商品发布4.1 复合对象分析

mybatis自动生成方法介绍(SPU和SKU详解及MyBatisPlus自动生成)(7)

商品发布,如上图,我们可以发现发布的商品信息包含Sku和Spu,因此我们应该在后端能有一个对象同时能接到Spu和多个Sku,方法有很多种,我们可以直接在Spu中写一个List<Sku>,但这种方法不推荐,按照对象设计原则,对一个对象进行扩展时,尽量避免对原始对象造成改变,因此我们可以使用复合类,可以创建一个Prodcut类,该类中有Spu也有List<Sku>,代码如下:

@Data @NoArgsConstructor @AllArgsConstructor public class Product { // Spu private Spu spu; // Sku private List<Sku> skus; } 123456789

4.2 添加商品

添加商品的时候,我们需要保存Spu,同时需要添加多个Sku。我们可以在华为商城中看看真实电商中Sku名字特征,每次点击不同属性的时候,前部分名字一样,只是将名字中的规格替换了,也就是说Sku的名字其实是组合成的,一部分是Spu的一部分是Sku的,可以进行组合。

mybatis自动生成方法介绍(SPU和SKU详解及MyBatisPlus自动生成)(8)

1)名字分析

添加商品的时候,会将商品的属性传入后台,格式如下,如果把规格名字添加到名字中,那就是华为商城中的效果了,我们可以这么做,把属性解析成Map,然后每个属性值添加到商品名字中即可。

{"适合人群":"有一定java基础的人","书籍分类":"软件编程"}

2)实现代码

Mapper

com.bobo.vip.mall.goods.mapper.SpuMapper代码如下:

public interface SpuMapper extends BaseMapper<Spu> { } 12

com.bobo.vip.mall.goods.mapper.SkuMapper代码如下:

public interface SkuMapper extends BaseMapper<Sku> { } 12

Service

com.bobo.vip.mall.goods.service.SpuService中添加产品方法如下

public interface SpuService extends IService<Spu> { //保存商品 void saveProduct(Product product); } 1234

com.bobo.vip.mall.goods.service.impl.SpuServiceImpl中添加产品方法如下:

@Service public class SpuServiceImpl extends ServiceImpl<SpuMapper,Spu> implements SpuService { @Autowired private SkuMapper skuMapper; @Autowired private SpuMapper spuMapper; @Autowired private CategoryMapper categoryMapper; @Autowired private BrandMapper brandMapper; // 保存商品 @Override public void saveProduct(Product product) { //Spu Spu spu = product.getSpu(); //上架 spu.setIsMarketable(1); //未删除 spu.setIsDelete(0); //状态 spu.setStatus(1); //添加 spuMapper.insert(spu); //查询三级分类 Category category = categoryMapper.selectById(spu.getCategoryThreeId()); //查询品牌 Brand brand = brandMapper.selectById(spu.getBrandId()); //当前时间 Date now = new Date(); //新增Sku集合 for (Sku sku : product.getSkus()) { //设置名字 String skuName = spu.getName(); Map<String,String> attrMap = JSON.parseObject(sku.getSkuAttribute(), Map.class); for (Map.Entry<String, String> entry : attrMap.entrySet()) { skuName = " " entry.getValue(); } sku.setName(skuName); //设置图片 sku.setImages(spu.getImages()); //设置状态 sku.setStatus(1); //设置类目ID sku.setCategoryId(spu.getCategoryThreeId()); //设置类目名称 sku.setCategoryName(category.getName()); //设置品牌ID sku.setBrandId(brand.getId()); //设置品牌名称 sku.setBrandName(brand.getName()); //设置Spuid sku.setSpuId(spu.getId()); //时间 sku.setCreateTime(now); sku.setUpdateTime(now); //增加 skuMapper.insert(sku); } } } 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768

Controller

创建com.bobo.vip.mall.goods.controller.SpuController,添加产品代码如下:

@Autowired private SpuService spuService; /*** * 保存 */ @PostMapping(value = "/save") public RespResult save(@RequestBody Product product){ //保存 spuService.saveProduct(product); return RespResult.ok(); }

4.3 产品修改

产品修改其实和产品添加几乎一致,只需要做小改动即可,实现步骤如下:

1、如果Spu的id值不为空,说明是修改操作 2、如果是修改操作,先删除之前对应的Sku集合 3、其他流程和添加商品一致

修改com.bobo.vip.mall.goods.service.impl.SpuServiceImpl的save方法,代码如下:

在这里插入图片描述

源码如下:

@Override public void saveProduct(Product product) { //Spu Spu spu = product.getSpu(); //如果ID为空,则增加 if(StringUtils.isEmpty(spu.getId())){ //上架 spu.setIsMarketable(1); //未删除 spu.setIsDelete(0); //状态 spu.setStatus(1); //添加 spuMapper.insert(spu); }else{ //ID 不为空,则修改 spuMapper.updateById(spu); //删除之前的Sku记录 skuMapper.delete(new QueryWrapper<Sku>().eq("spu_id",spu.getId())); } //查询三级分类 Category category = categoryMapper.selectById(spu.getCategoryThreeId()); //查询品牌 Brand brand = brandMapper.selectById(spu.getBrandId()); //当前时间 Date now = new Date(); //新增Sku集合 for (Sku sku : product.getSkus()) { //设置名字 String skuName = spu.getName(); Map<String,String> attrMap = JSON.parseObject(sku.getSkuAttribute(), Map.class); for (Map.Entry<String, String> entry : attrMap.entrySet()) { skuName = " " entry.getValue(); } sku.setName(skuName); //设置图片 sku.setImages(spu.getImages()); //设置状态 sku.setStatus(1); //设置类目ID sku.setCategoryId(spu.getCategoryThreeId()); //设置类目名称 sku.setCategoryName(category.getName()); //设置品牌ID sku.setBrandId(brand.getId()); //设置品牌名称 sku.setBrandName(brand.getName()); //设置Spuid sku.setSpuId(spu.getId()); //时间 sku.setCreateTime(now); sku.setUpdateTime(now); //增加 skuMapper.insert(sku); } } 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859

5 MyBatis Plus代码生成

我们可以发现个问题,刚才写的很多增删改查代码都比较简单,比较枯燥,重复写一些类的创建、单表增删改查非常类,而创建对象和单标操作的代码,在开发中几乎占用了开发时间的80%,如果能够用工具生成就可以大大节省我们开发成本了。

5.1 MyBatis Plus介绍

AutoGenerator 是 MyBatis-Plus 的代码生成器,通过 AutoGenerator 可以快速生成 Entity、Mapper、Mapper XML、Service、Controller 等各个模块的代码,极大的提升了开发效率。

学习网址 https://baomidou.com/guide/generator.html

5.2 MyBatisPlus代码生成配置

1)引入依赖

<dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-generator</artifactId> <version>3.4.0</version> </dependency> <dependency> <groupId>org.apache.velocity</groupId> <artifactId>velocity-engine-core</artifactId> <version>2.2</version> </dependency> 1234567891011

2)代码生成

public static void main(String[] args) { // 代码生成器 AutoGenerator mpg = new AutoGenerator(); // 全局配置 GlobalConfig gc = new GlobalConfig(); String projectPath = System.getProperty("user.dir"); gc.setOutputDir(projectPath "/src/main/java"); // 文件输出路径 gc.setAuthor("bobo"); //作者 gc.setOpen(false); //生成之后是否打开目录 gc.setIdType(IdType.NONE); //主键策略 gc.setServiceName("%sService"); //名字设置 %s是占位符,可以理解成类的名字 mpg.setGlobalConfig(gc); // 数据源配置 DataSourceConfig dsc = new DataSourceConfig(); dsc.setUrl("jdbc:mysql://192.168.100.140:3306/shop_goods?useUnicode=true&useSSL=false&characterEncoding=utf8"); dsc.setDriverName("com.mysql.jdbc.Driver"); dsc.setUsername("root"); dsc.setPassword("123456"); mpg.setDataSource(dsc); // 包配置 PackageConfig pc = new PackageConfig(); pc.setModuleName("mall-goods"); pc.setParent("com.bobo.code"); mpg.setPackageInfo(pc); // 策略配置 StrategyConfig strategy = new StrategyConfig(); strategy.setNaming(NamingStrategy.underline_to_camel); //驼峰命名 strategy.setColumnNaming(NamingStrategy.underline_to_camel); //驼峰命名 strategy.setEntityLombokModel(true); //是否使用Lombok strategy.setRestControllerStyle(true); //是否生成RestController // 写于父类中的公共字段 strategy.setSuperEntityColumns("id"); //公共字段定义 strategy.setControllerMappingHyphenStyle(true); //驼峰转连字符 strategy.setTablePrefix(pc.getModuleName() "_"); //表前缀 mpg.setStrategy(strategy); mpg.execute(); }

效果

mybatis自动生成方法介绍(SPU和SKU详解及MyBatisPlus自动生成)(9)

有红色的提示是因为没有引入依赖,我们可以把生成的相关内容拷贝到合适的项目位置即可。

,