三级分类的数据表设计和构造API数据

本文涉及的产品
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
云数据库 RDS MySQL,高可用系列 2核4GB
简介: 三级分类的数据表设计和构造API数据

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第4天,点击查看活动详情

如此的业务需求应该说是每个项目中的基本吧。

诸如下图这种:

1704465543331.jpg

一级菜单下有二级菜单和三级菜单。与此类似的应用场景还有很多很多,如省市县的三级联动、后台管理菜单、部门展示等等。

一、数据表设计

先看看表结构吧

1704465550719.jpg

具体的 sql 文件,在贴的源码仓库中有。

其中最重要的就是理清父子层级的关系就好了,知道是个什么样子设计的,如何去管理他们,那这些就都不是事啦。

二级评论的表的设计方法其实也大致如此。

数据表中对应的数据关系大致如下:

1704465552948.jpg

二、项目代码

技术就是常用的 SpringBoot、Mybatis-Plus

另外还用了下Mybatis-Plus逆向生成器,还有Java的函数式编程

3.1、导入依赖

完整依赖如下:

<parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-dependencies</artifactId>
        <version>2.5.2</version>
    </parent>
    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
        </dependency>
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.4.1</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.2.6</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <!--进行Spring Boot配置文件部署时,发出警告Spring Boot Configuration Annotation Processor not configured,但是不影响运行
            它的意思是“Spring Boot配置注解执行器没有配置”,配置注解执行器的好处是什么。
            配置注解执行器配置完成后,当执行类中已经定义了对象和该对象的字段后,在配置文件中对该类赋值时,便会非常方便的弹出提示信息。
        -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <optional>true</optional>
        </dependency>
        <!--mybatis-plus逆向工程-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-generator</artifactId>
            <version>3.4.1</version>
        </dependency>
        <dependency>
            <groupId>org.apache.velocity</groupId>
            <artifactId>velocity-engine-core</artifactId>
            <version>2.2</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>
    </dependencies>

关于 Mybatis-plus 的版本,最新的那个逆向生成器不太熟,就用了旧版本的。感兴趣的可以去试一试新版本的。

3.2、逆向生成代码

import com.baomidou.mybatisplus.core.exceptions.MybatisPlusException;
import com.baomidou.mybatisplus.core.toolkit.StringPool;
import com.baomidou.mybatisplus.generator.AutoGenerator;
import com.baomidou.mybatisplus.generator.InjectionConfig;
import com.baomidou.mybatisplus.generator.config.*;
import com.baomidou.mybatisplus.generator.config.po.TableInfo;
import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;
import com.baomidou.mybatisplus.generator.engine.VelocityTemplateEngine;
import org.junit.platform.commons.util.StringUtils;
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;
// 演示例子,执行 main 方法控制台输入模块表名回车自动生成对应项目目录中
public class CodeGenerator {
    /**
     * <p>
     * 读取控制台内容
     * </p>
     */
    public static String scanner(String tip) {
        Scanner scanner = new Scanner(System.in);
        StringBuilder help = new StringBuilder();
        help.append("请输入" + tip + ":");
        System.out.println(help.toString());
        if (scanner.hasNext()) {
            String ipt = scanner.next();
            if (StringUtils.isNotBlank(ipt)) {
                return ipt;
            }
        }
        throw new MybatisPlusException("请输入正确的" + tip + "!");
    }
    public static void main(String[] args) {
        // 代码生成器
        AutoGenerator mpg = new AutoGenerator();
        // 全局配置
        GlobalConfig gc = new GlobalConfig();
        final String projectPath = System.getProperty("user.dir");
        gc.setOutputDir(projectPath + "/src/main/java");
        gc.setAuthor("nzc");
        gc.setOpen(false);
        // gc.setSwagger2(true); 实体属性 Swagger2 注解
        mpg.setGlobalConfig(gc);
        // 数据源配置
        DataSourceConfig dsc = new DataSourceConfig();
        dsc.setUrl("jdbc:mysql://localhost:3306/task1?useUnicode=true&useSSL=false&characterEncoding=utf8");
        // dsc.setSchemaName("public");
        dsc.setDriverName("com.mysql.cj.jdbc.Driver");
        dsc.setUsername("root");
        dsc.setPassword("123456");
        mpg.setDataSource(dsc);
        // 包配置
        final PackageConfig pc = new PackageConfig();
        pc.setModuleName(scanner("模块名"));
        pc.setParent("com.nzc");
        mpg.setPackageInfo(pc);
        // 自定义配置
        InjectionConfig cfg = new InjectionConfig() {
            @Override
            public void initMap() {
                // to do nothing
            }
        };
        // 如果模板引擎是 freemarker
        //String templatePath = "/templates/mapper.xml.ftl";
        // 如果模板引擎是 velocity
        String templatePath = "/templates/mapper.xml.vm";
        // 自定义输出配置
        List<FileOutConfig> focList = new ArrayList<>();
        // 自定义配置会被优先输出
        focList.add(new FileOutConfig(templatePath) {
            @Override
            public String outputFile(TableInfo tableInfo) {
                // 自定义输出文件名 , 如果你 Entity 设置了前后缀、此处注意 xml 的名称会跟着发生变化!!
                return projectPath + "/src/main/resources/mapper/" + pc.getModuleName()
                        + "/" + tableInfo.getEntityName() + "Mapper" + StringPool.DOT_XML;
            }
        });
        cfg.setFileOutConfigList(focList);
        mpg.setCfg(cfg);
        // 配置模板
        TemplateConfig templateConfig = new TemplateConfig();
        templateConfig.setXml(null);
        mpg.setTemplate(templateConfig);
        // 策略配置
        StrategyConfig strategy = new StrategyConfig();
        strategy.setNaming(NamingStrategy.underline_to_camel);
        strategy.setColumnNaming(NamingStrategy.underline_to_camel);
        strategy.setEntityLombokModel(true);
        strategy.setRestControllerStyle(true);
        // 公共父类
        // 写于父类中的公共字段
        strategy.setSuperEntityColumns("id");
        strategy.setInclude(scanner("表名,多个英文逗号分割").split(","));
        strategy.setControllerMappingHyphenStyle(true);
        strategy.setTablePrefix("pms_");
        mpg.setStrategy(strategy);
        mpg.setTemplateEngine(new VelocityTemplateEngine());
        mpg.execute();
    }
}

