鸿蒙学习手册(HarmonyOSNext_API16)_数据持久化③:关系型数据库

概述

关系型数据库:像“Excel表格联合作战”的管家

关系型数据库就像一个超级智能的表格管理系统,专门处理数据之间有复杂关联的情况。比如学生和成绩、订单和商品、用户和评论——这些数据像蜘蛛网一样相互连接,用键值数据库的“独立抽屉”会手忙脚乱,而关系型数据库能像拼积木一样把它们严丝合缝地组装起来。


🌰 举个接地气的例子

场景1:班级学生管理系统

假设你是班主任,需要管理:

  1. 学生表(学号、姓名、性别)
  2. 课程表(课程编号、课程名称、任课老师)
  3. 成绩表(学号、课程编号、分数)

如果用键值数据库

  • 存储像这样👇(数据冗余且难关联):
    键: "学生_1001" → 值: {姓名:"张三", 性别:"男", 数学成绩:90, 英语老师:"王老师"}  
    键: "学生_1002" → 值: {姓名:"李四", 性别:"女", 数学成绩:85, 英语老师:"王老师"}  
    

问题:王老师改名时,需要遍历所有学生修改,效率极低。

用关系型数据库

  • 三张表互联,用“学号”“课程编号”作为桥梁:
    学生表课程表成绩表
    学号:1001 张三 男课程:C001 数学 张老师学号:1001 C001 90
    学号:1002 李四 女课程:C002 英语 王老师学号:1002 C001 85
  • 需要查“张三的数学成绩”时,只需一句SQL:
    SELECT 分数 FROM 成绩表  
    WHERE 学号=(SELECT 学号 FROM 学生表 WHERE 姓名='张三')  
    AND 课程编号=(SELECT 课程编号 FROM 课程表 WHERE 课程名称='数学');  
    

优势:改老师名字只需动课程表一处,所有关联数据自动生效。


场景2:电商订单系统

一个订单可能涉及:用户信息、商品详情、收货地址、支付记录。

  • 键值数据库的困局
    如果把整个订单存成一个Value:
    {  "订单号": "202310011234",  "用户": {"用户ID": 789, "姓名": "王五", "手机": "13800138000"},  "商品": [  {"商品ID": "G1001", "名称": "手机", "价格": 2999},  {"商品ID": "G1002", "名称": "耳机", "价格": 199}  ]  
    }  
    

痛点:想统计“所有买过手机的用户的手机号”,需要扫描所有订单,解析JSON,效率极低。

  • 关系型数据库的解法
    用户表商品表订单表订单详情表
    用户ID 姓名 手机商品ID 名称 价格订单ID 用户ID 总金额订单ID 商品ID 数量
  • 查“买过手机的用户的手机号”
    SELECT 用户表.手机  
    FROM 用户表  
    JOIN 订单表 ON 用户表.用户ID = 订单表.用户ID  
    JOIN 订单详情表 ON 订单表.订单ID = 订单详情表.订单ID  
    JOIN 商品表 ON 订单详情表.商品ID = 商品表.商品ID  
    WHERE 商品表.名称 = '手机';  
    

优势:像拼乐高一样关联多表,直接精准定位数据。


🤔 什么时候该用关系型数据库?

  1. 数据像家族族谱:需要理清“谁是谁的谁”(如用户→订单→商品)
  2. 经常要“跨界”查询:比如“统计每个班级的平均分,并显示班主任名字”
  3. 需要强一致性:比如转账操作(A账户减钱,B账户加钱必须同时成功)

⚠️ 什么时候别用它?

  1. 数据像散装沙子:比如独立缓存项(今日天气、临时验证码)
  2. 每秒要处理10万+请求:比如微博热搜实时计数(适合用Redis)
  3. 数据结构天天变:比如快速迭代中的实验性功能配置(适合用文档数据库如MongoDB)

现实中的工具

  • SQLite:手机APP本地存储(如微信聊天记录)、小型软件
  • MySQL:淘宝早期用户系统、知乎问答数据
  • PostgreSQL:苹果的iCloud部分服务、地理信息系统

