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

OpenHarmony 关系型数据库 RDB 完整封装(API Version23 本地持久化数据库)

摘要

RDB 是鸿蒙内置轻量关系型数据库,基于 SQLite 封装,适合存储结构化大量数据(笔记、用户列表、商品数据),相比 Preferences 仅适合简单键值对,RDB 支持表、字段、主键、条件分页查询、多条件筛选、批量增删改查。API23 重构数据库实例生命周期、事务、字段类型校验、并发锁机制,修复旧版数据库未关闭导致占用、大量数据查询卡顿、事务回滚失效、多页面同时操作崩溃等问题。本文封装通用 RDB 工具基类,附带笔记业务数据表完整 CRUD、分页查询、事务批量插入实战代码,给出数据库初始化、资源释放、事务使用规范,适配所有需要本地大量结构化存储的项目。

关键词

OpenHarmony;ArkTS;API23;relationalStore;RDB;本地数据库;SQLite;事务;分页查询

一、引言

1.1 RDB 开发背景

本地存储分为两类:

  1. Preferences:小型键值存储,仅存配置、开关、token,不支持复杂查询;
  2. RDB 关系数据库:结构化数据表,支持主键、自增、WHERE 条件、分页、批量操作,适合笔记、通讯录、本地缓存列表等多条业务数据。

API23 RDB 核心升级点:

  1. 强制规范 RdbStore 实例生命周期,不用必须 close,防止文件锁占用;
  2. 完善事务 transaction 原子操作,批量插入失败自动回滚;
  3. 优化 RdbPredicates 条件构造器,替代手写拼接 SQL 防注入;
  4. 统一字段类型强校验,区分数字、文本、布尔;
  5. 多页面并发读写加内置读写锁,避免同时操作数据库崩溃;
  6. 升级大数量查询内存优化,分页读取不一次性加载全部数据。

1.2 开发环境与场景

模块:@ohos.data.relationalStore 无需额外动态权限,应用沙盒自有数据库读写权限 测试场景:笔记新增 / 删除 / 修改、分页加载列表、批量插入事务、按标题模糊搜索、清空数据表

二、API23 RDB 核心基础 API

2.1 数据库初始化配置

ets