运行代码:

1704465575590.jpg

执行结果

1704465578400.jpg

默认里面是没有方法的话,不过用来写个基本的小demo是非常方便的。

3.3、业务代码

controller、mapper、entity都不是重点,一笔带过了哈。

@RestController
@RequestMapping("/category")
public class CategoryController {
    @Autowired
    ICategoryService categoryService;
    /**
     * 查询出所有的数据,以树形结构组装起来
     * @return
     */
    @GetMapping("/tree")
    public Result listTree() {
        List<Category> entities= categoryService.listWithTree();
        return ResultUtil.success(entities);
    }
}
public interface CategoryMapper extends BaseMapper<Category> {
}
@Data
@EqualsAndHashCode(callSuper = false)
@TableName("pms_category")
public class Category implements Serializable {
    private static final long serialVersionUID = 1L;
    @TableId(value = "cat_id", type = IdType.AUTO)
    private Long catId;
    private String name;
    private Long parentCid;
    private Integer catLevel;
    private Integer showStatus;
    private Integer sort;
    private String icon;
    private String productUnit;
    private Integer productCount;
    @TableField(exist = false)
    private List<Category> categoryChild;
}
public interface ICategoryService extends IService<Category> {
    /**
     * 封装成三级菜单形式
     * @return
     */
    List<Category> listWithTree();
}

真正的业务是在 serviceImpl 中

@Service
public class CategoryServiceImpl extends ServiceImpl<CategoryMapper, Category> implements ICategoryService {
//    @Autowired 
//    CategoryMapper categoryMapper;
    @Override
    public List<Category> listWithTree() {
        //1、查询出所有的分类
        List<Category> allCategory = baseMapper.selectList(null);
        //2、组装成父子的树形结构
        //2.1、找到所有的一级分类
        List<Category> categoriesLevel1 = allCategory.stream()
              // filter 方法就如名称一样,就是用来过滤的 
              // 满足条件的会留下,到最后会返回一个流式对象
                .filter(category ->
                        category.getParentCid() == 0
                )
              // map 的理解百话点说就是可以对传入的对象做出修改 
              // 这里的意思就是找出当前一级分类中的二级分类,然后再set进去
                // 最后再返回一个流式对象
                .map((menu) -> {
                    menu.setCategoryChild(getChildrens(menu, allCategory));
                    return menu;
                })
              // 见名知意,这就是排序 ,其实也算是重定义Java比较器
               .sorted((menu1, menu2) -> {
                    return (menu1.getSort() == null ? 0 : menu1.getSort()) - (menu2.getSort() == null ? 0 : menu2.getSort());
                })
              // 就是将流转换为带泛型的List对象
                .collect(Collectors.toList());
        return categoriesLevel1;
    }
    private List<Category> getChildrens(Category root, List<Category> all) {
        // 这里的逻辑和上面一段是一样的,这里是构造三级、四级等等多层也是一样构造的
        List<Category> collect = all.stream()
                .filter(category -> {
                    return category.getParentCid().equals(root.getCatId());
                }).map((menu) -> {
                    menu.setCategoryChild(getChildrens(menu, all));
                    return menu;
                })
                .sorted((menu1, menu2) -> {
                    return (menu1.getSort() == null ? 0 : menu1.getSort()) - (menu2.getSort() == null ? 0 : menu2.getSort());
                })
                .collect(Collectors.toList());
        return collect;
    }
}

还有一些公共代码和配置文件什么的,在仓库中都是有的,这里不再重复贴出来啦。

3.4、测试结果

我就是使用 Postman 测试的,没有专门画前端,前端太弱鸡啦,求大佬们放过...

1704465601216.jpg

源码:githubgitee

三、自言自语

真正到了工作的时候,你会发现你写代码和平常还是会有很大差别的,最主要就是体现在业务场景这个方面。

写 Demo 的时候,主要就想着能写出个 Demo 就不错了,但实际上真到了该上业务的时候,综合起来还是有很多值得思考的问题。

