鸿蒙开发(32)arkTS、通过关系型数据库实现数据持久化封装

应用数据持久化

应用数据持久化,是指应用将内存中的数据通过文件或数据库的形式保存到设备上。

HarmonyOS 标准系统支持典型的存储数据形态,包括用户首选项、键值型数据库、关系型数据库。

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

场景介绍

关系型数据库基于SQLite组件,适用于存储包含复杂关系数据的场景,比如一个班级的学生信息,需要包括姓名、学号、各科成绩等,又或者公司的雇员信息,需要包括姓名、工号、职位等,由于数据之间有较强的对应关系,复杂程度比键值型数据更高,此时需要使用关系型数据库来持久化保存数据。

大数据量场景下查询数据可能会导致耗时长甚至应用卡死,如有相关操作可参考文档批量数据写数据库场景,且有建议如下:

  • 单次查询数据量不超过5000条。
  • 在TaskPool中查询。
  • 拼接SQL语句尽量简洁。
  • 合理地分批次查询。

基本概念

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

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

运作机制

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

图1 关系型数据库运作机制

约束限制

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

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

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

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

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

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

接口说明

以下是关系型数据库持久化功能的相关接口,大部分为异步接口。异步接口均有callback和Promise两种返回形式,下表均以callback形式为例,更多接口及使用方式请见关系型数据库。

接口名称 描述
getRdbStore(context: Context, config: StoreConfig, callback: AsyncCallback): void 获得一个RdbStore,操作关系型数据库,用户可以根据自己的需求配置RdbStore的参数,然后通过RdbStore调用相关接口可以执行相关的数据操作。
executeSql(sql: string, bindArgs: Array, callback: AsyncCallback):void 执行包含指定参数但不返回值的SQL语句。
insert(table: string, values: ValuesBucket, callback: AsyncCallback):void 向目标表中插入一行数据。
update(values: ValuesBucket, predicates: RdbPredicates, callback: AsyncCallback):void 根据predicates的指定实例对象更新数据库中的数据。
delete(predicates: RdbPredicates, callback: AsyncCallback):void 根据predicates的指定实例对象从数据库中删除数据。
query(predicates: RdbPredicates, columns: Array, callback: AsyncCallback):void 根据指定条件查询数据库中的数据。
deleteRdbStore(context: Context, name: string, callback: AsyncCallback): void 删除数据库。

开发步骤

下面是一个购物车封装示例,包含添加,更新,删除,查询。

1、创建数据模型 CartModel.ets

typescript 复制代码
export class CartModel {
  id?:number
  goods_name?:string
  goods_code?:string
  goods_price?:number
  goods_count?:number
  goods_checked?:number
}

2、封装代码 CartDBUtils.ets

typescript 复制代码
import { relationalStore } from '@kit.ArkData'; // 导入模块
import { BusinessError } from '@kit.BasicServicesKit';
import { CartModel } from '../viewmodel/CartModel'

class CartDBUtils {
  private rdbStore: relationalStore.RdbStore | null = null

  /**
   *初始化数据库
   * @param context 上下文对象
   * @param tableName 创建的表格名字
   *
   */
  initCartDB(context: Context, tableName: string) {
    //(1)初始化数据库配置
    const STORE_CONFIG: relationalStore.StoreConfig = {
      name: 'woniumall.db', // 数据库文件名
      securityLevel: relationalStore.SecurityLevel.S1, // 数据库安全级别
      // encrypt: false, // 可选参数,指定数据库是否加密,默认不加密
      // customDir: 'customDir/subCustomDir', // 可选参数,数据库自定义路径。数据库将在如下的目录结构中被创建:context.databaseDir + '/rdb/' + customDir,其中context.databaseDir是应用沙箱对应的路径,'/rdb/'表示创建的是关系型数据库,customDir表示自定义的路径。当此参数不填时,默认在本应用沙箱目录下创建RdbStore实例。
      // isReadOnly: false // 可选参数,指定数据库是否以只读方式打开。该参数默认为false,表示数据库可读可写。该参数为true时,只允许从数据库读取数据,不允许对数据库进行写操作,否则会返回错误码801。
    };
    //(2)定义sql语句
    const SQL_CREATE_TABLE = `CREATE TABLE IF NOT EXISTS ${tableName} (
    ID INTEGER PRIMARY KEY AUTOINCREMENT,
    GOODS_NAME TEXT NOT NULL,
    GOODS_BIG_LOG TEXT,
    GOODS_CODE TEXT,
    GOODS_PRICE REAL,
    GOODS_COUNT INTEGER,
    GOODS_CHECKED INTEGER
    )`;
    //初始化数据库(getRdbStore 异步)
    relationalStore.getRdbStore(context, STORE_CONFIG, (err, store) => {
      if (err) {
        console.error(`获取RdbStore失败. Code:${err.code}, message:${err.message}`);
        return;
      }
      console.info('获取RdbStore成功.');
      this.rdbStore = store
      // 创建数据表
      store.executeSql(SQL_CREATE_TABLE, (error) => {
        if (!error) {
          console.info(`${tableName}创建成功`);
          return;
        } else {
          console.error(`${tableName}创建失败`);
        }
      });
    })
  }

