OpenHarmony 关系型数据库 RDB 完整封装(API Version23 本地持久化数据库)
摘要
RDB 是鸿蒙内置轻量关系型数据库,基于 SQLite 封装,适合存储结构化大量数据(笔记、用户列表、商品数据),相比 Preferences 仅适合简单键值对,RDB 支持表、字段、主键、条件分页查询、多条件筛选、批量增删改查。API23 重构数据库实例生命周期、事务、字段类型校验、并发锁机制,修复旧版数据库未关闭导致占用、大量数据查询卡顿、事务回滚失效、多页面同时操作崩溃等问题。本文封装通用 RDB 工具基类,附带笔记业务数据表完整 CRUD、分页查询、事务批量插入实战代码,给出数据库初始化、资源释放、事务使用规范,适配所有需要本地大量结构化存储的项目。
关键词
OpenHarmony;ArkTS;API23;relationalStore;RDB;本地数据库;SQLite;事务;分页查询
一、引言
1.1 RDB 开发背景
本地存储分为两类:
- Preferences:小型键值存储,仅存配置、开关、token,不支持复杂查询;
- RDB 关系数据库:结构化数据表,支持主键、自增、WHERE 条件、分页、批量操作,适合笔记、通讯录、本地缓存列表等多条业务数据。
API23 RDB 核心升级点:
- 强制规范 RdbStore 实例生命周期,不用必须 close,防止文件锁占用;
- 完善事务 transaction 原子操作,批量插入失败自动回滚;
- 优化 RdbPredicates 条件构造器,替代手写拼接 SQL 防注入;
- 统一字段类型强校验,区分数字、文本、布尔;
- 多页面并发读写加内置读写锁,避免同时操作数据库崩溃;
- 升级大数量查询内存优化,分页读取不一次性加载全部数据。
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 核心操作方法
- executeSql (sql):执行建表、删除表、自定义 SQL
- insert (table, values):新增单条数据
- update (table, values, predicates):按条件修改
- delete (table, predicates):按条件删除
- query (table, columns, predicates):条件查询返回 ResultSet
- beginTransaction /commit/rollback:事务批量操作
- 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 强制约束
- 页面销毁、工具释放必须调用 close (),否则数据库文件持续锁定无法删除;
- 复杂查询禁止手写拼接字符串 SQL,统一使用 RdbPredicates;
- 批量新增、批量删除必须包裹事务,避免中途插入一半数据错乱;
- ResultSet 使用完毕必须 close,否则内存持续占用;
- 禁止全局单例长期持有 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 生命周期规范
- 页面
aboutToAppear传入上下文并执行 init 初始化; - 页面
aboutToDisappear必须调用 closeDB 关闭 RdbStore 释放文件锁; - ResultSet 查询结果集使用完立即 close,避免内存泄漏。
6.2 事务使用规范
- 批量新增、批量删除、多表联动修改必须开启事务;
- 出现异常执行 rollback 回滚,保证原子性,不会出现半截数据;
- 单条增删改无需事务,减少性能开销。
6.3 查询性能规范
- 列表统一分页 offset+limit,禁止一次性查询上千条;
- 模糊搜索使用 RdbPredicates.contains,禁止拼接 SQL 字符串;
- 常用查询字段建立索引(建表 SQL 增加 INDEX)提升检索速度。
6.4 安全规范
- 所有条件过滤使用 RdbPredicates,杜绝 SQL 注入风险;
- 用户输入内容不直接拼接进 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 分层架构项目,适用于课程设计、毕设中笔记、通讯录、本地缓存等结构化数据持久化场景。
更多推荐

所有评论(0)