import relationalStore from '@ohos.data.relationalStore' const storeConfig: relationalStore.StoreConfig = { name: "app_db", // 数据库文件名 securityLevel: relationalStore.SecurityLevel.S1 // 安全等级 } // 获取数据库实例 const rdbStore = await relationalStore.getRdbStore(context, storeConfig)

2.2 建表 SQL 示例

sql

CREATE TABLE IF NOT EXISTS note ( id INTEGER PRIMARY KEY AUTOINCREMENT, title TEXT NOT NULL, content TEXT, create_time INTEGER )

2.3 核心操作方法

  1. executeSql (sql):执行建表、删除表、自定义 SQL
  2. insert (table, values):新增单条数据
  3. update (table, values, predicates):按条件修改
  4. delete (table, predicates):按条件删除
  5. query (table, columns, predicates):条件查询返回 ResultSet
  6. beginTransaction /commit/rollback:事务批量操作
  7. close ():关闭数据库释放文件锁

2.4 RdbPredicates 条件构造器(防 SQL 注入)

ets

let predicates = new relationalStore.RdbPredicates("note") predicates.equalTo("id", 1) // id=1 predicates.contains("title", "鸿蒙") // 模糊搜索标题 predicates.orderByDesc("create_time") // 按创建时间倒序 predicates.offset(0).limit(10) // 分页

2.5 API23 强制约束

  1. 页面销毁、工具释放必须调用 close (),否则数据库文件持续锁定无法删除;
  2. 复杂查询禁止手写拼接字符串 SQL,统一使用 RdbPredicates;
  3. 批量新增、批量删除必须包裹事务,避免中途插入一半数据错乱;
  4. ResultSet 使用完毕必须 close,否则内存持续占用;
  5. 禁止全局单例长期持有 RdbStore,切换页面重新获取实例。

三、通用 RDB 底层工具封装 utils/rdb_util.ets

ets

import relationalStore from '@ohos.data.relationalStore' import common from '@ohos.app.ability.common' import promptAction from '@ohos.promptAction' // 笔记实体类型导出 export interface Note { id: number title: string content: string create_time: number } // 数据库配置常量 const DB_NAME = "note_database" const TABLE_NOTE = "note" class RdbUtil { private static instance: RdbUtil private ctx: common.UIAbilityContext | null = null private store: relationalStore.RdbStore | null = null static getInstance(): RdbUtil { if (!RdbUtil.instance) { RdbUtil.instance = new RdbUtil() } return RdbUtil.instance } // 注入页面上下文 setContext(context: common.UIAbilityContext) { this.ctx = context } // 初始化数据库+自动建表 async init(): Promise<boolean> { if (!this.ctx) { promptAction.showToast({ message: "上下文未初始化" }) return false } try { const config: relationalStore.StoreConfig = { name: DB_NAME, securityLevel: relationalStore.SecurityLevel.S1 } this.store = await relationalStore.getRdbStore(this.ctx, config) // 创建笔记表 const createTableSql = ` CREATE TABLE IF NOT EXISTS ${TABLE_NOTE} ( id INTEGER PRIMARY KEY AUTOINCREMENT, title TEXT NOT NULL, content TEXT, create_time INTEGER ) ` await this.store.executeSql(createTableSql) return true } catch (err) { promptAction.showToast({ message: "数据库初始化失败" }) console.error("rdb init error", err) return false } } // 新增笔记,返回自增id async addNote(title: string, content: string): Promise<number> { if (!this.store) return -1 const values: Record<string, any> = { title, content, create_time: new Date().getTime() } const rowId = await this.store.insert(TABLE_NOTE, values) return rowId } // 根据id删除笔记 async deleteNote(id: number): Promise<boolean> { if (!this.store) return false const predicates = new relationalStore.RdbPredicates(TABLE_NOTE) predicates.equalTo("id", id) const delCount = await this.store.delete(TABLE_NOTE, predicates) return delCount > 0 } // 修改笔记 async updateNote(id: number, title: string, content: string): Promise<boolean> { if (!this.store) return false const values: Record<string, any> = { title, content } const predicates = new relationalStore.RdbPredicates(TABLE_NOTE) predicates.equalTo("id", id) const updateCount = await this.store.update(TABLE_NOTE, values, predicates) return updateCount > 0 } // 分页查询笔记列表(时间倒序) async queryNoteList(page: number, pageSize: number, keyword: string = ""): Promise<Note[]> { if (!this.store) return [] const predicates = new relationalStore.RdbPredicates(TABLE_NOTE) // 模糊搜索 if (keyword) { predicates.contains("title", keyword) } predicates.orderByDesc("create_time") predicates.offset(page * pageSize).limit(pageSize) const resultSet = await this.store.query(TABLE_NOTE, ["id", "title", "content", "create_time"], predicates) const list: Note[] = [] while (resultSet.goToNextRow()) { list.push({ id: resultSet.getLong(0), title: resultSet.getString(1), content: resultSet.getString(2), create_time: resultSet.getLong(3) }) } // 释放结果集 resultSet.close() return list } // 批量插入事务示例 async batchAddNote(noteArr: Array<{ title: string, content: string }>): Promise<boolean> { if (!this.store) return false try { await this.store.beginTransaction() for (const item of noteArr) { await this.store.insert(TABLE_NOTE, { title: item.title, content: item.content, create_time: new Date().getTime() }) } await this.store.commit() return true } catch (err) { await this.store.rollback() console.error("批量插入失败,事务回滚", err) return false } } // 清空整张笔记表 async clearAllNote(): Promise<void> { if (!this.store) return const predicates = new relationalStore.RdbPredicates(TABLE_NOTE) await this.store.delete(TABLE_NOTE, predicates) } // 关闭数据库释放锁(页面销毁必须调用) async closeDB() { if (this.store) { await this.store.close() this.store = null } } } export default RdbUtil.getInstance()

四、实战页面:笔记增删改查分页页面

ets

import RdbUtil, { Note } from '../utils/rdb_util' import promptAction from '@ohos.promptAction' @Entry @Component struct RdbNotePage { @State noteList: Note[] = [] @State searchKey: string = "" pageIndex: number = 0 readonly PAGE_SIZE = 10 async aboutToAppear() { const ctx = getContext(this) RdbUtil.setContext(ctx) const initOk = await RdbUtil.init() if (initOk) { await this.refreshList() } } // 刷新第一页数据 async refreshList() { this.pageIndex = 0 this.noteList = await RdbUtil.queryNoteList(this.pageIndex, this.PAGE_SIZE, this.searchKey) } // 加载下一页 async loadMore() { this.pageIndex += 1 const more = await RdbUtil.queryNoteList(this.pageIndex, this.PAGE_SIZE, this.searchKey) if (more.length > 0) { this.noteList = [...this.noteList, ...more] } else { promptAction.showToast({ message: "没有更多笔记" }) } } // 新增笔记弹窗简易逻辑 async addNewNote() { const id = await RdbUtil.addNote("新建笔记", "这里是笔记内容文本") if (id > 0) { promptAction.showToast({ message: "新增成功" }) await this.refreshList() } } // 删除单条笔记 async delItem(id: number) { const res = await RdbUtil.deleteNote(id) if (res) { promptAction.showToast({ message: "删除完成" }) await this.refreshList() } } build() { Column({ space: 12 }) { Row({ space: 10 }) { TextInput({ text: this.searchKey, placeholder: "搜索笔记标题" }) .layoutWeight(1) .onChange(val => this.searchKey = val) Button("搜索").onClick(() => this.refreshList()) } .width("95%") Row({ space: 10 }) { Button("新增笔记").backgroundColor("#007DFF").onClick(() => this.addNewNote()) Button("加载更多").backgroundColor("#009966").onClick(() => this.loadMore()) Button("清空全部").backgroundColor("#f56c6c").onClick(async () => { await RdbUtil.clearAllNote() await this.refreshList() }) } List({ space: 10 }).layoutWeight(1).width("95%") { ForEach(this.noteList, (item: Note) => { ListItem() { Row() { Column({ space: 4 }).layoutWeight(1) { Text(item.title).fontSize(17).fontWeight(500) Text(item.content).fontColor("#666") } Button("删除").backgroundColor("#f56c6c") .onClick(() => this.delItem(item.id)) } .width("100%") .padding(16) .backgroundColor(Color.White) .borderRadius(10) } }) } } .width("100%") .height("100%") .padding(15) .backgroundColor("#F5F5F5") } async aboutToDisappear() { // 页面退出关闭数据库,释放文件锁 await RdbUtil.closeDB() } }

五、事务批量插入演示页面片段

ets

// 批量测试数据插入 async function batchInsertDemo() { const data = [ { title: "批量笔记1", content: "批量事务测试内容1" }, { title: "批量笔记2", content: "批量事务测试内容2" }, { title: "批量笔记3", content: "批量事务测试内容3" } ] const ok = await RdbUtil.batchAddNote(data) if (ok) { promptAction.showToast({ message: "批量插入完成" }) await refreshList() } else { promptAction.showToast({ message: "批量插入失败,已回滚" }) } }

六、API23 RDB 开发规范

6.1 生命周期规范

  1. 页面aboutToAppear传入上下文并执行 init 初始化;
  2. 页面aboutToDisappear必须调用 closeDB 关闭 RdbStore 释放文件锁;
  3. ResultSet 查询结果集使用完立即 close,避免内存泄漏。

6.2 事务使用规范

  1. 批量新增、批量删除、多表联动修改必须开启事务;
  2. 出现异常执行 rollback 回滚,保证原子性,不会出现半截数据;
  3. 单条增删改无需事务,减少性能开销。

6.3 查询性能规范

  1. 列表统一分页 offset+limit,禁止一次性查询上千条;
  2. 模糊搜索使用 RdbPredicates.contains,禁止拼接 SQL 字符串;
  3. 常用查询字段建立索引(建表 SQL 增加 INDEX)提升检索速度。

6.4 安全规范

  1. 所有条件过滤使用 RdbPredicates,杜绝 SQL 注入风险;
  2. 用户输入内容不直接拼接进 executeSql 原始语句。

七、高频报错与解决方案

问题 1:数据库文件被占用,无法删除 / 初始化失败 解决:页面退出调用 closeDB,及时释放 RdbStore 实例。

问题 2:查询结果为空,新增数据不显示 解决:确认 init 建表语句执行成功,主键自增配置正确。

问题 3:批量插入一半数据中断,数据残留 解决:批量操作包裹 beginTransaction + commit,异常执行 rollback。

问题 4:页面多次进入数据库卡顿 解决:不要全局持有 store,每次页面重新初始化,退出关闭。

问题 5:升级 API23 旧版 sql 拼接查询报错 解决:全部替换为 RdbPredicates 条件构造器,废弃字符串拼接 SQL。

问题 6:ResultSet 内存持续上涨 解决:循环读取完数据后调用 resultSet.close () 释放。

八、总结

RDB 是鸿蒙本地结构化海量数据存储标准方案,API23 完善数据库实例生命周期、事务原子操作、条件安全查询、并发读写锁能力,弥补 Preferences 无法复杂查询、仅支持简单键值对的短板。 封装通用 Rdb 工具类统一管理初始化、增删改查、分页搜索、批量事务、资源释放,搭配完整笔记业务页面,可直接集成进之前 HAR/HSP 分层架构项目,适用于课程设计、毕设中笔记、通讯录、本地缓存等结构化数据持久化场景。

http://www.jsqmd.com/news/1190333/

相关文章:

  • 莲籽甘,籽心苦,不剥玉壳不知我。龟求毛,兔觅角,几回一池秋水浪过。乱!乱!乱!春风红,秋光短,莲籽无须甘苦果山河碎,今昨何,俯弄星月,卧沐汉河,吃,了,睡!
  • 收藏了!1个让零基础小白也能看懂AI的GitHub开源课程,太全了
  • 工业洗地机靠谱厂家判定指南 资质与实绩双维度解析 - 奔跑123
  • 芝柏中国官方售后服务中心|网点地址和官方热线权威信息公告(2027年7月最新) - 亨得利官方服务中心
  • 抖音小店标题主图详情怎么优化?AI 自动整改方法 抖店微信小店批量商品优化软件工具 - 电商分享
  • 生产变更准入与回滚 SOP:从上线前检查到故障回滚的通用实践
  • ArkCompiler 5.0:方舟编译器新一代编译优化技术解析(160)
  • 河南冷库厂家综合实力排行:全维度实测对比解析 - 奔跑123
  • 权威评测:上海活动跟拍机构TOP10精选|2026年度最新测评结果全流程服务标准与价格透明说明及选型参考完整指南 - 全国影像制作行业测评
  • 【1979-2026】全国GDP栅格数据|1km分辨率
  • 2026面向跨境从业者的欧洲超大件物流服务推荐 - 奔跑123
  • OpenCV工业视觉实战04|形态学操作(腐蚀/膨胀/开运算/闭运算)去除毛刺、孔洞、碎点,YOLO目标轮廓修复神器
  • ChatGPT技术文档编写:为什么Top 1%团队坚持“3轮人机协同”?——基于GitHub上2,147份PR文档的A/B测试数据揭秘
  • YOLO-NAS 自定义目标检测训练实战:SuperGradients 训练与测试
  • 2026年7月最新厦门翔安区大嶝街道亨得利官方名表服务中心电话公示 - 亨得利官方博客
  • 2026年济南鑫舜捷化工水处理药剂厂家业务线全面梳理 - 奔跑123
  • 商丘市区虞城县性价比高且材料靠谱的装修公司整理 - 奔跑123
  • 国内专业不锈钢锻造厂排行:实力与资质双维度解析 - 奔跑123
  • ArkUI 5.0渲染管线:从代码到像素的极致渲染路径(161)
  • 重庆大学城租房评测:全维度对比与实用选择指南 - 奔跑123
  • 2026美国双清专线公司推荐实用选择标准汇总参考 - 奔跑123
  • 2026济南寻找工业级硫酸氢钠的日常经历分享 - 奔跑123
  • 2026年长沙市二手房全屋定制相关服务商名录 - 奔跑123
  • 亲身到店探访杭州亨得利官方名表服务中心|详细地址与官方售后热线(2026年7月更新) - 亨得利官方
  • OpenCV工业视觉实战05|轮廓检测+面积筛选+外接矩形,过滤干扰目标,实现CV精准目标筛选(YOLO后处理核心)
  • 为什么 AI 总是“一本正经地胡说八道”?大模型幻觉终于讲明白了
  • 2026年海安油烟机维修选哪家 本地靠谱服务商榜单参考 - 热点品牌推荐
  • 【1980-2025】中科院30m土地利用/覆盖数据|全国|TIFF
  • 深度评测:福州商业摄影机构TOP10精选|2026年度最新测评结果全流程服务标准与价格透明说明及选型参考完整指南 - 全国影像制作行业测评
  • 并发任务管理:TaskPool与Worker的底层调度机制(162)