更优雅的多表查询

数据库设计原则

三大范式:

​ 在数据库设计中,范式是一种指导和规范数据库设计的原则,旨在减少数据冗余、维护数据一致性和提高数据结构的合理性。遵循范式可以确保数据库具有良好的性能、可扩展性和维护性。本文将介绍数据库设计范式的基本概念、为什么需要遵循它们,以及如何应用这些原则来设计数据库。
一、范式的基本概念
​ 范式是在关系型数据库设计中定义数据库结构的一种方式。它基于函数依赖和多值依赖的概念,确保数据表中的数据满足一定的组织和结构要求。范式可以分为不同的级别,从低级到高级依次为:第一范式(1NF)、第二范式(2NF)、第三范式(3NF)、BCNF、第四范式(4NF)等。每个级别都要求满足特定的条件,以消除数据冗余、维护数据一致性和提高数据结构的合理性。
二、为什么需要遵循范式
​ 遵循范式有很多好处。首先,它有助于减少数据冗余,从而节省存储空间和提高数据检索效率。其次,它有助于维护数据一致性,避免数据不一致和数据冲突的问题。此外,遵循范式还可以提高数据结构的合理性和可维护性,使数据库设计更加规范和易于理解。
三、如何应用范式原则设计数据库

  1. 第一范式(1NF):确保每个列都是不可分割的最小单元,即原子性。这意味着每个字段都只包含一个值,没有重复的组或记录。
  2. 第二范式(2NF):在1NF的基础上,消除部分依赖。这意味着将数据表按照关键字段进行分解,以消除非键字段对键的部分依赖。这样可以进一步减少数据冗余和提高数据结构的合理性。
  3. 第三范式(3NF):在2NF的基础上,消除传递依赖。这意味着将数据表按照非键字段进行分解,以消除一个非键字段对另一个非键字段的传递依赖。这样可以进一步减少数据冗余和提高数据结构的合理性。
  4. BCNF:在3NF的基础上,消除多值依赖和连接依赖。这意味着将数据表按照函数依赖进行分解,以消除多值依赖和连接依赖。这样可以进一步减少数据冗余和提高数据结构的合理性。
  5. 第四范式(4NF):在BCNF的基础上,消除非平凡的函数依赖。这意味着将数据表按照非平凡的函数依赖进行分解,以消除非平凡的函数依赖。这样可以进一步减少数据冗余和提高数据结构的合理性。
    在应用范式原则设计数据库时,需要仔细分析和评估每个级别的要求,并根据实际情况进行适当的分解和重组。同时,也需要考虑到性能和可维护性的因素,以确保数据库设计既合理又高效。
    四、实践经验与建议
    在实际应用中,遵循范式并不意味着每个数据表都必须严格满足每个级别的要求。有时候为了性能和可维护性的考虑,可能需要适当放宽某些要求。此外,对于某些复杂的数据关系和业务逻辑,可能需要在设计阶段就与业务人员和技术人员密切协作,共同讨论和确定最佳的设计方案。
    总之,数据库设计范式是确保数据库结构合理、高效和可维护的重要原则。通过遵循这些原则并灵活运用它们,可以设计出高质量的数据库系统,从而为应用程序提供稳定、高效和可靠的数据支持。

数据关系

  • 一对一:

    •   {
            "id": 111,
            "name": "qcqcqc",
            "companyId": 222,
            "company": {
                "id": 222,
                "name": "zust",
                "testId": 111,
                "test": {
                    "id": 111,
                    "name": "test"
                }
            }
        }
      
    • 在这个json数据中,一个user对应一个company,一个company对应一个test

  • 一对多

    •   {
            "id": 111,
            "name": "qcqcqc",
            "companyId": 222,
            "company": {
                "id": 222,
                "name": "zust",
                "tests": [
                    {
                        "id": 555,
                        "name": "test3",
                        "companyId": 222
                    },
                    {
                        "id": 666,
                        "name": "test4",
                        "companyId": 222
                    },
                    {
                        "id": 777,
                        "name": "test5",
                        "companyId": 222
                    }
                ]
            }
        }
      
    • 在这个json数据中,companyId定义在test表内,一个company对应多个test

  • 多对多

    •   {
            "id": 111,
            "name": "qcqcqc",
            "companyId": 222,
            "company": {
                "id": 222,
                "name": "zust",
                "testList": [
                    {
                        "id": 111,
                        "name": "test",
                    },
                    {
                        "id": 222,
                        "name": "test2",
                    },
                    {
                        "id": 333,
                        "name": "test3",
                    }
                ],
            }
        }
      
    • 在这个json数据中,test内没有company的id,company内也没有test的id,而是通过关联表来查询二者关系并进行拼接

    • 然后查询三张表来拼接数据

增强的Mybatisplus

在日常增删改查中,存在大量重复劳动,这种数据表拼接成最终数据的方式需要我们大量重复的编写查询和set代码,实在是繁琐

  • EnhanceService

  • 实现了IService接口,实现与ServiceImpl相同的逻辑,但是增加了注解处理,可以使用我提供的注解来进行更加快速方便的查询

  •       public T getDeepSearch(T entity) {
              if (entity == null) {
                  return null;
              }
              Class<?> aClass = entity.getClass();
              Field[] declaredFields = aClass.getDeclaredFields();
              for (Field declaredField : declaredFields) {
                  if (declaredField.isAnnotationPresent(OtODeepSearch.class)) {
                      handleOtOAnnotation(entity, declaredField, aClass);
                  }
                  if (declaredField.isAnnotationPresent(MtMDeepSearch.class)) {
                      handleMtMAnnotation(entity, declaredField, aClass);
                  }
                  if (declaredField.isAnnotationPresent(OtMDeepSearch.class)) {
                      handleOtMAnnotation(entity, declaredField, aClass);
                  }
              }
              return entity;
          }
    

一对一查询

  • 使用OtODeepSearch注解,指定查询服务
  • baseId:基础id (类字段名) 在该对象中用于查找目标对象的id信息,默认为注解字段+Id
  • service:查询服务,用于根据id查找目标对象
  • notNull:非空检查,如果为true则在未查询到数据时抛出异常

一对多查询

  • 使用OtMDeepSearch注解来完成一对多查询
  • field:自身id的类字段名,默认为id
  • baseId:目标表中对应自身id的表字段名
  • service:查询服务,用于查找指定对象列表
  • notNull:非空检查,如果为true则在未查询到数据时抛出异常

多对多查询

  • 使用MtMDeepSearch注解来完成多对多查询
  • 多对多查询涉及三张表,相对复杂
  • baseId:基础id (表字段名)关联中表示自己id的字段名
  • targetId:目标id(类字段名)关联对象中表示目标id的字段名
  • relaService: 关联服务,用来查询关联信息
  • targetService:目标服务,用来查询目标对象列表
  • notNull:非空检查,如果为true则在未查询到数据时抛出异常

测试

让我们来构建一个复杂查询,包含一对一,一对多,多对多三种查询方式

查询测试:

测试结果:

demo下载地址:https://raw.githubusercontent.com/pqcqaq/imageSource/main/static/test-demo.7z