在学习的时候针对一些必要的技术还是可以往深里挖掘挖掘的,并且自己也要学会根据现学的东西想到应用场景,思考如何整合项目,那些地方还有不足的,项目的扩展性如何等等,这些可以不用实现,但是在学习的时候能够多一些思考,这对于学习一个新技术是很有必要的。

相关实践学习
如何在云端创建MySQL数据库
开始实验后,系统会自动创建一台自建MySQL的 源数据库 ECS 实例和一台 目标数据库 RDS。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助 &nbsp; &nbsp; 相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
目录
相关文章
|
16天前
|
人工智能 关系型数据库 MySQL
数据魔力,一触即发 —— Dataphin数据服务API,百炼插件新星降临!
本文通过一个利用百炼大模型平台和Dataphin数据服务API构建一个客户360智能应用的案例,介绍如何使用Dataphin数据服务API在百炼平台创建一个自定义插件,用于智能应用的开发,提升企业智能化应用水平。
数据魔力,一触即发 —— Dataphin数据服务API,百炼插件新星降临!
|
13天前
|
XML 数据可视化 API
商品详情数据实战案例,API接口系列
淘宝商品详情数据在电商领域具有广泛的应用价值,而淘宝商品详情API接口则为开发者提供了获取这些数据的重要途径。通过合理利用这些接口和数据,可以提升业务效率、优化用户体验,为电商行业的发展注入新的活力。
|
17天前
|
SQL 缓存 API
在API接口数据获取过程中,如何确保数据的安全性和隐私性?
在API接口数据获取过程中,确保数据的安全性和隐私性至关重要。本文介绍了身份认证与授权、防止SQL注入和XSS攻击、加密传输、API版本控制、限流与熔断、压力测试与性能优化、备份与恢复以及法律和伦理考量等关键措施,帮助开发者和管理者有效保护API接口的数据安全和隐私性。
|
23天前
|
存储 JSON API
淘宝API接口实战:高效获取商品标题、分类及店铺名称
在淘宝API接口实战中,通过以下步骤高效获取商品标题、分类及店铺名称:1. 准备工作:了解淘宝开放平台文档,注册开发者账号,选择开发语言和工具。2. 获取API访问权限:申请相应权限,提供应用场景说明。3. 调用API接口:构建HTTP请求,提供必要参数。4. 解析响应数据:提取JSON数据中的所需信息。5. 数据处理和存储:进一步处理并存储数据。6. 注意事项:遵守使用规范,注意调用频率和数据安全。示例代码使用Python调用淘宝API。
|
24天前
|
前端开发 JavaScript API
探索GraphQL:如何构建高效的数据API
【10月更文挑战第25天】在现代Web开发中,API的效率和灵活性至关重要。本文探讨了如何利用GraphQL构建高效的数据API。GraphQL通过声明式查询方式,允许客户端精确指定所需数据,减少数据传输量,提高API效率。文章介绍了设置GraphQL服务器、设计API Schema、实现解析函数及调整前后端交互的具体步骤,展示了GraphQL的优势和应用场景。
33 2
|
29天前
|
缓存 监控 测试技术
获取API接口数据的最佳实践详解
在开发过程中,与API进行交互是获取数据和服务的关键步骤。本文详细介绍了10个最佳实践,包括明确需求和文档、错误处理、数据验证、性能优化、安全性、日志和监控、版本控制、代码复用和维护、测试以及遵守法律和道德规范,帮助开发者更高效地从API获取数据,确保数据的准确性、安全性和性能。
|
28天前
|
存储 数据可视化 API
API接口数据获取流程的细化
本文概述了API的基础知识、获取API访问权限的方法、编写代码调用API的步骤、数据处理与分析技巧以及数据安全与合规的重要性,并提供了社交媒体数据分析、天气预报应用和电商数据分析等API数据获取的应用实例,旨在帮助读者全面了解和实践API接口数据获取的流程。
|
29天前
|
缓存 监控 API
抖音抖店 API 请求获取宝贝详情数据的调用频率限制如何调整?
抖音抖店API请求获取宝贝详情数据的调用频率受限,需遵循平台规则。开发者可通过提升账号等级、申请更高配额、优化业务逻辑(如缓存数据、异步处理、批量请求)及监控调整等方式来应对。
|
14天前
|
JSON API 数据安全/隐私保护
拍立淘按图搜索API接口返回数据的JSON格式示例
拍立淘按图搜索API接口允许用户通过上传图片来搜索相似的商品,该接口返回的通常是一个JSON格式的响应,其中包含了与上传图片相似的商品信息。以下是一个基于淘宝平台的拍立淘按图搜索API接口返回数据的JSON格式示例,同时提供对其关键字段的解释
|
24天前
|
JSON 前端开发 JavaScript
API接口商品详情接口数据解析
商品详情接口通常用于提供特定商品的详细信息,这些信息比商品列表接口中的信息更加详细和全面。以下是一个示例的JSON数据格式,用于表示一个商品详情API接口的响应。这个示例假定API返回一个包含商品详细信息的对象。
下一篇
无影云桌面