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

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

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

相关文章:

  • Kubernetes实战教程:基于Vue前端与Java后端的应用部署
  • 数据结构:Map Set(一)
  • 蓝桥杯 Java B 组之简单数学问题(素数判断、最大公约数)
  • Golang并发编程最佳实践:协程与通道
  • jar命令解压jar包及更新jar的配置文件
  • 机器视觉--图像的运算(加法)
  • 管理WSL实例 以及安装 Ubuntu 作为 WSL 子系统 流程
  • 【DeepSeek问答】QProcess::start是异步的吗?会使UI卡顿吗?
  • Playwright入门之---命令
  • 基于MATLAB的城轨车辆跨接电缆长度计算
  • 前端+后端实现全选、反选+批量删除
  • springcloudalibaba组件gateway
  • 制作Ubuntu根文件
  • docker下部署kong+consul+konga 报错问题处理
  • 直播平台营销困境与开源AI智能名片2+1链动模式S2B2C商城小程序源码的创新解决方案探究
  • 前端【技术方案】重构项目
  • 【React组件通讯双重视角】函数式 vs 类式开发指南
  • 安全测试|SQLMap渗透扫描工具
  • 基于Python的医院运营数据可视化平台:设计、实现与应用(上)
  • JS宏实例:数据透视工具的制作(二)