当前位置: 首页 > news >正文

学生信息管理系统开发实战:掌握多数据模型关联关系的设计和使用

前言

我们日常使用的业务系统,核心都是围绕数据展开,基于数据变化出无穷的可能。本篇文章将基于《学生信息管理系统》这样浅显易懂的场景,介绍如何设计和创建模型,如何在多模型之间建立复杂的关联关系,以及如何在云开发平台中实际操作数据。

1. 数据模型设计范式

1.1 关系型数据库设计范式

数据模型就是基于业务的深刻理解抽象出数据存储的框架,最终落实到实际使用中使数据的读写具有可靠性、扩展性和高效率,从而提升生产效率带来效益。

在传统业务应用开发过程中,首先最重要的是对数据库做好设计构建,其理论依据则是上世纪 70 年代提出的“数据库三范式”:

  • 第一范式(1NF)表中的每一列都是不可拆分的,即保证列的原子性。

  • 第二范式(2NF)表中必须存在主键,且普通字段必须和主键相关,即保证主键列的完全依赖。

  • 第三范式(3NF)表中非主键字段不应互相依赖,即避免依赖传递。

随着时代向前演进,为了满足日益复杂的业务系统要求,范式成员中又陆续新增了BCNF、4NF、5NF 等更多的范式,高阶范式一定满足低阶范式要求且范式设计越高阶,表的精细化越高,冗余度就越低。

图片

1.2 反范式设计

既然数据库范式是减少冗余,那顾名思义,“反范式”就是增加冗余。事实上,在面对有些业务场景时,过于追求范式设计,会将拆分更多原子表,在数据整合时也会更多使用联表操作,联表本身就带来了复杂性和性能损耗,所以适当增加冗余反而更能高效率的完成查询任务,是一种“用空间换时间”的做法。

冗余,在提高查询性能的同时会增加数据写入的难度,通常需要双写或多写来保证冗余字段的一致性问题,所以开发者应精准识别业务中可提升性能、有价值的字段进行反范式设计。

1.3 数据模型设计范式

综上所述,数据模型设计范式基本沿用关系型数据库范式:将表抽象为模型,将列抽象为字段,按照具体业务需求合理设置模型中的字段,系统已为每个模型固定内置了主键 “_id” 作为数据标识,开发者无需关心主键,只需要在模型中创建模型直接依赖的字段即可,适当增加冗余。

2. 数据模型创建与关联关系定义

接下来,我们以《学生信息管理系统》为需求背景,从数据库E-R设计延伸出数据模型设计,直到生产中如何使用模型操作数据。

说明:以下截图均来自云后台数据管理界面,点击阅读原文登录

2.1 业务模型 E-R 图

《学生信息管理系统》主要做学生相关数据管理,其中包含多对一、多对多和一对一关系,如下图所示:

图片

2.2 创建模型

基于业务需求,我们整理成表格信息,首先我们先依次创建出每个模型单体,不考虑关联关系。说明:

  • 红色表示多对一关系

  • 绿色表示多对多关系

  • 黄色表示一对一关系

  • 关联关系方便展示,本小节先不关心,在下一小节使用

学生班级课程学籍信息
姓名名称编号编号
年龄年级名称学籍所在地
性别
所在班级班内学生
所学课程选课学生
学籍档案绑定学生

创建模型时,统一使用云后台-数据管理-从空白创建-云开发MySQL数据库

图片

如果mysql数据库未初始化,可点击下方的初始化按钮,再继续操作

图片

  • 创建学生模型

图片

如果没有性别枚举,可以在创建时新建选项集

图片

  • 创建班级模型

图片

  • 创建课程模型

图片

  • 创建学籍信息模型

图片

2.3 创建关联关系

接下来我们来为模型建立关联关系,在云开发数据管理中关联关系是成对出现的,例如在学生和班级关系中(多对一关系),班级是学生的父模型(一方),那么学生就是班级的子模型(多方)。当我们成功为学生模型创建多对一关系关联班级模型后,班级模型中就会出现一对多关系关联学生模型。此时关联关系字段会自动关联目标模型的数据标识,即主键。

说明:

  1. 在编辑模式下,可以通过”添加一列“来继续创建关系字段

  2. 为了方便后续直观识别关系字段,我们将关系字段命名按照两个模型标识表示,如学生关联班级的字段是student_class,那么班级中成对的字段就是class_student,其他关系如是

  3. 在建立关系时,一定要分清当前主模型和关联模型的父子关系,从而选择对应字段类型

  • 创建学生-班级多对一关系

图片

  • 创建学生-课程多对多关系

图片

  • 创建学生-学籍一对一关系

图片

  • 学生关联关系概览

图片

  • 班级、课程、学籍中对应的关系

