一、前言

前面讲解的 Preferences 仅适合存储轻量键值对,面对通讯录、备忘录、商品清单、大量结构化数据时能力不足。OpenHarmony 提供 RDB(关系型数据库),基于 SQLite 内核,支持标准 SQL 语句、数据表创建、增删改查、事务操作,是应用存储大批量结构化数据的首选方案。

本文从环境依赖、数据库初始化、数据表设计、基础 CRUD、分页查询、事务操作一步步讲解,搭配完整可运行案例,同时结合前文组件、弹窗完成综合业务实战。

适用场景:备忘录、联系人、本地账单、离线缓存列表等复杂数据存储。 依赖模块:@ohos.data.rdb

二、核心概念与前置说明

  1. 核心对象
    • RdbStore:数据库实例,执行增删改查、事务的核心对象
    • RdbHelper:数据库工具类,负责打开 / 创建数据库
    • ResultSet:查询结果集,遍历读取查询数据
  2. 数据类型:支持整型、浮点型、字符串、布尔值,兼容 SQLite 字段规则
  3. 权限:本地数据库无需额外申请权限,应用沙盒内私有存储

三、基础使用:数据库与数据表初始化

3.1 导入模块 & 工具类封装

统一封装数据库工具,避免重复编写初始化代码,便于项目维护。

ets

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

// 数据库全局工具类
class RdbUtil {
  private static rdbStore: rdb.RdbStore | null = null
  // 数据库名称
  private static readonly DB_NAME = "app_database.db"
  // 数据库版本
  private static readonly DB_VERSION = 1

  /**
   * 初始化数据库 & 创建数据表
   * @param context 应用上下文
   */
  static async initDB(context: common.UIAbilityContext) {
    if (this.rdbStore) {
      return this.rdbStore
    }
    // 数据库配置
    let config: rdb.RdbStoreConfig = {
      name: this.DB_NAME,
      securityLevel: rdb.SecurityLevel.S1
    }
    // 打开/创建数据库
    this.rdbStore = await rdb.getRdbStore(context, config, this.DB_VERSION, (store) => {
      // 数据库首次创建时执行:建表语句
      const createTableSql = `
        CREATE TABLE IF NOT EXISTS user_note (
          id INTEGER PRIMARY KEY AUTOINCREMENT,
          title TEXT NOT NULL,
          content TEXT,
          create_time TEXT
        )
      `
      store.executeSql(createTableSql)
    })
    return this.rdbStore
  }

  // 获取数据库实例
  static getStore(): rdb.RdbStore | null {
    return this.rdbStore
  }
}
  • 数据表 user_note:简易备忘录表,id 自增主键,title 标题,content 内容,create_time 创建时间。
  • 版本升级:如需修改表结构,递增 DB_VERSION 并在版本回调中编写更新 SQL。

3.2 应用入口初始化数据库

在页面生命周期中完成数据库初始化,全局只需执行一次。

ets

@Entry
@Component
struct RdbIndex {
  context = getContext(this) as common.UIAbilityContext

  aboutToAppear() {
    // 初始化数据库
    RdbUtil.initDB(this.context)
  }

  build() {
    Column() {
      Text("RDB 关系型数据库演示")
        .fontSize(22)
        .fontWeight(FontWeight.Bold)
    }
    .width("100%")
    .height("100%")
    .justifyContent(FlexAlign.Center)
  }
}

四、数据表 CRUD 基础操作

4.1 新增数据(插入记录)

使用 insert 方法插入单条数据,接收表名与数据对象。

ets

@Entry
@Component
struct RdbInsertDemo {
  context = getContext(this) as common.UIAbilityContext

  // 插入备忘录数据
  async insertNote() {
    let store = await RdbUtil.initDB(this.context)
    if (!store) return

    // 构造插入数据
    let valueBucket: rdb.ValuesBucket = {
      title: "第一条备忘录",
      content: "学习 OpenHarmony RDB 数据库",
      create_time: new Date().toLocaleString()
    }
    // 执行插入
    let rowId = await store.insert("user_note", valueBucket)
    console.info("插入成功,行ID:", rowId)
  }

  build() {
    Column({ space: 20 }) {
      Button("新增一条备忘录")
        .width(220)
        .onClick(() => this.insertNote())
    }
    .width("100%")
    .height("100%")
    .justifyContent(FlexAlign.Center)
  }
}

4.2 查询数据(全量查询 + 条件查询)

使用 query 方法查询数据,通过 ResultSet 遍历结果集。

ets

@Entry
@Component
struct RdbQueryDemo {
  @State noteList: string[] = []
  context = getContext(this) as common.UIAbilityContext

  // 查询全部数据
  async queryAll() {
    let store = await RdbUtil.initDB(this.context)
    if (!store) return

    // 查询条件:查所有字段、无筛选条件
    let predicates = new rdb.RdbPredicates("user_note")
    let resultSet = await store.query(predicates)

    this.noteList = []
    // 遍历结果集
    while (resultSet.goToNextRow()) {
      let id = resultSet.getLong(resultSet.getColumnIndex("id"))
      let title = resultSet.getString(resultSet.getColumnIndex("title"))
      let time = resultSet.getString(resultSet.getColumnIndex("create_time"))
      this.noteList.push(`ID:${id} 标题:${title} 时间:${time}`)
    }
    // 关闭结果集,释放资源
    resultSet.close()
  }

