摘要

RDB(关系型数据库)基于 SQLite 封装,适用于海量结构化、多字段复杂数据本地存储,如笔记、通讯录、商品列表、离线缓存等场景,弥补 Preferences 仅能存储简单键值的短板。API Version23 重构 RDB 数据库连接池、事务机制、异步 SQL 执行、游标内存回收逻辑,修复低版本数据库多次打开未关闭导致文件锁死、批量插入卡顿、游标泄漏、多表查询数据错乱、事务回滚失效等兼容缺陷。低版本工程升级至 API23 后常出现:数据库重复创建报错、查询游标未释放内存持续上涨、批量插入丢失数据、页面退出数据库无法关闭、分页查询偏移计算错误等问题。本文基于 DevEco Studio 适配 OpenHarmony API23 及以上版本,讲解数据库创建、表结构设计、单条 / 批量增删改查、分页查询、事务操作、数据库关闭销毁完整流程,以离线笔记管理系统为实战案例提供可直接运行代码,输出数据库连接复用、游标回收、事务容错规范,汇总版本升级兼容故障解决方案,为鸿蒙大量结构化数据持久化开发提供标准化实操模板。

关键词

OpenHarmony;ArkUI;API Version23;RDB;关系型数据库;SQLite;增删改查;事务;本地离线缓存

一、引言

1.1 RDB 数据库开发背景

Preferences 仅适合存储少量开关、用户名等简单键值数据,无法处理多字段、大批量结构化数据。RDB 基于标准 SQLite 实现,支持多数据表、条件筛选、分页、排序、事务批量操作,是离线笔记、通讯录、本地商品缓存、表单草稿等业务唯一轻量化数据库方案。

OpenHarmony API Version23 针对 RDB 底层引擎完成大规模升级,核心变更:

  1. 新增数据库连接池管理,避免重复打开数据库产生文件锁冲突;
  2. 强化 RdbStore 自动关闭机制,页面销毁自动释放连接,杜绝文件占用;
  3. 重构 ResultSet 游标内存回收逻辑,必须主动 close,否则持续内存泄漏;
  4. 标准化事务 begin/commit/rollback 执行顺序,修复低版本批量写入丢失;
  5. 统一 SQL 参数绑定防注入规则,禁止直接拼接字符串 SQL 语句;
  6. 限制超大无分页全表查询,超过 1000 条数据强制分页,防止页面卡顿。

旧项目随意打开数据库不关闭、拼接原始 SQL、游标不销毁的写法升级 API23 后极易闪退、内存溢出,因此掌握 API23 标准 RDB 开发规范是本地大数据存储必备技能。

1.2 开发环境与测试场景

开发工具:DevEco Studio 5.0 及以上 适配系统:OpenHarmony API Version23、HarmonyOS NEXT 开发语言:ArkTS 导入模块:@ohos.data.relationalStore 测试场景:笔记数据表创建、新增笔记、修改内容、单条 / 批量删除、分页查询、模糊搜索、事务批量插入、页面退出数据库释放

二、API23+ RDB 核心 API 与版本变更说明

2.1 数据库创建与获取

ets

import relationalStore from '@ohos.data.relationalStore'

// 数据库配置参数
const STORE_CONFIG: relationalStore.StoreConfig = {
  name: "note_db", // 数据库文件名
  securityLevel: relationalStore.SecurityLevel.S1
}
// 获取数据库实例(API23推荐单例全局复用)
let rdbStore: relationalStore.RdbStore | null = null
async function getRdbStore() {
  if (!rdbStore) {
    rdbStore = await relationalStore.getRdbStore(getContext(this), STORE_CONFIG)
    // 首次打开自动建表
    await createTable(rdbStore)
  }
  return rdbStore
}

2.2 核心 CRUD 操作 API

  1. insert (table, valuesBucket):插入单条数据,返回行 ID
  2. update (table, valuesBucket, predicates):按条件更新数据
  3. delete (table, predicates):条件删除数据
  4. query (table, columns, predicates):条件查询,返回 ResultSet 游标
  5. executeSql (sql, bindArgs):执行自定义 SQL 语句,支持批量事务

2.3 事务 API(API23 稳定)

  • beginTransaction ():开启事务
  • commit ():提交事务,数据永久写入
  • rollback ():回滚,放弃本次所有修改

2.4 游标 ResultSet 强制规范