图片

图片

图片

将所有的模型都创建完毕,物理层的数据库存储也伴随模型而创建,接下来对数据进行操作。

3. 数据模型的物理意义

数据模型是业务需求的抽象,属于逻辑层含义,但实际进行数据存储和处理的还是物理层的数据库,为了更形象的解释其对应关系,我们采用关系型数据库 SQL 作为参照说明。

以下给出的 SQL 仅为了解释映射关系,而非实际存储

  • 数据模型到物理存储

图片

有同学肯定会问:既然模型字段和数据库列是一一对应的,为什么还需要数据模型,直接操作DB岂不更加直接明了?

  1. 首先,模型更容易理解更贴近自然语言,就好比面向对象语言之于汇编语言、 AIGC 之于人工智能、矿泉水之于冰山融雪...一一对应只是在当前业务场景下,更复杂的企业级模型可不一定哦

  2. 其次,模型更贴近业务的使用者而非底层逻辑的实现者,两者点亮的技能树不同

  3. 最后,模型身处逻辑层,屏蔽了来自物理层数据库的复杂设计和使用,最重要的是模型可以对接纷繁复杂眼花缭乱的各路数据库产品,云开发为广大开发者不仅提供了关系型数据库MySQL,还包括 NoSQL 数据库选项,都可以通过同一种模型接入

  • 关联关系物理意义

  1. 前文不断强调要识别多对一和一对多关系中的父子模型概念,在数据模型中,这两者关系都是通过子模型的关联字段来维护的。比如“学生-班级”是多对一关系,学生是子模型班级是父模型,那么关联关系值的物理存储位置一定是子模型学生的关联列student_class

  2. 从上图 SQL 可以看出,在多对多关系中,关联关系并不维护在某一方模型,而是会有中间模型来维护,该中间模型不具有业务属性,而仅仅作为多对多关系维系的纽带,所以它并不开放给开发者

  3. 一对一 关系是特殊的多对一(或一对多)关系,物理存储位置同后者

以上,我们只是对模型和物理存储做浅尝即止的解释,为求在模型设计和数据操作时有更直观的理解,接下来我们利用创建好的模型做实机数据演示。

4. 数据操作

4.1 创建数据

  • 通过控制台,依次创建班级、课程、学生数据,最后创建学籍数据并绑定学生信息

图片

图片

图片

图片

  • 如果使用sdk创建可参考下列方式,请点击使用文档 https://docs.cloudbase.net/model/introduce/

