HarmonyOS NEXT通过关系型数据库实现数据的持久化

场景介绍

应用中,我们有些数据比较复杂,而且需要频繁的增删改查,这时候就不适合使用首选项来进行存储和管理了,HarmonyOS NEXT中的关系型数据库基于SQLite组件,适用于存储包含复杂关系数据。

基本概念

  • 谓词:数据库中用来代表数据实体的性质、特征或者数据实体之间关系的词项,主要用来定义数据库的操作条件。

  • 结果集:指用户查询之后的结果集合,可以对数据进行访问。结果集提供了灵活的数据访问方式,可以更方便地拿到用户想要的数据

运作机制

关系型数据库对应用提供通用的操作接口,底层使用SQLite作为持久化存储引擎,支持SQLite具有的数据库特性,包括但不限于事务、索引、视图、触发器、外键、参数化查询和预编译SQL语句。

约束限制

  • 系统默认日志方式是WAL(Write Ahead Log)模式,系统默认落盘方式是FULL模式。

  • 数据库中有4个读连接和1个写连接,线程获取到空闲读连接时,即可进行读取操作。当没有空闲读连接且有空闲写连接时,会将写连接当做读连接来使用。

  • 为保证数据的准确性,数据库同一时间只能支持一个写操作。

  • 当应用被卸载完成后,设备上的相关数据库文件及临时文件会被自动清除。

  • ArkTS侧支持的基本数据类型:number、string、二进制类型数据、boolean。

  • 为保证插入并读取数据成功,建议一条数据不要超过2M。超出该大小,插入成功,读取失败。

开发步骤

  1. 通过getRdbStore获取一个relationalStore.RdbStore类型的对象,用于管理数据库,对数据库的建库,建表,升降级等操作。
  2. 获取到RdbStore后可以调用execute方法创建表,execute接收一条SQL语句。
  3. 调用insert方法插入一条数据或者使用batchInsert批量插入数据。
  4. 根据谓词指定的实例对象,对数据进行修改或删除。
  5. 使用ResultSet获取执行完操作的查询结果。

1.创建RdbStore类型的对象,用于对数据库的建库,建表,升降级等操作

TypeScript 复制代码
export class DBUtils {
  appRDB?: relationalStore.RdbStore;
  // 创建数据库
  creatRDB(context: Context) {
    const STORE_CONFIG: relationalStore.StoreConfig = {
      name: "projectRdb.db",//数据库文件名
      securityLevel: relationalStore.SecurityLevel.S1,// 数据库安全级别
      encrypt: false, // 可选参数,指定数据库是否加密,默认不加密
      customDir: 'dbDir', // 可选参数,数据库自定义路径。数据库将在如下的目录结构中被创建:context.databaseDir + '/rdb/' + dbDir,其中context.databaseDir是应用沙箱对应的路径,
      // '/rdb/'表示创建的是关系型数据库,dbDir表示自定义的路径。当此参数不填时,默认在本应用沙箱目录下创建RdbStore实例。
      isReadOnly: false // 可选参数,指定数据库是否以只读方式打开。该参数默认为false,表示数据库可读可写。该参数为true时,只允许从数据库读取数据,不允许对数据库进行写操作,否则会返回错误码801。

    };

    relationalStore.getRdbStore(context, STORE_CONFIG, (error: BusinessError, store: relationalStore.RdbStore) => {
      this.appRDB = store;
      if (error) {
        hilog.error(0x0000, TAG, `Get RdbStore failed, code is ${error.code}, message is ${error.message}`);
        return;
      }
      if (store.version === 0) {
        //创建表
        this.createUserTable();
        store.version = 1;
      }
    });
  }
}

2.通过execute创建表

TypeScript 复制代码
  // 创建用户表
  createUserTable() {
    this.appRDB?.execute(CommonConstants.CREATE_USER_TABLE_SQL).then(() => {
      hilog.info(0x0000, TAG, `execute create user table sql success`);
      //初始化表
      this.initTable();
    }).catch((error: BusinessError) => {
      hilog.error(0x0000, TAG, `execute sql failed, code is ${error.code}, message is ${error.message}`);
    });
  }

3.调用insert方法插入一条数据或者使用batchInsert批量插入数据

假设需要创建一个学生数据表,刚刚建表完成后,我们现在批量插入一批数据,批量插入需要传入表名,和一个List<ValuesBucket>类型的参数,代码如下:

