HarmonyOS开发 - 记事本实例二(关系型数据库数据存储)
开发一个记事本App的主要功能点包括以下几点:
创建笔记:用户可以在应用中创建新的笔记,包括输入笔记标题、内容,以及记录创建时间和更新时间等。
编辑笔记:用户可以对已创建的笔记进行修改。
删除笔记:用户可以删除不需要的笔记。
分类管理:笔记可以按照类别管理,自定义类别等。
查询功能:支持按标题或内容进行查询。
选择数据库:
这里使用关系型数据库(Relational Database,RDB),它是一种基于关系模型来管理数据的数据库。关系型数据库基于SQLite组件提供了一套完整的对本地数据库进行管理的机制,对外提供了一系列的增、删、改、查等接口,也可以直接运行用户输入的SQL语句来满足复杂的场景需要。支持通过ResultSet.getSendableRow方法获取Sendable数据,进行跨线程传递。
为保证插入并读取数据成功,建议一条数据不要超过2M。超出该大小,插入成功,读取失败。
注意:大数据量场景下查询数据可能会导致耗时长甚至应用卡死,建议如下:
- 单次查询数据量不超过5000条。
- 在TaskPool中查询。
- 拼接SQL语句尽量简洁。
- 合理地分批次查询。
关于@ohos.data.relationalStore (关系型数据库)的官方文档地址:文档中心。
此篇接着上一篇内容继续讲,完成数据的存储、读取等操作,上一篇地址:HarmonyOS开发 - 记事本实例一(界面搭建)-CSDN博客
一、初始化数据库
在HarmonyOS中,RdbStore是关系型数据库(Relational Database Store)的核心接口,用于管理和操作本地关系型数据库。
1.1 主要功能:
1、创建或打开数据库:
- RdbStore 可以用来创建一个新的数据库,或者打开一个已经存在的数据库文件。
- 通过 getRdbStore 接口获取 RdbStore 实例时,可以指定数据库的路径、版本号等参数。
2、执行数据库操作:
- RdbStore 提供了增删改查(CRUD)操作的接口,支持执行 SQL 查询和事务处理。
- 例如,可以使用 insert、delete、update 和 query 等方法来操作数据库。
3、数据库版本管理:
- 在获取 RdbStore 实例时,可以通过配置参数来控制数据库的行为,例如设置数据库版本号和升级策略。
- 如果数据库版本发生变化,可以实现相应的升级逻辑。
4、跨线程数据传递:
- 支持通过 ResultSet.getSendableRow 方法获取可跨线程传递的数据
1.2 创建数据库
在目录src/main/ets/db下,创建文件index.ets,用于创建和数据库配置。代码如下:
import relationalStore from '@ohos.data.relationalStore';
import common from '@ohos.app.ability.common';
// 配置数据类型接口
interface configTypes {
name: string;
securityLevel: number;
}
// RDB配置
const storeConfig: configTypes = {
name: 'myNotes.db', // 数据库文件名
securityLevel: relationalStore.SecurityLevel.S1 // 数据库安全级别
}
// 本地存储实例
export let store: relationalStore.RdbStore;
// 分类信息建表SQL
const ClassifySql = `CREATE TABLE IF NOT EXISTS Classify (
ID INTEGER PRIMARY KEY,
NAME TEXT NOT NULL,
CREATE_TIME DATE NOT NULL,
UPDATE_TIME DATE NOT NULL,
IS_VIEW INTEGER NOT NULL
)`
// 记事本建表SQL
const notesSql = `CREATE TABLE IF NOT EXISTS Notes (
ID INTEGER PRIMARY KEY,
NAME TEXT NOT NULL,
CONTENT TEXT NOT NULL,
CLASSIFY_ID INTEGER NOT NULL,
CREATE_TIME DATE NOT NULL,
UPDATE_TIME DATE NOT NULL,
IS_VIEW INTEGER NOT NULL
)`
/**
* 初始化数据库
* @param context
*/
export const initialDB = async (context: common.UIAbilityContext) => {
const rdbStore = await relationalStore.getRdbStore(context, storeConfig)
if(!rdbStore) {
console.error(`Get RdbStore failed`);
return;
}
store = rdbStore
rdbStore.executeSql(ClassifySql) // 创建分类表
rdbStore.executeSql(notesSql) // 创建记事本表
console.info(`Get RdbStore successfully.`);
}
注意的是,初始化数据库函数initialDB()函数,使用的是 async 和 await 异步操作,async 关键字用于声明一个函数是异步的,wait 关键字用于暂停 async 函数的执行,直到某个异步操作完成。
1.3 初始化数据库
打开src/main/ets/entryability/EntryAbility.ets文件,找到onWindowStageCreate函数,在此处初始化数据库,以及获取RdbStore实例。
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.');
});
}
如果想在onWindowStageCreate函数中使用await关键字,其自身必须使用async声明为异步函数,同时将 : void返回类型去除,否则会报错。
当onWindowStageCreate声明为异步函数后,在initialDb()函数前加上await关键词,待数据库初始化完毕后,再执行windowStage.loadContent函数加载主界面,以确保页面加载时,获取的RdbStore实例对象不为空。
import { AbilityConstant, ConfigurationConstant, UIAbility, Want } from '@kit.AbilityKit';
import { hilog } from '@kit.PerformanceAnalysisKit';
import { window } from '@kit.ArkUI';
import { initialDB } from '../db/index'
export default class EntryAbility extends UIAbility {
// 略...
async onWindowStageCreate(windowStage: window.WindowStage) {
// Main window is created, set main page for this ability
hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onWindowStageCreate');
// 初始化数据库,并获取RdbStore实例对象
await initialDB(this.context)
//
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.');
});
}
// 略...
}
此时,在”设备管理器“中打开虚拟机,运行后查看控制台,会输出”Get RdbStore successfully."日志,表示数据库初始化完成,并成功获取RdbStore实例对象。
二、类型定义
在HarmonyOS的ArkUI开发框架中,使用TypeScript语言进地开发。在TypeScript中,给变量指定类型是一个常见的做法,这有助于在编译时捕获潜在的错误,并提供了更好的代码提示和自动补全功能。
在ArkUI中给变量指定类型的基本语法 与TypeScript中是类似的,我们先在项目中创建types目录,用于存储相关类型定义,路径:src/main/ets/types/types.ets,打开文件并定义Classify和Notes的数据结构类型。代码如下:
// 定义:分类信息的类型
export interface ClassifyInfo {
id: number
name: string
updateTime?: number
}
// 定义笔记的类型
export interface NotesInfo {
id?: number
title: string // 笔记名称
content: string // 笔记内容
classify_id: number //对应分类ID
create_time?: number // 创建笔记时间
update_time?: number // 修改笔记时间
}
三、创建模型
使用Model(模型)来表示数据结构和业务逻辑是常见的设计模式。Model 在数据的增、删、改、查(CRUD)操作起到了核心的作用。
在项目中创建model目录,用于创建和定义分类信息Classify和记事本Notes的Model,路径:src/main/ets/model。
3.1 Model的作用
- 封装数据:Model通常是一个类,用于封装数据与数据相关的业务逻辑。它定义了数据和结构(属性)和操作数据的方法。
- 与数据库交互:Model通常与数据库表对应,负责将数据持久化到数据库中,或者从数据库中读取数据。
通过这种方式,Model在数据的增、删、改、查操作起到了桥梁的作用,将业务逻辑与数据库操作紧密结合起来,使得代码更加清晰和易于维护。
3.2 RdbPredicates
RdbPredicates表示关系型数据库的谓词,用于确定RDB中条件表达式值是true还是false。它支持多语句拼接,默认使用and()连接。
作用:
- 构建查询条件:通过 RdbPredicates,可以指定查询条件,例如等于(equalTo)、以某个值开头(beginsWith)等。
- 支持多种条件组合:可以将多个条件组合在一起,形成复杂的查询条件。
- 简化数据库操作:通过 RdbPredicates,可以更方便地构建和执行数据库查询操作
3.3 分类信息
创建分类信息的model文件,路径:src/main/ets/model/Classify.ets,创建类Classify并定义其对应的相关属性。代码如下:
import { store } from '../db/index'
import relationalStore from '@ohos.data.relationalStore'
import { ClassifyInfo } from '../types/types'
/**
* 分类 - 模型
*/
export class Classify {
private tableName: string = 'Classify'
private _ID: string = 'ID'
private _NAME: string = 'NAME'
private _UPDATE_TIME: string = 'UPDATE_TIME'
}
export const ClassifyModal = new Classify()
3.3.1 获取分类信息
通过实例relationalStore获取RdbPredicates,调用orderByDesc()函数来构建查询条件,查询Classify表中的所有行数据,并以更新日期进行倒序显示。
当查询到数据库,通过while循环提取出每行数据,并且使用getColumnIndex()函数获取对应字段的索引,再使用索引取出对应字段的值。
代码如下:
/**
* 获取行数据
*/
async getAllRows(){
const predicates = new relationalStore.RdbPredicates(this.tableName)
predicates.orderByDesc(this._UPDATE_TIME)
const result = await store.query(predicates, [this._ID, this._NAME, this._UPDATE_TIME])
const list: ClassifyInfo[] = []
while (!result.isAtLastRow) {
// 指针移动到下一行数据
result.goToNextRow()
// 根据字段读取index,从而获取对应字段的值
let id = result.getLong(result.getColumnIndex(this._ID))
let name = result.getString(result.getColumnIndex(this._NAME))
let updateTime = result.getLong(result.getColumnIndex(this._UPDATE_TIME))
list.push({ id, name, updateTime })
}
return list
}
3.3.2 判断分类名称
这里定义isContainName()函数,用于判断分类名称是否已存在;如果已存在则返回true,否则为false。代码如下:
/**
* 判断 名称是否存在
* @param name
*/
async isContainName(name: string) {
const predicates = new relationalStore.RdbPredicates(this.tableName)
predicates.equalTo(this._NAME, name)
const result = await store.query(predicates, [this._ID, this._NAME])
// 大于0表示已存在
return result.rowCount > 0
}
3.3.3 添加分类信息
通过RdbStore提供的insert方法,插入一条新数据。代码如下:
/**
* 添加行数据
* @param name
*/
async addRowData(name: string){
await store.insert(this.tableName, {
NAME: name,
CREATE_TIME: new Date().getTime(),
UPDATE_TIME: new Date().getTime(),
IS_VIEW: 1
})
}
3.3.4 修改分类名称
通过RdbPredicate谓词构建查询条件,找到指定id行数据,再使用RdbStore的update方法完成分类名称的修改。代码如下:
/**
* 修改行数据
* @param id
* @param name
*/
async editRowData(id: number, name: string) {
const predicates = new relationalStore.RdbPredicates(this.tableName)
predicates.equalTo(this._ID, id)
await store.update({
NAME: name,
UPDATE_TIME: new Date().getTime()
}, predicates)
}
3.3.5 删除分类信息
通过RdbPredicate谓词构建查询条件,找到指定id行数据,再使用RdbStore的delete方法完成数据的删除操作。代码如下:
/**
* 删除行数据
* @param id
*/
async deleteRowData(id: number) {
const predicates = new relationalStore.RdbPredicates(this.tableName)
predicates.equalTo(this._ID, id)
await store.delete(predicates)
}
3.4 记事本信息
创建记事本信息的model文件,路径:src/main/ets/model/Notes.ets,创建类Notes并定义其对应的相关属性。代码如下:
import { store } from '../db/index'
import relationalStore from '@ohos.data.relationalStore'
import { NotesInfo } from '../types/types'
/**
* 记事本 - 模型
*/
class Notes {
private tableName: string = 'Notes'
private _ID: string = 'ID'
private _NAME: string = 'NAME'
private _CONTENT: string = 'CONTENT'
private _CLASSIFY_ID: string = 'CLASSIFY_ID'
private _CREATE_TIME: string = 'CREATE_TIME'
private _UPDATE_TIME: string = 'UPDATE_TIME'
}
export const NotesModal = new Notes()
3.4.1 获取记事本信息
通过RdbPredicates谓词构建查询条件,以倒序查询出所有记事本的行数据;当分类信息ID存在时,追加关联分类的ID查询条件。代码如下:
/**
* 获取行数据
* @param classType -1 默认
* @returns
*/
async getAllRows(classType: number = -1){
const predicates = new relationalStore.RdbPredicates(this.tableName)
predicates.orderByDesc(this._UPDATE_TIME)
if (classType > 0) {
predicates.equalTo(this._CLASSIFY_ID, classType)
}
const result = await store.query(predicates,
[this._ID, this._NAME, this._CONTENT, this._CLASSIFY_ID, this._CREATE_TIME, this._UPDATE_TIME]
)
const list: NotesInfo[] = []
while (!result.isAtLastRow) {
// 指针移动到下一行数据
result.goToNextR