鸿蒙自带关系型数据库的使用研究2——数据库操作方法的优化与封装、案例引入

466 阅读8分钟

一、已经解决的问题和仍存在问题

根据上一篇中研究得到的结果,目前还存在部分问题。

已经得出的测试结果:一、二、三、四、五的功能可以正常使用,六、七存在优化空间、八存在一定问题。

1717338757743.png

二、数据库相关知识回顾、理解

首先明确:本文所讲解的是基于鸿蒙自带的SQLLite风格的内置关系型数据库。

(一)应用数据持久化

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

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

  1. 数据库级别概念

关系型数据库(Relational Database,RDB)是一种基于关系模型来管理数据的数据库。关系型数据库基于SQLite组件提供了一套完整的对本地数据库进行管理的机制,对外提供了一系列的增、删、改、查等接口,也可以直接运行用户输入的SQL语句来满足复杂的场景需要。不支持Worker线程。

ArkTS侧支持的基本数据类型:number、string、二进制类型数据、boolean。为保证插入并读取数据成功,建议一条数据不要超过2M。超出该大小,插入成功,读取失败。

该模块提供以下关系型数据库相关的常用功能:

  • RdbPredicates: 数据库中用来代表数据实体的性质、特征或者数据实体之间关系的词项主要用来定义数据库的操作条件。
  • RdbStore提供管理关系数据库(RDB)方法的接口
  • ResultSet:提供用户调用关系型数据库查询接口之后返回的结果集合

(二)接口说明

以下是关系型数据库持久化功能的相关接口,大部分为异步接口。

异步接口均有 callback 和 Promise 两种返回形式,更多接口及使用方式请见关系型数据库

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

(三)数据库操作

1、数据字段

// ValuesBucket 数据库支持的类型
interface NoteItem extends ValuesBucket {
  id: number | null // 新增时设置 id 为空值 null,用于自增 id
  title: string
  content: string
  date_added: number
}

2、初始化数据库-getRdbStore

(1)relationalStore.getRdbStore

getRdbStore(context: Context, config: StoreConfig): Promise

获得一个相关的RdbStore,操作关系型数据库,用户可以根据自己的需求配置RdbStore的参数,然后通过RdbStore调用相关接口可以执行相关的数据操作,使用Promise异步回调。

系统能力: SystemCapability.DistributedDataManager.RelationalStore.Core

参数:

参数名类型必填说明
contextContext应用的上下文。
configStoreConfig与此RDB存储相关的数据库配置。

返回值

类型说明
PromisePromise对象。返回RdbStore对象。

检查数据库文件是否创建成功

/data/app/el2/100/database/包名/entry/rdb

(2)executeSql

executeSql(sql: string, bindArgs?: Array):Promise

执行包含指定参数但不返回值的SQL语句,使用Promise异步回调。

系统能力: SystemCapability.DistributedDataManager.RelationalStore.Core

参数:

参数名类型必填说明
sqlstring指定要执行的SQL语句。
bindArgsArraySQL语句中参数的值。该值与sql参数语句中的占位符相对应。当sql参数语句完整时,该参数不填。

返回值

类型说明
Promise无返回结果的Promise对象。

1717341224692.png

CREATE TABLE IF NOT EXISTS 如果表格不存在则创建表格

INTEGER 整数类型,相当于 number (PS:布尔类型也是 INTEGER 可用 0 1 表示)

TEXT 字符串类型,相当于 string

NOT NULL 不允许空

PRIMARY KEY 主键,唯一标识

AUTOINCREMENT 自增

3、删除数据库-deleteRdbStore

(1)relationalStore.deleteRdbStore

deleteRdbStore(context: Context, name: string): Promise

使用指定的数据库文件配置删除数据库,使用Promise异步回调。

删除成功后,建议将数据库对象置为null。建立数据库时,若在StoreConfig中配置了自定义路径,则调用此接口进行删库无效,必须使用 deleteRdbStore10+ 接口进行删库。

4、新增数据-insert

(1)insert

insert(table: string, values: ValuesBucket):Promise

向目标表中插入一行数据,使用Promise异步回调。由于共享内存大小限制为2Mb,因此单条数据的大小需小于2Mb,否则会查询失败。

5、谓词

RdbPredicates

表示关系型数据库(RDB)的谓词,主要用来定义数据库的操作条件。

使用说明

RdbPredicates 为构造函数类,需要通过 new 关键词创建类的实例,参数为数据库表名

6、查询-query

query(predicates: RdbPredicates, columns?: Array):Promise

根据指定条件查询数据库中的数据,使用Promise异步回调。由于共享内存大小限制为2Mb,因此单条数据的大小需小于2Mb,否则会查询失败。

7、结果集

ResultSet

提供通过查询数据库生成的数据库结果集的访问方法。结果集是指用户调用关系型数据库查询接口之后返回的结果集合,提供了多种灵活的数据访问方式,以便用户获取各项数据。

属性

名称类型必填说明
columnNamesArray获取结果集中所有列的名称。
columnCountnumber获取结果集中的列数。
rowCountnumber获取结果集中的行数。

三、功能优化与封装——思路版

在更新数据这个板块中,可以优化的思路:将谓词限定条件中的数据写活,比如传参的方式。

1717341624414.png

四、实战应用

在手机助手-隐私笔记的项目中,这些功能进行了优化,数据库相关操作进行了较好的封装

(一)功能与核心界面