查询返回的 ResultSet 使用完毕必须调用resultSet.close(),API23 不再自动回收,遗漏会造成内存持续泄漏、数据库锁死。

2.5 API23 废弃与强制约束

  1. 废弃全局多处重复 getRdbStore 打开数据库,必须单例复用;
  2. 禁止直接拼接变量进 SQL 字符串,统一使用绑定参数防注入;
  3. 全表查询必须设置分页 offset/limit,禁止一次性加载上万条数据;
  4. 页面销毁必须调用 rdbStore.close () 释放连接;
  5. 不支持异步嵌套事务,同一时间仅允许一层事务执行。

三、API23 标准基础示例代码

3.1 建表语句(笔记表)

ets

async function createTable(store: relationalStore.RdbStore) {
  const sql = `CREATE TABLE IF NOT EXISTS note(
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    title TEXT NOT NULL,
    content TEXT,
    create_time INTEGER
  )`
  await store.executeSql(sql)
}

3.2 单条插入数据

ets

async function insertNote(store: relationalStore.RdbStore, title:string, content:string) {
  let bucket: relationalStore.ValuesBucket = {
    title: title,
    content: content,
    create_time: new Date().getTime()
  }
  let rowId = await store.insert("note", bucket)
  return rowId
}

3.3 分页条件查询

ets

async function queryNotePage(store: relationalStore.RdbStore, page:number, size:number) {
  let predicates = new relationalStore.RdbPredicates("note")
  predicates.orderBy("create_time", false) // 时间倒序
  predicates.offset(page * size).limit(size)
  let resultSet = await store.query("note", ["id","title","content","create_time"], predicates)
  let list = []
  while(resultSet.goToNextRow()) {
    list.push({
      id: resultSet.getLong(0),
      title: resultSet.getString(1),
      content: resultSet.getString(2),
      create_time: resultSet.getLong(3)
    })
  }
  // API23强制关闭游标
  resultSet.close()
  return list
}

3.4 根据 ID 删除数据

ets

async function deleteNoteById(store: relationalStore.RdbStore, id:number) {
  let predicates = new relationalStore.RdbPredicates("note")
  predicates.equalTo("id", id)
  let delCount = await store.delete("note", predicates)
  return delCount
}

四、完整实战案例:离线笔记数据库管理(全兼容 API23)

4.1 全局 RDB 工具类封装(utils/rdb_util.ets)

ets

import relationalStore from '@ohos.data.relationalStore'
import common from '@ohos.app.ability.common'

// 数据库配置
const DB_CONFIG: relationalStore.StoreConfig = {
  name: "note_database",
  securityLevel: relationalStore.SecurityLevel.S1
}

class RdbUtil {
  private static instance: RdbUtil
  private rdbStore: relationalStore.RdbStore | null = null
  private context: common.UIAbilityContext | null = null

  static getInstance(): RdbUtil {
    if (!RdbUtil.instance) {
      RdbUtil.instance = new RdbUtil()
    }
    return RdbUtil.instance
  }

  // 初始化数据库,首页调用一次
  async init(ctx: common.UIAbilityContext) {
    this.context = ctx
    if (!this.rdbStore) {
      this.rdbStore = await relationalStore.getRdbStore(this.context, DB_CONFIG)
      await this.createNoteTable()
    }
  }

  // 创建笔记数据表
  private async createNoteTable() {
    const createSql = `CREATE TABLE IF NOT EXISTS note(
      id INTEGER PRIMARY KEY AUTOINCREMENT,
      title TEXT NOT NULL,
      content TEXT,
      create_time INTEGER
    )`
    await this.rdbStore!.executeSql(createSql)
  }

  // 新增笔记
  async addNote(title: string, content: string): Promise<number> {
    if (!this.rdbStore) return -1
    let bucket: relationalStore.ValuesBucket = {
      title,
      content,
      create_time: new Date().getTime()
    }
    return await this.rdbStore.insert("note", bucket)
  }

  // 根据id修改笔记
  async updateNote(id: number, title: string, content: string): Promise<number> {
    if (!this.rdbStore) return 0
    let bucket: relationalStore.ValuesBucket = { title, content }
    let predicates = new relationalStore.RdbPredicates("note")
    predicates.equalTo("id", id)
    return await this.rdbStore.update("note", bucket, predicates)
  }

