一、已经解决的问题和仍存在问题
根据上一篇中研究得到的结果,目前还存在部分问题。
已经得出的测试结果:一、二、三、四、五的功能可以正常使用,六、七存在优化空间、八存在一定问题。
二、数据库相关知识回顾、理解
首先明确:本文所讲解的是基于鸿蒙自带的SQLLite风格的内置关系型数据库。
(一)应用数据持久化
应用数据持久化,是指应用将内存中的数据通过文件或数据库的形式保存到设备上。
HarmonyOS 标准系统支持典型的存储数据形态,包括用户首选项、键值型数据库、关系型数据库。
- 数据库级别概念
关系型数据库(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
参数:
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| context | Context | 是 | 应用的上下文。 |
| config | StoreConfig | 是 | 与此RDB存储相关的数据库配置。 |
返回值:
| 类型 | 说明 |
|---|---|
| Promise | Promise对象。返回RdbStore对象。 |
检查数据库文件是否创建成功
/data/app/el2/100/database/包名/entry/rdb
(2)executeSql
executeSql(sql: string, bindArgs?: Array):Promise
执行包含指定参数但不返回值的SQL语句,使用Promise异步回调。
系统能力: SystemCapability.DistributedDataManager.RelationalStore.Core
参数:
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| sql | string | 是 | 指定要执行的SQL语句。 |
| bindArgs | Array | 否 | SQL语句中参数的值。该值与sql参数语句中的占位符相对应。当sql参数语句完整时,该参数不填。 |
返回值:
| 类型 | 说明 |
|---|---|
| Promise | 无返回结果的Promise对象。 |
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
提供通过查询数据库生成的数据库结果集的访问方法。结果集是指用户调用关系型数据库查询接口之后返回的结果集合,提供了多种灵活的数据访问方式,以便用户获取各项数据。
属性
| 名称 | 类型 | 必填 | 说明 |
|---|---|---|---|
| columnNames | Array | 是 | 获取结果集中所有列的名称。 |
| columnCount | number | 是 | 获取结果集中的列数。 |
| rowCount | number | 是 | 获取结果集中的行数。 |
三、功能优化与封装——思路版
在更新数据这个板块中,可以优化的思路:将谓词限定条件中的数据写活,比如传参的方式。
四、实战应用
在手机助手-隐私笔记的项目中,这些功能进行了优化,数据库相关操作进行了较好的封装
(一)功能与核心界面
功能:隐私笔记可通过数据库存储,数据库支持 增、删、查、改、排序等操作,可存储大量持久化数据。
核心界面:
(二)主要封装源码
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>)
在实战应用中,我们还会实现以下功能
页面结构合理分配 表单页-新增隐私笔记 隐私空间设置页-获取笔记总数 列表页-获取笔记列表 列表页-侧滑删除笔记 表单页-回显和修改笔记 表单页-删除 表单页-华为分享
五、总结
本部分主要是简要整理了关系型数据库的相关知识点,并以封装好的代码的形式给出了实战中的应用案例,并点出了一些注意要点,详细的应用分析与功能拓展将在本专题后续的文章中给出。