  /**
   *添加到数据库
   * @param tableName
   * 注意传参顺序
   */
  insertDataDB(value: relationalStore.ValuesBucket, tableName: string) {
    if (this.rdbStore !== null) {
      this.rdbStore.insert(tableName, value, (err: BusinessError, rowId: number) => {
        if (err) {
          console.error(`往${tableName}中添加数据失败 ${err.code}-${err.name}-${err.message}`)
          return;
        }
        console.log(`往${tableName}中添加数据成功`)
      })
    }
  }

  /**
   *查询数据库
   * param column 投影列["ID","NAME"]
   * @param tableName 创建的表格名字
   */
  async queryDataDB(column: Array<string>, tableName: string, id?: number) {
    let predicates2 = new relationalStore.RdbPredicates(tableName);
    if (id) {
      //筛选条件指数据
      predicates2.equalTo('ID', id);
    }
    let carts: CartModel[] = []
    const resultSet = await (this.rdbStore as relationalStore.RdbStore).query(predicates2, column)
    console.info(`ResultSet column names: ${resultSet.columnNames}, column count: ${resultSet.columnCount}`);
    // resultSet是一个数据集合的游标,默认指向第-1个记录,有效的数据从0开始。
    while (resultSet.goToNextRow()) {
      const id = resultSet.getLong(resultSet.getColumnIndex('ID'));
      const goods_name = resultSet.getString(resultSet.getColumnIndex('GOODS_NAME'));
      const goods_code = resultSet.getString(resultSet.getColumnIndex('GOODS_CODE'));
      const goods_price = resultSet.getDouble(resultSet.getColumnIndex('GOODS_PRICE'));
      const goods_count = resultSet.getLong(resultSet.getColumnIndex('GOODS_COUNT'));
      const goods_checked = resultSet.getLong(resultSet.getColumnIndex('GOODS_CHECKED'));
      const temp: CartModel = new CartModel()
      temp.id = id
      temp.goods_name = goods_name
      temp.goods_code = goods_code
      temp.goods_price = goods_price
      temp.goods_count = goods_count
      temp.goods_checked = goods_checked
      carts.push(temp)
    }
    // 释放数据集的内存
    resultSet.close();
    return carts
  }

  /**
   *删除
   * @param tableName
   * @param id
   */
  deleteById(tableName: string, id: number,) {
    //获取要操作的对象
    let predicates1 = new relationalStore.RdbPredicates(tableName)
    //通过什么删除
    predicates1.equalTo('ID', id)
    if (this.rdbStore !== null) {
      this.rdbStore.delete(predicates1, (err: BusinessError, rows: number) => {
        if (err) {
          console.error(`删除购物车失败. Code:${err.code}, message:${err.message}`);
          return;
        }
        console.info(`删除购物车成功: ${rows}`);
      })
    }
  }

  /**
   * 修改
   * @param tableName
   * @param id 修改的商品编号
   * @param value 修改的字段
   */
  updateById(tableName: string, id: number, value: relationalStore.ValuesBucket) {
    //获取要操作的对象
    let predicates1 = new relationalStore.RdbPredicates(tableName)
    //通过什么删除
    predicates1.equalTo('ID', id)
    if (this.rdbStore !== null) {
      this.rdbStore.update(value, predicates1, (err: BusinessError, rows: number) => {
        if (err) {
          console.error(`修改购物车失败. Code:${err.code}, message:${err.message}`);
          return;
        }
        console.info(`修改购物车成功: ${rows}`);
      })
    }
  }
}

