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

鸿蒙学习手册(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%
  }
}

相关文章:

  • 【 <二> 丹方改良:Spring 时代的 JavaWeb】之 Spring Boot 中的消息队列:使用 RabbitMQ 实现异步处
  • C++ STL常用算法之常用算术生成算法
  • 【区块链安全 | 第十四篇】类型之值类型(一)
  • ShuffleNet、MobileNet 和 EfficientNet的区别
  • 探索OCR的第二个方案:EasyOCR
  • 小智机器人关键函数解析,Application::MainLoop() 用于持续监听事件组中的事件,并根据不同的事件触发相应的操作
  • Android在KSP中简单使用Room
  • Vue.js的多个组件过渡:实现组件的动态切换
  • 互联网的组成
  • C语言信号量使用案例
  • 每日小积累day1
  • TDengine tar.gz和docker两种方式安装和卸载
  • 【蓝桥杯速成】| 16.完全背包组合|排序
  • Rollup系列之安装和入门
  • MQTT之重复消息(6、在项目中遇到的问题)
  • Pandas **Series**
  • 传统策略梯度方法的弊端与PPO的改进:稳定性与样本效率的提升
  • 【干货】前端实现文件保存总结
  • rce操作
  • 唤起“堆”的回忆