  build() {
    Column({ space: 15 }) {
      Button("查询全部备忘录").onClick(() => this.queryAll())
      List({ space: 5 }) {
        ForEach(this.noteList, (item: string) => {
          ListItem() {
            Text(item).fontSize(16).padding(10)
          }
        })
      }
      .layoutWeight(1)
      .width("100%")
    }
    .width("100%")
    .height("100%")
    .padding(20)
  }
}

条件查询示例(根据标题模糊查询)

ets

async queryByTitle(keyword: string) {
  let store = RdbUtil.getStore()
  if (!store) return
  let predicates = new rdb.RdbPredicates("user_note")
  // 模糊匹配
  predicates.like("title", `%${keyword}%`)
  let resultSet = await store.query(predicates)
  // 遍历逻辑同上
  resultSet.close()
}

4.3 修改数据

通过 RdbPredicates 设置条件,使用 update 方法更新字段。

ets

async updateNote() {
  let store = RdbUtil.getStore()
  if (!store) return

  // 要更新的内容
  let valueBucket: rdb.ValuesBucket = {
    title: "修改后的标题",
    content: "内容已更新"
  }
  // 更新条件:id = 1
  let predicates = new rdb.RdbPredicates("user_note")
  predicates.equalTo("id", 1)

  let count = await store.update(valueBucket, predicates)
  console.info("受影响行数:", count)
}

4.4 删除数据

支持按条件删除、清空整张表。

ets

// 按ID删除
async deleteNoteById(delId: number) {
  let store = RdbUtil.getStore()
  if (!store) return
  let predicates = new rdb.RdbPredicates("user_note")
  predicates.equalTo("id", delId)
  let count = await store.delete(predicates)
  console.info("删除行数:", count)
}

// 清空整张表
async clearTable() {
  let store = RdbUtil.getStore()
  if (!store) return
  let predicates = new rdb.RdbPredicates("user_note")
  await store.delete(predicates)
}

五、进阶用法:分页查询

数据量较大时,使用分页加载提升性能,核心依靠 predicates.offset()predicates.limit()

ets

/**
 * 分页查询
 * @param pageIndex 页码,从0开始
 * @param pageSize 每页条数
 */
async queryByPage(pageIndex: number, pageSize: number) {
  let store = RdbUtil.getStore()
  if (!store) return
  let predicates = new rdb.RdbPredicates("user_note")
  predicates.offset(pageIndex * pageSize) // 偏移量
  predicates.limit(pageSize)             // 每页数量
  predicates.orderByDesc("id")           // 按ID倒序

  let resultSet = await store.query(predicates)
  // 遍历读取数据
  resultSet.close()
}

六、进阶用法:事务操作

多条数据批量增删改时,使用事务保证数据一致性,要么全部执行成功,要么全部回滚。

ets

async batchInsertWithTransaction() {
  let store = RdbUtil.getStore()
  if (!store) return

  try {
    // 开启事务
    await store.beginTransaction()

    // 批量插入多条数据
    let data1: rdb.ValuesBucket = { title: "事务笔记1", content: "测试事务", create_time: new Date().toLocaleString() }
    let data2: rdb.ValuesBucket = { title: "事务笔记2", content: "测试事务", create_time: new Date().toLocaleString() }
    await store.insert("user_note", data1)
    await store.insert("user_note", data2)

    // 提交事务
    await store.commit()
    console.info("事务执行成功")
  } catch (err) {
    // 异常回滚
    await store.rollBack()
    console.error("事务执行失败,已回滚", err)
  }
}

七、综合实战:备忘录完整页面(RDB + 自定义弹窗 + List)

整合前面所学的 CustomDialog 自定义弹窗、List 列表、RDB 数据库,实现新增、查看、删除备忘录完整功能。

7.1 新增笔记弹窗

ets

@CustomDialog
struct AddNoteDialog {
  controller: CustomDialogController
  @DialogParam onSubmit: (title: string, content: string) => void
  @State noteTitle: string = ""
  @State noteContent: string = ""

  build() {
    Column({ space: 15 }) {
      Text("新增备忘录").fontSize(20).fontWeight(FontWeight.Bold)
      TextInput({ placeholder: "请输入标题" })
        .width("100%")
        .height(45)
        .onChange(v => this.noteTitle = v)
      TextInput({ placeholder: "请输入内容" })
        .width("100%")
        .height(80)
        .onChange(v => this.noteContent = v)

      Row({ space: 20 }) {
        Button("取消")
          .layoutWeight(1)
          .backgroundColor("#999")
          .onClick(() => this.controller.close())
        Button("保存")
          .layoutWeight(1)
          .backgroundColor("#007DFF")
          .onClick(() => {
            this.onSubmit(this.noteTitle, this.noteContent)
            this.controller.close()
          })
      }
    }
    .width(320)
    .padding(20)
    .backgroundColor(Color.White)
    .borderRadius(12)
  }
}