const cartDBUtils = new CartDBUtils()

export { cartDBUtils }

3、在EntryAbility.ets使用注册初始化仓库

typescript 复制代码
import { cartDBUtils } from '../utils/CartDBUtils'
onWindowStageCreate(windowStage: window.WindowStage): void {
  //加载数据库
  cartDBUtils.initCartDB(this.context, "MYCART")
  //加载窗口
  hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onWindowStageCreate');

  windowStage.loadContent('pages/Page02_GuidePage', (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.');
  });
}

4、添加到购物车

typescript 复制代码
import { cartDBUtils } from '../utils/CartDBUtils'
@Entry
@Component
struct Page03_Detail {
  addCart = () => {
    const valueBucket1: relationalStore.ValuesBucket = {
      'GOODS_NAME': this.detail.goods_name,
      'GOODS_BIG_LOG': this.detail.goods_big_logo,
      'GOODS_CODE': this.detail.goods_id,
      'GOODS_PRICE': Number(this.detail.goods_price),
      'GOODS_COUNT': 1,
      'GOODS_CHECKED': 0,
    }
    //调用户数库,保存数据
    cartDBUtils.insertDataDB(valueBucket1,"MYCART")
  }
  build() {
	  Button('修改').onClick(() => {
	     this.addCart()
	  })
  }
}

5、在Texting.ets使用测试

typescript 复制代码
import { cartDBUtils } from '../utils/CartDBUtils'
import { relationalStore } from "@kit.ArkData"
import {CartModel} from '../viewmodel/CartModel'
// xxx.ets
@Entry
@Component
struct GridExample {
  @State myId: number = 0
  @State count: number = 0
  @State cartList:CartModel[] =[]

  aboutToAppear(): void {
    this.findDataByDB()
  }
  findDataByDB = async ()=>{
    const res = await cartDBUtils.queryDataDB(["ID","GOODS_NAME","GOODS_BIG_LOG","GOODS_CODE","GOODS_PRICE","GOODS_COUNT","GOODS_CHECKED"],"MYCART")
    this.cartList = res
  }
  build() {
    Column() {
      TextInput({ placeholder: "请输入要删除的编号", text: $$this.myId })
      Button('删除').onClick(() => {
        cartDBUtils.deleteById("MYCART", this.myId)
      })

      TextInput({ placeholder: "请输入要修改的编号", text: $$this.myId })
      TextInput({ placeholder: "请输入购买数量", text: $$this.count })
      Button('修改').onClick(() => {
        const updateColum: relationalStore.ValuesBucket = {
          'GOODS_COUNT': this.count
        }
        cartDBUtils.updateById("MYCART", this.myId, updateColum)
      })

      List() {
        ForEach(this.cartList,(item:CartModel)=>{
          ListItem() {
            Column() {
              Text(`ID:${item.id}`)
              Text(`goods_name:${item.goods_name}`)
              Text(`goods_count:${item.goods_count}`)
            }
          }
          .width("100%")
          .height(120)
          .borderRadius(10)
          .backgroundColor(Color.White)
        })
      }
    }
  }
}

参考文档:通过关系型数据库实现数据持久化

相关推荐
jiayou644 小时前
KingbaseES 实战:深度解析数据库对象访问权限管理
数据库
Wect5 小时前
LeetCode 130. 被围绕的区域:两种解法详解(BFS/DFS)
前端·算法·typescript
Dilettante2585 小时前
这一招让 Node 后端服务启动速度提升 75%!
typescript·node.js
jonjia1 天前
模块、脚本与声明文件
typescript
jonjia1 天前
配置 TypeScript
typescript
jonjia1 天前
TypeScript 工具函数开发
typescript
jonjia1 天前
注解与断言
typescript
jonjia1 天前
IDE 超能力
typescript
jonjia1 天前
对象类型
typescript
jonjia1 天前
快速搭建 TypeScript 开发环境
typescript