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

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类型的对象,用于对数据库的建库,建表,升降级等操作

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创建表

  // 创建用户表
  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>类型的参数,代码如下:

//初始化表
  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,代码如下:

  //插入一条数据
  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确定数据库操作条件,然后根据筛选条件来执行修改或者删除

 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,释放资源并返回实体类数组。
//查询所有用户名数据
  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中初始化数据库

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展示只需要调用查询所有数据的方法:

@State plansSet: Array<Userinfo> = [];

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

页面效果如下:

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

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

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

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

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

执行效果如下:

删除:

 await DBUtils.deletePlan(0).then(() => {
            DBUtils.queryAllUsers().then((value) => {
              this.plansSet = value;
            });
          });

效果如下:

总结

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


http://www.kler.cn/a/560786.html

相关文章:

  • 计算机网络-面试总结
  • 企业财务数据分析-投资回报指标ROA
  • Unity模拟零件拆解组装
  • AI时代的前端开发学习:效率提升与学习曲线
  • Nginx中$http_host、$host、$proxy_host的区别
  • 【蓝桥杯单片机】客观题
  • linux 命令+相关配置记录(持续更新...)
  • Unity中一个节点实现植物动态(Shader)
  • Linux | UDP Socket 编程(C++ 基础demo)
  • Java 集合框架大师课:集合流式编程革命(三)
  • C++双指针:算法优化的“左右互搏术”与高效问题破解全指南
  • 【HeadFirst系列之HeadFirst设计模式】第8天之适配器模式与外观模式:让不兼容的接口和谐共处!
  • 服务器租用的价格受哪些因素影响?
  • Kafka面试题----如何保证Kafka消费者在消费过程中不丢失消息
  • 深入理解 Kafka 主题分区机制
  • 基于 Python 和 Django 的文本情感分析系统设计与实现
  • 计算机毕业设计SpringBoot+Vue.js网上租赁系统(源码+文档+PPT+讲解)
  • 微相E316实现FM电台监听
  • DirectX12(D3D12)基础教程三 线性代数与3D世界空间
  • istio介绍补充以及使用篇