  // 根据id删除单条笔记
  async deleteNote(id: number): Promise<number> {
    if (!this.rdbStore) return 0
    let predicates = new relationalStore.RdbPredicates("note")
    predicates.equalTo("id", id)
    return await this.rdbStore.delete("note", predicates)
  }

  // 分页查询所有笔记,时间倒序
  async queryNoteList(page: number, pageSize: number) {
    if (!this.rdbStore) return []
    let predicates = new relationalStore.RdbPredicates("note")
    predicates.orderBy("create_time", false)
    predicates.offset(page * pageSize).limit(pageSize)
    let resultSet = await this.rdbStore.query("note", ["id","title","content","create_time"], predicates)
    let dataList = []
    while (resultSet.goToNextRow()) {
      dataList.push({
        id: resultSet.getLong(0),
        title: resultSet.getString(1),
        content: resultSet.getString(2),
        create_time: resultSet.getLong(3)
      })
    }
    // API23强制释放游标
    resultSet.close()
    return dataList
  }

  // 模糊搜索笔记标题
  async searchNote(keyword: string) {
    if (!this.rdbStore) return []
    let predicates = new relationalStore.RdbPredicates("note")
    predicates.like("title", `%${keyword}%`)
    let resultSet = await this.rdbStore.query("note", ["id","title","content"], predicates)
    let list = []
    while (resultSet.goToNextRow()) {
      list.push({
        id: resultSet.getLong(0),
        title: resultSet.getString(1),
        content: resultSet.getString(2)
      })
    }
    resultSet.close()
    return list
  }

  // 事务批量插入测试数据
  async batchInsertTest(amount: number) {
    if (!this.rdbStore) return
    try {
      this.rdbStore.beginTransaction()
      for(let i=0;i<amount;i++) {
        let bucket = { title:`测试笔记${i}`, content:"批量事务插入内容", create_time:Date.now() }
        await this.rdbStore.insert("note", bucket)
      }
      this.rdbStore.commit()
    } catch(e) {
      this.rdbStore.rollback()
      console.error("批量插入失败,事务回滚", e)
    }
  }

  // 关闭数据库,页面销毁调用
  async closeDB() {
    if (this.rdbStore) {
      await this.rdbStore.close()
      this.rdbStore = null
    }
  }
}

export default RdbUtil.getInstance()

4.2 笔记列表主页面(新增、分页、删除、搜索)

ets

import rdbUtil from '../utils/rdb_util'
import promptAction from '@ohos.promptAction'
import CustomDialog from '../components/InputDialog'

interface NoteItem {
  id: number
  title: string
  content: string
  create_time: number
}

@Entry
@Component
struct NoteRdbPage {
  @State noteList: NoteItem[] = []
  @State pageIndex: number = 0
  readonly PAGE_SIZE = 10
  dialogCtrl: CustomDialogController | null = null

  async aboutToAppear() {
    // 初始化数据库
    await rdbUtil.init(getContext(this))
    await this.loadNoteData()
  }

  // 分页加载数据
  async loadNoteData() {
    let data = await rdbUtil.queryNoteList(this.pageIndex, this.PAGE_SIZE)
    if(this.pageIndex === 0) {
      this.noteList = data
    } else {
      this.noteList = [...this.noteList, ...data]
    }
  }

  // 新增笔记弹窗
  openAddDialog() {
    this.dialogCtrl = new CustomDialogController({
      builder: CustomDialog({
        submit: async (title, content) => {
          await rdbUtil.addNote(title, content)
          promptAction.showToast({message:"新增成功"})
          this.pageIndex = 0
          await this.loadNoteData()
        }
      }),
      autoCancel:true
    })
    this.dialogCtrl.open()
  }

  // 删除笔记
  async deleteItem(id: number) {
    await rdbUtil.deleteNote(id)
    promptAction.showToast({message:"删除完成"})
    this.pageIndex = 0
    await this.loadNoteData()
  }