TypeScript 复制代码
//初始化表
  initTable() {
    const user1: relationalStore.ValuesBucket = {
      'ID': 0,
      'NAME': 'Jack',
      'AGE': 18,
      'SCORE': 90,
    }
    const user2: relationalStore.ValuesBucket = {
      'ID': 1,
      'NAME': 'Tonny',
      'AGE': 19,
      'SCORE': 100,
    }
    const user3: relationalStore.ValuesBucket = {
      'ID': 2,
      'NAME': 'kirk',
      'AGE': 20,
      'SCORE': 60,
    }
    let valueBuckets = new Array(user1, user2, user3);
    //向表中批量添加数据,关系型数据库没有显式的flush操作实现持久化,数据插入即保存在持久化文件。
    this.appRDB?.batchInsert('USER', valueBuckets).then((insertNum: number) => {
      hilog.info(0x0000, TAG, `Insert is successful, rows number : ${insertNum}`);
    }).catch((error: BusinessError) => {
      hilog.error(0x0000, TAG, `Insert is failed, code is ${error.code},message is ${error.message}`);
    })
  }

如果是插入单挑数据可以用insert,代码如下:

TypeScript 复制代码
  //插入一条数据
  async insertUser() {
    const user: relationalStore.ValuesBucket = {
      'NAME': 'mutang',
      'AGE': 8,
      'SCORE': 100
    };
    await this.appRDB?.insert('USER', user,
      relationalStore.ConflictResolution.ON_CONFLICT_REPLACE).then((rowId: number) => {
      hilog.info(0x0000, TAG, `Insert is successful, rowId = ${rowId}`);
    }).catch((error: BusinessError) => {
      hilog.error(0x0000, TAG, `Insert is failed, code is ${error.code},message is ${error.message}`);
    })
  }

4.根据谓词指定的实例对象,对数据进行修改或删除

首先使用RdbPredicates确定数据库操作条件,然后根据筛选条件来执行修改或者删除

TypeScript 复制代码
 async updateUser(id:number,score:number){
    const tempDate:relationalStore.ValuesBucket ={
      'SCORE':score
    }
    //使用RdbPredicates确定数据库操作条件
    let predicates = new relationalStore.RdbPredicates('USER');
    predicates.equalTo('ID',id);
    //4.执行数据库操作
    this.appRDB?.update(tempDate,predicates,relationalStore.ConflictResolution.ON_CONFLICT_REPLACE).then(async (rows:Number)=>{
      hilog.info(0x0000, TAG, `Updated row count: ${rows}`);
    }).catch((error:BusinessError)=>{

    })
  }
  async deletePlan(planID: number) {
    //使用RdbPredicates确定数据库操作条件
    let predicates = new relationalStore.RdbPredicates('USER');
    predicates.equalTo('ID', planID);
    await this.appRDB?.delete(predicates).then((rows: Number) => {
      hilog.info(0x0000, TAG, `Delete rows: ${rows}`);
    }).catch((err: BusinessError) => {
      hilog.error(0x0000, TAG, `Delete failed, code is ${err.code},message is ${err.message}`);
    })
  }

5.使用ResultSet获取执行完操作的查询结果

查询数据库表大概步骤如下:

  1. 在回调函数中会返回一个ResultSet类型的值。
  2. 我们可以通过goToNextRow进行遍历,获取到一行数据后,可以使用getColumnIndex()方法,参数传入数据表中的字段名,即可获取到该字段名对应的索引值,然后通过getValue()方法传入字段索引,即可获取到该字段名对应的值,getValue()的返回值类型是ValueType,我们可以将其强制类型转化为具体的数据类型。
  3. 再将取出的数据保存至实体类中,将所有的实体类对象存储在实体类数组中。
  4. 关闭ResultSet,释放资源并返回实体类数组。
TypeScript 复制代码
//查询所有用户名数据
  async queryAllUsers(): Promise<Userinfo[]> {
    let userList: Array<Userinfo> = [];
    //查询数据
   await this.appRDB?.querySql(CommonConstants.QUERY_ALL_NAME_SQL).then((resultSet: relationalStore.ResultSet) => {
      //使用ResultSet获取执行完操作的查询结果
      while (resultSet.goToNextRow()) {
        const id = resultSet.getValue(resultSet.getColumnIndex('ID')) as number;
        const name = resultSet.getValue(resultSet.getColumnIndex('NAME')) as string;
        const age = resultSet.getValue(resultSet.getColumnIndex('AGE')) as number;
        const score = resultSet.getValue(resultSet.getColumnIndex('SCORE')) as number;
        //将所有的实体类对象存储在实体类数组中
        userList.push(new Userinfo(id, name, age,score));
      }
      //关闭ResultSet,释放资源
      resultSet.close();
    },(error:BusinessError)=>{
      hilog.error(0x0000, TAG, `Query failed, code is ${error.code},message is ${error.message}`);
    })
    //将该实体类数组作为该方法的返回值返回
    return userList;
  }