// 导入关系型数据库模块和业务错误模块
import { relationalStore } from '@kit.ArkData';
import { BusinessError } from '@kit.BasicServicesKit';// 定义数据库配置
const STORE_CONFIG: relationalStore.StoreConfig = {name: 'RdbTest.db', // 数据库文件名securityLevel: relationalStore.SecurityLevel.S3, // 数据库安全级别encrypt: false, // 是否加密数据库,默认为不加密customDir: 'customDir/subCustomDir', // 自定义数据库路径(可选)isReadOnly: false // 是否以只读方式打开数据库,默认为可读写
};// 定义创建表的 SQL 语句
const SQL_CREATE_TABLE ='CREATE TABLE IF NOT EXISTS EMPLOYEE (ID INTEGER PRIMARY KEY AUTOINCREMENT, NAME TEXT NOT NULL, AGE INTEGER, SALARY REAL, CODES BLOB, IDENTITY UNLIMITED INT)'; // 创建 EMPLOYEE 表// 定义插入数据的值
let value1 = 'Lisa';
let value2 = 18;
let value3 = 100.5;
let value4 = new Uint8Array([1, 2, 3, 4, 5]);
let value5 = BigInt('15822401018187971961171');let value6 = 'Rose';
let value7 = 22;
let value8 = 200.5;
let value9 = new Uint8Array([1, 2, 3, 4, 5]);
let value10 = BigInt('15822401018187971967863');// 定义插入的数据对象
const valueBucket1: relationalStore.ValuesBucket = {'NAME': value1,'AGE': value2,'SALARY': value3,'CODES': value4,'IDENTITY': value5,
};const valueBucket5: relationalStore.ValuesBucket = {NAME: value6,AGE: value7,SALARY: value8,CODES: value9,IDENTITY: value10,
};// 定义组件结构
@Entry
@Component
struct RdbStore {context = getContext(this); // 获取上下文store: relationalStore.RdbStore | undefined = undefined; // 数据库实例/*** 插入数据到数据库*/async rdbInsert() {if (this.store !== undefined) {await (this.store as relationalStore.RdbStore).insert('EMPLOYEE', valueBucket1, (err: BusinessError, rowId: number) => {if (err) {console.error(`Failed to insert data. Code:${err.code}, message:${err.message}`); // 插入失败时的日志return;}console.log(`Succeeded in inserting data. rowId:${rowId}`); // 插入成功时的日志});console.log('rdbInsert'); // 调试日志}}/*** 更新数据库中的数据*/async rdbUpdate() {let predicates1 = new relationalStore.RdbPredicates('EMPLOYEE'); // 创建查询条件predicates1.equalTo('NAME', 'Lisa'); // 匹配 NAME 为 'Lisa' 的记录if (this.store !== undefined) {await (this.store as relationalStore.RdbStore).update(valueBucket5, predicates1, (err: BusinessError, rows: number) => {if (err) {console.error(`Failed to update data. Code:${err.code}, message:${err.message}`); // 更新失败时的日志return;}console.log(`Succeeded in updating data. row count: ${rows}`); // 更新成功时的日志});console.log('rdbUpdate'); // 调试日志}}/*** 删除数据库中的数据*/async rdbDelete() {let predicates1 = new relationalStore.RdbPredicates('EMPLOYEE'); // 创建查询条件predicates1.equalTo('NAME', 'Lisa'); // 匹配 NAME 为 'Lisa' 的记录if (this.store !== undefined) {await (this.store as relationalStore.RdbStore).delete(predicates1, (err: BusinessError, rows: number) => {if (err) {console.error(`Failed to delete data. Code:${err.code}, message:${err.message}`); // 删除失败时的日志return;}console.log(`Delete rows: ${rows}`); // 删除成功时的日志});console.log('rdbDelete'); // 调试日志}}/*** 查询数据库中的数据*/async rdbQuery() {let predicates2 = new relationalStore.RdbPredicates('EMPLOYEE'); // 创建查询条件predicates2.equalTo('NAME', 'Lisa'); // 匹配 NAME 为 'Lisa' 的记录if (this.store !== undefined) {await (this.store as relationalStore.RdbStore).query(predicates2, ['ID', 'NAME', 'AGE', 'SALARY', 'IDENTITY'], (err: BusinessError, resultSet) => {if (err) {console.error(`Failed to query data. Code:${err.code}, message:${err.message}`); // 查询失败时的日志return;}console.info(`ResultSet column names: ${resultSet.columnNames}, column count: ${resultSet.columnCount}`); // 查询结果信息while (resultSet.goToNextRow()) { // 遍历查询结果const id = resultSet.getLong(resultSet.getColumnIndex('ID'));const name = resultSet.getString(resultSet.getColumnIndex('NAME'));const age = resultSet.getLong(resultSet.getColumnIndex('AGE'));const salary = resultSet.getDouble(resultSet.getColumnIndex('SALARY'));const identity = resultSet.getValue(resultSet.getColumnIndex('IDENTITY'));console.log(`id=${id}, name=${name}, age=${age}, salary=${salary}, identity=${identity}`); // 输出每条记录的信息}resultSet.close(); // 关闭结果集});console.log('rdbQuery'); // 调试日志}}/*** 获取数据库实例*/async getRdbStore() {console.log('getRdbStore'); // 调试日志await relationalStore.getRdbStore(this.context, STORE_CONFIG, (err, store) => {if (err) {console.error(`Failed to get RdbStore. Code:${err.code}, message:${err.message}`); // 获取数据库失败时的日志return;}console.info('Succeeded in getting RdbStore.'); // 获取数据库成功时的日志store.executeSql(SQL_CREATE_TABLE); // 执行建表语句this.store = store; // 将数据库实例赋值给成员变量});}/*** 页面即将消失时关闭数据库*/async aboutToDisappear() {if (this.store !== undefined) {await this.store.close(); // 关闭数据库}}/*** 页面即将出现时初始化数据库*/async aboutToAppear() {await this.getRdbStore(); // 初始化数据库}/*** 构建页面布局*/build() {Column() {Button("添加数据") // 添加数据按钮.onClick(() => {this.rdbInsert(); // 调用插入方法}).width('100%').margin({ top: 10 });Button("查询数据") // 查询数据按钮.onClick(async () => {this.rdbQuery(); // 调用查询方法}).width('100%').margin({ top: 10 });Button("修改数据") // 修改数据按钮.onClick(async () => {this.rdbUpdate(); // 调用更新方法}).width('100%').margin({ top: 10 });Button("删除数据") // 删除数据按钮.onClick(async () => {this.rdbDelete(); // 调用删除方法}).width('100%').margin({ top: 10 });}.width('100%'); // 设置列宽为 100%}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/74041.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Windows 11 VS Code C/C++ 开发环境搭建——一种尽量“绿色”的方法

我的电脑是Windows 11 系统,安装了Visual Studio Code,在上面搭建C/C开发环境,当然,这需要用到MinGW之流了。作为一个绿色爱好者(帽子除外),我也尽量绿色地架设这样一个环境…… 第一步&#x…

mysql-分区和性能

mysql自身只支持表的横向分区。 常听到开发人员说“”对表做个分区“,然后数据的查询就会快了。这是真的吗?实际上可能跟根本感觉不到查询速度的提升,甚至会发现查询速度急剧下降。因此,在合理使用分区之前,必须了解分…

DeepSeek协助优化-GTX750Ti文物显卡0.65秒卷完400MB float 音频512阶时域FIR

文章目录 1. 学习目的2. 阶段成果2.1 NVVP 性能探查2.2 测试编译环境2.3 测试样例 3 学习过程3.1 提问DeepSeek3.2 最终代码 4. 体会 1. 学习目的 最近在学习cuda,准备给我的taskBus SDR添加CUDA的模块支持,以便可以用PC机压榨山寨 B210那56M的带宽。 因…

RabbitMQ高级特性--TTL和死信队列

目录 1.TTL 1.1设置消息的TTL 1.1.1配置交换机&队列 1.1.2发送消息 1.1.3运行程序观察结果 1.2设置队列的TTL 1.2.1配置队列和交换机的绑定关系 1.2.2发送消息 1.2.3运行程序观察结果 1.3两者区别 2.死信队列 2.1 声名队列和交换机 2.2正常队列绑定死信交换机 …

【JavaEE】UDP数据报套接字编程

目录 网络编程基础 基本概念 发送端和接收端 请求和响应 客户端和服务端 常见的客户端服务端模型 Socket套接字 TCP/UDP特点 Java数据报套接字通信模型(UDP通信) UDP数据报套接字编程 DatagramSocket 1.类定义 2.构造方法 3.核心方法 4.特性说明 DatagramPacke…

Spring Boot 3.4.3 基于 SpringDoc 2 和 Swagger 3 实现项目接口文档管理

在现代企业级应用开发中,前后端分离已成为主流模式,前端负责界面呈现,后端专注提供 RESTful API 接口。然而,接口文档的编写和维护往往是开发过程中的痛点。Spring Boot 3.4.3 结合 SpringDoc 2 和 Swagger 3,为开发者…

构建大语言模型应用:数据准备(第二部分)

本专栏通过检索增强生成(RAG)应用的视角来学习大语言模型(LLM)。 本系列文章 简介数据准备(本文)句子转换器向量数据库搜索与检索大语言模型开源检索增强生成评估大语言模型服务高级检索增强生成 RAG 如上…

Linux 随机数据生成

目录 一. /dev/urandom1.1 dd 命令1.2 head命令1.3 随机字母 二. openssl 命令三. yes命令 一. /dev/urandom ⏹/dev/urandom 是 Linux 和 Unix 系统中的一个特殊文件,它是一个伪随机数生成器,用于提供高吞吐量的随机数据。 1.1 dd 命令 bs1M count10…

项目如何安装本地tgz包并配置局部registry

一、判断包来源是否正确 1. 检查url curl <registry_url>2. 查看包是否存在 npm view <package_name> --registry<registry_url>二、局部registry配置步骤&#xff1a; 1. 全局配置 如果你希望对所有项目生效&#xff0c;可以将这行配置添加到全局.npmr…

QCustomPlot入门

QCustomPlot 是一个基于 Qt 的 C++ 绘图库,专注于高效、美观的 2D 数据可视化。进入QCustomPlot下载页,下载最新的完整包(包含:源码、文档、示例)。 一、核心架构设计 1. 分层架构模型 层级主要组件职责说明用户接口层QCustomPlot 类提供顶层API,管理所有子组件逻辑控制…

C语言快速入门-C语言基础知识

这个c语言入门&#xff0c;目标人群是有代码基础的&#xff0c;例如你之前学过javaSE&#xff0c;看此文章可能是更有帮助&#xff0c;会让你快速掌握他们之间的差异&#xff0c;文章内容大部分都是泛谈&#xff0c;详细的部分我会在之后时间发布&#xff0c;我也在慢慢学习&am…

【商城实战(91)】安全审计与日志管理:为电商平台筑牢安全防线

【商城实战】专栏重磅来袭!这是一份专为开发者与电商从业者打造的超详细指南。从项目基础搭建,运用 uniapp、Element Plus、SpringBoot 搭建商城框架,到用户、商品、订单等核心模块开发,再到性能优化、安全加固、多端适配,乃至运营推广策略,102 章内容层层递进。无论是想…

信息安全工程师第 1 章

《信息安全工程师教程(第2版)》第一章 一、网络信息安全基本概念与重要性 网络信息安全定义 狭义:保障信息系统的机密性(C)、完整性(I)、可用性(A)——CIA三性。广义:涵盖国家安全、经济安全、社会安全等的“大安全”。法律依据:《网络安全法》定义网络安全为防范攻…

为什么视频文件需要压缩?怎样压缩视频体积即小又清晰?

在日常生活中&#xff0c;无论是为了节省存储空间、便于分享还是提升上传速度&#xff0c;我们常常会遇到需要压缩视频的情况。本文将介绍为什么视频需要压缩&#xff0c;压缩视频的好处与坏处&#xff0c;并教你如何使用简鹿视频格式转换器轻松完成MP4视频文件的压缩。 为什么…

网络空间安全(45)PHP入门学习

一、PHP文件与结构 PHP文件扩展名&#xff1a;PHP文件通常以.php作为扩展名&#xff0c;例如index.php。 PHP代码嵌入&#xff1a;PHP代码可以嵌入到HTML文件中&#xff0c;通常使用<?php ... ?>标签包围PHP代码。短标签<? ... ?>在某些配置下也可以使用&…

深入 OpenPDF:高级 PDF 生成与操作技巧

1 引言 1.1 项目背景 在许多企业级应用中,生成和操作 PDF 文档是一个常见的需求。PDF(Portable Document Format)因其格式统一、易于打印和分发而被广泛使用。本文将介绍如何使用 OpenPDF 库在 Java 项目中生成和操作 PDF 文档。 1.2 技术选型理由 OpenPDF:OpenPDF 是一…

力扣hot100——最长连续序列(哈希unordered_set)

题目链接&#xff1a;最长连续序列 1、错解&#xff1a;数组做哈希表&#xff08;内存超出限制&#xff09; int longestConsecutive(vector<int>& nums) {vector<bool> hash(20000000010, false);for(int i0; i<nums.size();i){hash[1000000000nums[i]]t…

Qt中信号带参传值

在我们的Qt信号中是可以进行参数的传递的&#xff0c;不过格式上与写普通函数不同。 这是头文件中定义一个含参信号和一个含参槽函数 我们再来看它们两个的绑定 。第一行的clicked()和on_btn_clicked()就是普通无参信号和槽的绑定&#xff1b;第二行就是上图中两个带参信号和槽…

CSS3学习教程,从入门到精通, CSS3 列表控制详解语法知识点及案例代码(24)

CSS3 列表控制详解 CSS 列表控制的语法知识点及案例代码的详细说明&#xff0c;包括 list-style-type、list-style-image、list-style-position 和 list-style 的用法。 1. list-style-type 属性 list-style-type 属性用于设置列表项标记的类型。 语法 list-style-type: v…

用Deepseek写扫雷uniapp小游戏

扫雷作为Windows系统自带的经典小游戏&#xff0c;承载了许多人的童年回忆。本文将详细介绍如何使用Uniapp框架从零开始实现一个完整的扫雷游戏&#xff0c;包含核心算法、交互设计和状态管理。无论你是Uniapp初学者还是有一定经验的开发者&#xff0c;都能从本文中获得启发。 …