7.2 主页面(列表展示 + 增删操作)

ets

@Entry
@Component
struct NoteMainPage {
  @State noteArr: Array<rdb.ValuesBucket> = []
  context = getContext(this) as common.UIAbilityContext
  // 弹窗控制器
  addDialog: CustomDialogController = new CustomDialogController({
    builder: AddNoteDialog({
      onSubmit: (title: string, content: string) => {
        this.saveNote(title, content)
      }
    }),
    autoCancel: false
  })

  aboutToAppear() {
    // 初始化数据库 + 加载列表
    RdbUtil.initDB(this.context).then(() => {
      this.loadNoteList()
    })
  }

  // 保存笔记到数据库
  async saveNote(title: string, content: string) {
    let store = RdbUtil.getStore()
    if (!store || !title) return
    let bucket: rdb.ValuesBucket = {
      title: title,
      content: content,
      create_time: new Date().toLocaleString()
    }
    await store.insert("user_note", bucket)
    // 重新刷新列表
    this.loadNoteList()
  }

  // 加载列表数据
  async loadNoteList() {
    let store = RdbUtil.getStore()
    if (!store) return
    let predicates = new rdb.RdbPredicates("user_note")
    predicates.orderByDesc("id")
    let resultSet = await store.query(predicates)

    let tempList: Array<rdb.ValuesBucket> = []
    while (resultSet.goToNextRow()) {
      tempList.push({
        id: resultSet.getLong(resultSet.getColumnIndex("id")),
        title: resultSet.getString(resultSet.getColumnIndex("title")),
        content: resultSet.getString(resultSet.getColumnIndex("content")),
        create_time: resultSet.getString(resultSet.getColumnIndex("create_time"))
      })
    }
    resultSet.close()
    this.noteArr = tempList
  }

  // 删除单条笔记
  async deleteItem(id: number) {
    let store = RdbUtil.getStore()
    if (!store) return
    let predicates = new rdb.RdbPredicates("user_note")
    predicates.equalTo("id", id)
    await store.delete(predicates)
    this.loadNoteList()
  }

  build() {
    Column() {
      // 顶部标题+新增按钮
      Row() {
        Text("我的备忘录")
          .fontSize(22)
          .fontWeight(FontWeight.Bold)
          .layoutWeight(1)
        Button("新增")
          .width(80)
          .onClick(() => this.addDialog.open())
      }
      .width("100%")
      .padding(15)

      // 笔记列表
      List({ space: 8 }) {
        ForEach(this.noteArr, (item: rdb.ValuesBucket) => {
          ListItem() {
            Row() {
              Column({ space: 5 }) {
                Text(item.title as string)
                  .fontSize(18)
                  .fontWeight(FontWeight.Bold)
                Text(item.content as string)
                  .fontSize(14)
                  .fontColor("#666")
                Text(item.create_time as string)
                  .fontSize(12)
                  .fontColor("#999")
              }
              .layoutWeight(1)

              Button("删除")
                .width(70)
                .backgroundColor("#f56c6c")
                .onClick(() => this.deleteItem(item.id as number))
            }
            .width("100%")
            .padding(12)
            .backgroundColor(Color.White)
            .borderRadius(8)
          }
        })
      }
      .layoutWeight(1)
      .width("100%")
    }
    .width("100%")
    .height("100%")
    .backgroundColor("#F5F5F5")
  }
}

八、开发规范与踩坑总结

8.1 使用规范

  1. 资源释放ResultSet 使用完毕必须调用 close(),避免内存泄漏。
  2. 数据库初始化:全局仅初始化一次,建议放在应用入口页面。
  3. 批量操作:多条增删改优先使用事务,提升执行效率、保证数据安全。
  4. 分表原则:不同业务数据拆分不同数据表,不要全部存在一张表。
  5. 版本管理:表结构变更时,递增数据库版本号,编写版本升级逻辑。

8.2 常见问题

  1. 表不存在:检查建表 SQL 语句是否正确,数据库初始化是否执行。
  2. 查询无数据:核对字段名、查询条件大小写,SQLite 默认区分字段名。
  3. 卡顿问题:大数据列表必须使用分页查询,禁止一次性查询全表。
  4. 线程阻塞:所有数据库操作均为异步,统一使用 async/await

8.3 选型对比

表格

存储方案 适用场景 优缺点
Preferences 轻量配置、开关、登录状态 简单易用,不支持复杂结构
RDB 大量结构化数据、列表数据 支持 SQL、事务、分页,功能强大

九、系列总结 & 后续拓展

至此,整套 OpenHarmony 应用开发核心技术栈 全部完结: UI 组件 → 布局 / 懒加载 → 路由传参 → Preferences → 网络请求 → AppStorage 全局状态 → 自定义弹窗 → RDB 数据库

Logo

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

更多推荐