项目实践

实践背景:现有一批学员信息,包含学员姓名,年龄,成绩。满足对学员信息的增删改查

首先在UIAbliity中初始化数据库

TypeScript 复制代码
import { AbilityConstant, ConfigurationConstant, UIAbility, Want } from '@kit.AbilityKit';
import { hilog } from '@kit.PerformanceAnalysisKit';
import { window } from '@kit.ArkUI';
import DBUtils from '../utils/DBUtils'
export default class EntryAbility extends UIAbility {
  onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
    this.context.getApplicationContext().setColorMode(ConfigurationConstant.ColorMode.COLOR_MODE_NOT_SET);
    hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onCreate');
  }

  onDestroy(): void {
    hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onDestroy');
  }

  onWindowStageCreate(windowStage: window.WindowStage): void {
    // Main window is created, set main page for this ability
    hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onWindowStageCreate');
    windowStage.loadContent('pages/Index', (err) => {
      if (err.code) {
        hilog.error(0x0000, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err) ?? '');
        return;
      }
      hilog.info(0x0000, 'testTag', 'Succeeded in loading the content.');
    });
    //创建数据库
    DBUtils.creatRDB(this.context)
  }

  onWindowStageDestroy(): void {
    // Main window is destroyed, release UI related resources
    hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onWindowStageDestroy');
  }

  onForeground(): void {
    // Ability has brought to foreground
    hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onForeground');
  }

  onBackground(): void {
    // Ability has back to background
    hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onBackground');
  }
}

上面的开发步骤中,其实已经将大部分功能完成了,我们接下来要做的就是页面该如何调用,以及效果展示

在开发步骤中,获取到RdbStore对象后进行了创建表,而后初始化了数据,所以我们UI展示只需要调用查询所有数据的方法:

TypeScript 复制代码
@State plansSet: Array<Userinfo> = [];

  async aboutToAppear(): Promise<void> {
    await DBUtils.queryAllUsers().then((value) => {
      this.plansSet = value;
    });
  }

页面效果如下:

点击新增学员,这里为了方便,我就不做编辑信息的UI了

TypeScript 复制代码
 Button('新增学员')
          .width('100%')
          .margin({ bottom: '10vp' }).onClick(async() => {
          await DBUtils.insertUser().then(() => {
            DBUtils.queryAllUsers().then((value) => {
              this.plansSet = value;
            });
          });
        })

执行完插入操作后调用查询所有信息的方法,最新的结果会更新在UI上,效果如下:

修改:修改学员ID为0的成绩为100分:

TypeScript 复制代码
Button('修改第一名学员成绩为100分')
          .width('100%')
          .margin({ bottom: '10vp' }).onClick(async() => {
          await DBUtils.updateUser(0,100).then(() => {
            DBUtils.queryAllUsers().then((value) => {
              this.plansSet = value;
            });
          });
        })

执行效果如下:

删除:

TypeScript 复制代码
 await DBUtils.deletePlan(0).then(() => {
            DBUtils.queryAllUsers().then((value) => {
              this.plansSet = value;
            });
          });

效果如下:

总结

鸿蒙关系型数据库的开发步骤和简单使用就到这了,还有更多的API和高阶使用请参考文档

相关推荐
水深火乐1 分钟前
HmarkX(码笺)的本地数据库模块重构实战
harmonyos
大锅盖17 分钟前
Map 搜索没有返回可信候选时,派单页面该怎么继续工作
华为·harmonyos
长江&后浪16 分钟前
华为wlan实验-三层旁挂
运维·服务器·网络·华为
HarmonyOS_SDK25 分钟前
HarmonyOS SDK API诊断 Skill,一键定位功能故障问题
harmonyos
做萤石二次开发的哈哈1 小时前
萤石蓝海AIoT一站式工作台计费说明
网络·数据库·萤石开放平台·蓝海aiot一站式工作台
竹枝溪1 小时前
MySQL数据库零基础入门
数据库·sql·mysql·navicat·ddl·dml·dql
龙仔7251 小时前
人大金仓Kingbase V8 自动全量备份部署完整笔记
数据库·笔记·备份·人大金仓
王志来137944730081 小时前
从选型到交付,工控服务器机箱一站式采购平台匀天如何实现服务闭环?
数据库
大锅盖11 小时前
Canvas 签名板里的 antialias 开关,怎样确保它真正触发重绘
华为·harmonyos
ShuiShenHuoLe1 小时前
HmarkX(码笺)的本地数据库模块重构实战
数据库·oracle·重构