  build() {
    Column({space:15}) {
      Row() {
        Button("新增笔记").onClick(()=>this.openAddDialog())
        Button("加载下一页").onClick(async ()=>{
          this.pageIndex++
          await this.loadNoteData()
        })
      }
      List({space:12}) {
        ForEach(this.noteList, (item:NoteItem)=>{
          ListItem() {
            Row() {
              Column({space:6}).layoutWeight(1) {
                Text(item.title).fontSize(18).fontWeight(FontWeight.Medium)
                Text(item.content).fontSize(14).fontColor("#666")
              }
              Button("删除").backgroundColor("#f56c6c")
                .onClick(async ()=>await this.deleteItem(item.id))
            }
            .width("100%")
            .padding(15)
            .backgroundColor(Color.White)
            .borderRadius(10)
          }
        })
      }
      .layoutWeight(1)
    }
    .width("100%")
    .height("100%")
    .padding(15)
    .backgroundColor("#F5F5F5")
  }

  async aboutToDisappear() {
    // 页面销毁关闭数据库释放资源 API23必需
    await rdbUtil.closeDB()
  }
}

4.3 新增笔记弹窗组件

ets

@CustomDialog
struct InputDialog {
  controller: CustomDialogController
  @DialogParam submit: (title:string, content:string)=>void
  @State title: string = ""
  @State content: string = ""
  build() {
    Column({space:20}) {
      Text("新建笔记").fontSize(22).fontWeight(FontWeight.Bold)
      TextInput({text:this.title, placeholder:"标题"})
        .width("100%").height(46)
      TextInput({text:this.content, placeholder:"内容"})
        .width("100%").height(46)
      Row({space:15}) {
        Button("取消").layoutWeight(1).onClick(()=>this.controller.close())
        Button("保存").layoutWeight(1).backgroundColor("#007DFF")
          .onClick(async ()=>{
            if(!this.title) return promptAction.showToast({message:"标题不能为空"})
            this.submit(this.title, this.content)
            this.controller.close()
          })
      }
    }
    .width(340)
    .padding(24)
    .backgroundColor(Color.White)
    .borderRadius(16)
  }
}

五、API23+ RDB 数据库适配与性能优化规范

5.1 数据库连接规范

  1. 全局单例工具类管理 RdbStore,全程仅打开一次数据库;
  2. 页面销毁生命周期aboutToDisappear调用 close () 关闭连接,释放文件锁;
  3. 禁止多页面同时重复执行 getRdbStore,会触发文件占用报错。

5.2 游标内存强制规范

  1. 所有 query 查询返回 ResultSet,循环读取完毕必须执行resultSet.close()
  2. 禁止提前 return 跳过 close,使用 try-finally 包裹游标逻辑保证释放;
  3. 长列表分页一次性读取不超过 100 条,减少游标内存占用。

5.3 事务批量操作规范

  1. 批量插入、批量更新统一使用事务,中途异常自动 rollback 回滚;
  2. 事务内避免复杂异步逻辑阻塞,仅执行数据库 CRUD;
  3. 禁止嵌套事务,API23 仅支持单层事务执行。

5.4 SQL 安全规范

  1. 禁止字符串拼接变量构造 SQL,全部使用 ValuesBucket 参数绑定;
  2. 模糊查询 like 语句统一使用%关键字%绑定,防止 SQL 注入漏洞。

六、API23 升级高频兼容问题与解决方案

问题 1:多次进入页面提示数据库文件被占用、打开失败 解决:全局单例复用 RdbStore,页面销毁执行 close 关闭数据库连接。

问题 2:页面多次查询后内存持续上涨、应用卡顿闪退 解决:所有 ResultSet 读取完成强制调用 close 释放游标,不要遗漏。

问题 3:批量插入部分数据丢失,重启数据库数据不全 解决:批量操作包裹事务 begin/commit,捕获异常执行 rollback 回滚。

问题 4:升级 API23 后直接拼接 SQL 语句编译警告、存在安全风险 解决:全部替换为 RdbPredicates 条件查询、ValuesBucket 参数绑定。

问题 5:全表查询上千条数据页面滑动严重掉帧 解决:统一分页 offset+limit 加载,禁止一次性查询全部数据。

问题 6:页面退出再次进入,数据库无响应卡死 解决:页面销毁执行 close 关闭实例,下次进入重新初始化。

七、总结

RDB 关系型数据库是 OpenHarmony 处理大批量结构化离线数据的核心存储方案,API Version23 重构数据库连接、游标回收、事务底层逻辑,强化资源释放强制约束,大幅优化内存占用与并发读写稳定性。项目标准分层存储架构:少量配置使用 Preferences、海量结构化数据使用 RDB、全局内存缓存使用 AppStorage。

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