// 创建班级
const { data } = await models.class.createMany({data: [{name: "1班",  grade: "一年级"},{name: "2班",  grade: "一年级"}
],
});// 创建课程
const { data } = await models.course.createMany({data: [{name: "语文",  code: "001"},{name: "数学",  code: "002"}
],
});// 创建学籍信息
const { data } = await models.profile.createMany({data: [{code: "01",  address: "北京朝阳区"},{code: " 02",  address: "上海虹桥区"}
],
});// 创建学生
const { data } = await models.student.createMany({data: [{"name": "小明","birth": 6,"gender": "1",// 建立学籍一对一关系,_id为学籍数据标识"student_profile": {"_id": "9ZRF3VHQR6"},// 建立班级多对一关系,_id为班级数据标识"student_class": {"_id": "9ZREB4QKDS"},// 建立课程多对多关系,_id为课程数据标识"student_course": [{"_id": "9ZREE51ERE"},{"_id": "9ZREDFX4G8"}]},],
});
  • 为了方便在控制台查看,重新编辑模型,将学生姓名、班级名称、课程名称、学籍编号设置为“主展示列”。

图片

我们创建好的两条学生数据如下:

姓名

年龄性别学籍档案所在班级

所学

课程

数据标识
小明6022班

语文、数学

9ZREQJ0MPW
小红6011班语文、数学9ZREUB0FJ0

图片

4.2 数据查询

用如下几个场景介绍关联关系的查询。

  • 查询一年级 2 班所有的男生信息,包括班级、学籍和学习课程信息。

const { data } = await models.student.list({
// 展示参数,如果只展示当前模型非关联数据,可以使用 select:{$master:true}select: {_id: true,name: true,birth: true,gender: true,// 展示结果包含关联课程信息(多对多)student_course: {name: true,code: true},// 展示结果包含班级信息(多对一)student_class: {name: true,grade: true},// 展示结果包含学籍信息(一对一)student_profile: {code: true,name: true}},// 筛选条件,where指当前主模型条件;relateWhere指当前关联模型条件,其中relateWhere内的第一级参数为当前模型的关联字段,where内字段为关联模型字段filter: {// 条件:男生where: {$and: [{gender: {$eq: "1"}}]},// 条件:一年级 2 班relateWhere: {student_class:{ // 学生模型关联字段where: {$and:[{grade: {$eq: "一年级"}  // 班级模型字段},{name: {$eq: "2班"}  // 班级模型字段}]}}}},pageSize: 10,   // 分页pageNumber: 1,  // 当前页数getCount: true  // 查询数据总数}
});// 返回查询到的数据列表 records 和 总数 total
console.log(JSON.stringify((data)));// return
//{  
// "records": [  
//   {  
//     "student_class": {"grade": "一年级","name": "2班","_id": "9ZREB4QKDS"},  
//     "gender": "1",  
//     "student_profile": {"code": " 02","_id": "9ZRF3VHQR6"},  
//     "name": "小明",  
//     "birth": 6,  
//     "_id": "9ZREQJ0MPW",  
//     "student_course": [  
//       {  
//         "code": "001",  
//         "name": "语文",  
//         "_id": "9ZREDFX4G8"  
//       },  
//       {  
//         "code": " 002",  
//         "name": "数学",  
//         "_id": "9ZREE51ERE"  
//       }  
//     ]  
//   }  
// ],  
// "total": 1  
//}

查询所有学生信息,按姓名字典序排序

const { data } = await models.student.list({
select: {$master:true  // 展示学生模型所有非关联字段},filter: {where: {}},orderBy:[{name: "DESC"}],pageSize: 10,pageNumber: 1,getCount: true});

快来点击这里立即体验吧!

相关文章:

  • 「iOS」——KVC
  • 使用 pypdf 给 PDF 添加目录书签
  • 搜索引擎onesearch3实现解释和升级到Elasticsearch v8系列(四)-搜索
  • 基于Hive和Hadoop的图书分析系统
  • nodejs逐字读取文件示例
  • 防火墙详解(三)华为防火墙基础安全策略配置(命令行配置)
  • 如何恢复被删除的 GitLab 项目?
  • 前端Vue.js与后端Flask/Django协同开发指南
  • 修改DNS地址有什么影响
  • 选择更轻松:山海鲸可视化与PowerBI的深度对比
  • RP2040 C SDK GPIO和IRQ 唤醒功能使用
  • Angular与Vue的全方位对比分析
  • uni-app 封装websocket 心跳检测,开箱即用
  • 原码反码补码移码
  • 快速创建第一个Spring Boot 项目
  • 【跃迁之路】【669天】程序员高效学习方法论探索系列(实验阶段426-2018.12.13)...
  • 5分钟即可掌握的前端高效利器:JavaScript 策略模式
  • Angular 4.x 动态创建组件
  • avalon2.2的VM生成过程
  • Cookie 在前端中的实践
  • Docker容器管理
  • Java 网络编程(2):UDP 的使用
  • JavaScript HTML DOM
  • javascript 总结(常用工具类的封装)
  • Lsb图片隐写
  • MYSQL 的 IF 函数
  • node-glob通配符
  • SegmentFault 社区上线小程序开发频道,助力小程序开发者生态
  • 分享一个自己写的基于canvas的原生js图片爆炸插件
  • 基于MaxCompute打造轻盈的人人车移动端数据平台
  • 实现简单的正则表达式引擎
  • 学习使用ExpressJS 4.0中的新Router
  • 7行Python代码的人脸识别
  • LevelDB 入门 —— 全面了解 LevelDB 的功能特性
  • MPAndroidChart 教程:Y轴 YAxis
  • 国内唯一,阿里云入选全球区块链云服务报告,领先AWS、Google ...
  • ​Kaggle X光肺炎检测比赛第二名方案解析 | CVPR 2020 Workshop
  • ​ssh免密码登录设置及问题总结
  • ​探讨元宇宙和VR虚拟现实之间的区别​
  • # Redis 入门到精通(八)-- 服务器配置-redis.conf配置与高级数据类型
  • $Django python中使用redis, django中使用(封装了),redis开启事务(管道)
  • (06)金属布线——为半导体注入生命的连接
  • (13)DroneCAN 适配器节点(一)
  • (3)llvm ir转换过程
  • (LLM) 很笨
  • (pojstep1.1.1)poj 1298(直叙式模拟)
  • (超简单)使用vuepress搭建自己的博客并部署到github pages上
  • (力扣)1314.矩阵区域和
  • (南京观海微电子)——I3C协议介绍
  • (牛客腾讯思维编程题)编码编码分组打印下标(java 版本+ C版本)
  • (三)elasticsearch 源码之启动流程分析
  • (算法)前K大的和
  • (学习日记)2024.03.25:UCOSIII第二十二节:系统启动流程详解
  • (一)插入排序
  • (一)面试需要掌握的技巧