摘要

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 分层架构项目,适用于课程设计、毕设中笔记、通讯录、本地缓存等结构化数据持久化场景。

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