功能:隐私笔记可通过数据库存储,数据库支持 增、删、查、改、排序等操作,可存储大量持久化数据。

核心界面:

1717342975159.png

(二)主要封装源码

PrivacyNoteDB.ets:

import { relationalStore, ValuesBucket } from '@kit.ArkData'// 隐私笔记的类型
export interface PrivacyNoteDBInfo extends ValuesBucket {
  id: number | null // 新增时 id 设置为 null ,可实现 id 自增
  title: string
  content: string
  date_added: number
}
​
// 隐私笔记数据库封装
class PrivacyNoteDB {
  // 操作数据库的实例
  private store: relationalStore.RdbStore | null = null
  // 数据库表名
  private tableName = 'privacy_note'
  // 创建数据库的语句
  private sqlCreate = `CREATE TABLE IF NOT EXISTS ${this.tableName} (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        title TEXT NOT NULL,
        content TEXT NOT NULL,
        date_added INTEGER NOT NULL
      )`
​
  // 获取数据库操作的实例
  async getStoreInstance() {
    // 如果数据库实例已存在,直接返回,没有才创建实例
    if (this.store) { return this.store }
    // 获取操作数据库的实例
    const store = await relationalStore.getRdbStore(getContext(), {
      name: this.tableName + '.db', // 数据库名称
      securityLevel: relationalStore.SecurityLevel.S1 // 安全等级
    })
    // 执行创建语句,用于创建数据库的表
    store.executeSql(this.sqlCreate)
    // 存储起来方便下次直接获取
    this.store = store
    // 返回 store 实例
    return this.store
  }
​
  // 新增数据
  async insert(value: PrivacyNoteDBInfo) {
    // 获取操作数据库的对象
    const store = await this.getStoreInstance()
    // 记得把添加的返回值 return
    return store.insert(this.tableName, value)
  }
​
  // 查询总数
  async queryCount(){
    // 获取操作数据库的对象
    const store = await this.getStoreInstance()
    // 谓词(条件)
    const predicates = new relationalStore.RdbPredicates(this.tableName)
    // 结果集
    const resultSet = await store.query(predicates)
    // 温馨提示:数量有时候可能返回 -1 , 人为修复
    return resultSet.rowCount > 0 ? resultSet.rowCount : 0
  }
​
  // 查询所有列表  - 不传参数
  // 查询单条数据  - 传 id
  async query(id?: number) {
    // 获取操作数据库的对象
    const store = await this.getStoreInstance()
    // 谓词(条件)
    const predicates = new relationalStore.RdbPredicates(this.tableName)
    if (id) {
      // 如果有id,就通过 id 查找
      predicates.equalTo('id', id)
    } else {
      // 列表倒序(大到小)
      predicates.orderByDesc('id')
    }
    // 结果集
    const resultSet = await store.query(predicates)
    // 准备数组
    const list: PrivacyNoteDBInfo[] = []
    // 移动指针到下一行
    while (resultSet.goToNextRow()) {
      // 把提取的数据追加到数组中
      list.push({
        // 按列下标提取数据,注意数据类型
        id: resultSet.getLong(resultSet.getColumnIndex('id')),
        title: resultSet.getString(resultSet.getColumnIndex('title')),
        content: resultSet.getString(resultSet.getColumnIndex('content')),
        date_added: resultSet.getLong(resultSet.getColumnIndex('date_added'))
      })
    }
    // 循环结束后,返回晚整的数组
    return list
  }
​
  // 修改数据
  async update(value: Partial<PrivacyNoteDBInfo>) {
    // 如果没有 id 直接退出
    if (!value.id) {
      return Promise.reject('id error')
    }
    // 获取操作数据库的对象
    const store = await this.getStoreInstance()
    // 谓词(条件)
    const predicates = new relationalStore.RdbPredicates(this.tableName)
    // 添加 id 作为限定条件
    predicates.equalTo('id', value.id)
    // 更新数据
    return store.update(value, predicates)
  }
​
  // 删除数据
  async delete(id: number) {
    // 如果没有 id 直接退出
    if (!id) {
      return Promise.reject('id error')
    }
    // 获取操作数据库的对象
    const store = await this.getStoreInstance()
    // 谓词(条件)
    const predicates = new relationalStore.RdbPredicates(this.tableName)
    // 添加 id 作为限定条件
    predicates.equalTo('id', id)
    // 删除数据
    return store.delete(predicates)
  }
}
​
// 通过小写开头的这个类实例操作数据库
export const privacyNoteDB = new PrivacyNoteDB()

在上面的封装案例中,值得注意的几处用法

(1)查询query方法中,参数id应设置为可选的,因为有时可能一条数据也没有,相应地,在下面的方法中,也使用 if(id){predicates.equalTo('id', id)} 的形式,在判断id存在时才执行谓词相关操作

(2)在update中使用Partial的方法:因为有时候只修改部分数据

update(value: Partial<PrivacyNoteDBInfo>)

在实战应用中,我们还会实现以下功能

页面结构合理分配 表单页-新增隐私笔记 隐私空间设置页-获取笔记总数 列表页-获取笔记列表 列表页-侧滑删除笔记 表单页-回显和修改笔记 表单页-删除 表单页-华为分享

五、总结

本部分主要是简要整理了关系型数据库的相关知识点,并以封装好的代码的形式给出了实战中的应用案例,并点出了一些注意要点,详细的应用分析与功能拓展将在本专题后续的文章中给出。