通过关系型数据库实现数据持久化 (ArkTS) 使用指南

效果

一、概述

HarmonyOS 提供了基于 SQLite 的关系型数据库(RelationalStore),适用于存储结构化、具有复杂关系的数据。开发者可以通过 @kit.ArkData 中的 relationalStore 模块,对本地数据进行增删改查操作,实现数据的持久化存储。

1.1 适用场景

  • 用户账号信息的本地缓存
  • 订单、商品等结构化数据存储
  • 离线数据的本地保存与同步
  • 聊天记录、笔记等频繁增删改查的数据

1.2 核心概念

概念 说明
RdbStore 关系型数据库实例,通过 getRdbStore 获取,是操作数据库的入口
StoreConfig 数据库配置对象,包含数据库文件名、安全等级、是否加密等
ValuesBucket 数据容器,以键值对形式存储一行数据,键为字段名,值为字段值
RdbPredicates 查询谓词,用于构建 WHERE 条件,支持链式调用
ResultSet 查询结果集(游标),需手动遍历并在使用后关闭

二、环境准备

2.1 导入模块

import { relationalStore } from '@kit.ArkData';
import { BusinessError } from '@kit.BasicServicesKit';
import { hilog } from '@kit.PerformanceAnalysisKit';

2.2 数据库安全等级说明

等级 说明
S1 最低安全等级,适用于非敏感数据
S2 中等安全等级
S3 较高安全等级,适用于一般敏感数据
S4 最高安全等级,适用于高度敏感数据(如用户隐私)

三、实现流程

3.1 第一步:获取数据库实例(建库)

通过 relationalStore.getRdbStore 方法获取 RdbStore 实例:

const STORE_CONFIG: relationalStore.StoreConfig = {
  name: 'myApp.db',                           // 数据库文件名
  securityLevel: relationalStore.SecurityLevel.S1  // 安全等级
};

let rdbStore: relationalStore.RdbStore | undefined = undefined;

async function initDatabase(context: Context): Promise<void> {
  try {
    rdbStore = await relationalStore.getRdbStore(context, STORE_CONFIG);
    hilog.info(0x0000, 'RDB', 'Succeeded in getting RdbStore.');
  } catch (err) {
    hilog.error(0x0000, 'RDB', 'Failed to get RdbStore: %{public}s', JSON.stringify(err));
  }
}

注意RdbStore 是单例的,同一个数据库文件在应用中只需创建一次,其他地方直接复用即可。

3.2 第二步:创建数据表(建表)

使用 executeSql 执行 SQL 语句创建表结构:

const CREATE_TABLE_SQL = `CREATE TABLE IF NOT EXISTS userInfo (
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  userName TEXT NOT NULL,
  email TEXT NOT NULL,
  age INTEGER,
  createTime TEXT
)`;

async function createTable(): Promise<void> {
  if (rdbStore) {
    await rdbStore.executeSql(CREATE_TABLE_SQL);
    hilog.info(0x0000, 'RDB', 'Succeeded in creating table.');
  }
}

关键点:务必使用 IF NOT EXISTS,避免应用第二次启动时因表已存在而报错。

3.3 第三步:插入数据(增)

使用 ValuesBucket 封装数据,调用 insert 方法插入:

async function insertUser(userName: string, email: string, age: number): Promise<number> {
  const valueBucket: relationalStore.ValuesBucket = {
    userName: userName,
    email: email,
    age: age,
    createTime: new Date().toISOString()
  };

  if (rdbStore) {
    const rowId = await rdbStore.insert('userInfo', valueBucket);
    hilog.info(0x0000, 'RDB', 'Succeeded in inserting, rowId: %{public}d', rowId);
    return rowId;
  }
  return -1;
}

ValuesBucket 本质是键值对对象,键对应表的字段名,值对应字段值。插入后自增 id 会自动生成。

3.4 第四步:查询数据(查)

使用 RdbPredicates 构建查询条件,调用 query 方法获取 ResultSet

interface UserInfo {
  id: number;
  userName: string;
  email: string;
  age: number;
}

async function queryAllUsers(): Promise<UserInfo[]> {
  const predicates = new relationalStore.RdbPredicates('userInfo');
  predicates.orderByDesc('createTime');  // 按创建时间倒序

  const resultList: UserInfo[] = [];

  if (rdbStore) {
    const resultSet = await rdbStore.query(predicates, ['id', 'userName', 'email', 'age']);
    while (resultSet.goToNextRow()) {
      const user: UserInfo = {
        id: resultSet.getLong(resultSet.getColumnIndex('id')),
        userName: resultSet.getString(resultSet.getColumnIndex('userName')),
        email: resultSet.getString(resultSet.getColumnIndex('email')),
        age: resultSet.getLong(resultSet.getColumnIndex('age'))
      };
      resultList.push(user);
    }
    resultSet.close();  // 【重要】使用完毕后必须关闭结果集
  }
  return resultList;
}

重要ResultSet 是游标对象,使用完毕后必须调用 close() 方法释放资源,否则会造成内存泄漏。

3.5 第五步:更新数据(改)

结合 ValuesBucketRdbPredicates 实现条件更新:

async function updateUserName(id: number, newUserName: string): Promise<number> {
  const valueBucket: relationalStore.ValuesBucket = {
    userName: newUserName
  };

  const predicates = new relationalStore.RdbPredicates('userInfo');
  predicates.equalTo('id', id);

  if (rdbStore) {
    const updatedRows = await rdbStore.update(valueBucket, predicates);
    hilog.info(0x0000, 'RDB', 'Updated %{public}d rows', updatedRows);
    return updatedRows;
  }
  return 0;
}

更新时只需在 ValuesBucket 中放入要修改的字段,未指定的字段保持不变。

3.6 第六步:删除数据(删)

使用 RdbPredicates 指定删除条件:

async function deleteUser(id: number): Promise<number> {
  const predicates = new relationalStore.RdbPredicates('userInfo');
  predicates.equalTo('id', id);

  if (rdbStore) {
    const deletedRows = await rdbStore.delete(predicates);
    hilog.info(0x0000, 'RDB', 'Deleted %{public}d rows', deletedRows);
    return deletedRows;
  }
  return 0;
}

四、完整示例:待办事项管理器

以下是一个完整的待办事项 CRUD 示例,涵盖建库、建表、增删改查全流程。

4.1 定义数据模型

// TodoItem.ets
export interface TodoItem {
  id: number;
  title: string;
  done: boolean;
  createTime: string;
}

4.2 封装数据库管理类

// TodoDatabase.ets
import { relationalStore } from '@kit.ArkData';
import { TodoItem } from './TodoItem';

const STORE_CONFIG: relationalStore.StoreConfig = {
  name: 'todo.db',
  securityLevel: relationalStore.SecurityLevel.S1
};

const CREATE_TABLE_SQL = `CREATE TABLE IF NOT EXISTS todo (
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  title TEXT NOT NULL,
  done INTEGER DEFAULT 0,
  createTime TEXT
)`;

export class TodoDatabase {
  private rdbStore: relationalStore.RdbStore | null = null;

  async init(context: Context): Promise<void> {
    this.rdbStore = await relationalStore.getRdbStore(context, STORE_CONFIG);
    await this.rdbStore.executeSql(CREATE_TABLE_SQL);
  }

  async addTodo(title: string): Promise<number> {
    const value: relationalStore.ValuesBucket = {
      title: title,
      done: 0,
      createTime: new Date().toISOString()
    };
    return await this.rdbStore!.insert('todo', value);
  }

  async getAllTodos(): Promise<TodoItem[]> {
    const predicates = new relationalStore.RdbPredicates('todo');
    predicates.orderByDesc('createTime');
    const resultSet = await this.rdbStore!.query(predicates, ['id', 'title', 'done']);
    const list: TodoItem[] = [];
    while (resultSet.goToNextRow()) {
      list.push({
        id: resultSet.getLong(resultSet.getColumnIndex('id')),
        title: resultSet.getString(resultSet.getColumnIndex('title')),
        done: resultSet.getLong(resultSet.getColumnIndex('done')) === 1,
        createTime: resultSet.getString(resultSet.getColumnIndex('createTime'))
      });
    }
    resultSet.close();
    return list;
  }

  async toggleTodo(id: number, done: boolean): Promise<number> {
    const value: relationalStore.ValuesBucket = { done: done ? 1 : 0 };
    const predicates = new relationalStore.RdbPredicates('todo');
    predicates.equalTo('id', id);
    return await this.rdbStore!.update(value, predicates);
  }

  async deleteTodo(id: number): Promise<number> {
    const predicates = new relationalStore.RdbPredicates('todo');
    predicates.equalTo('id', id);
    return await this.rdbStore!.delete(predicates);
  }
}

4.3 页面中使用

// TodoPage.ets
import { TodoDatabase } from '../database/TodoDatabase';
import { TodoItem } from '../database/TodoItem';

@Entry
@Component
struct TodoPage {
  @State todoList: TodoItem[] = [];
  @State inputText: string = '';
  private db: TodoDatabase = new TodoDatabase();

  async aboutToAppear() {
    await this.db.init(getContext(this));
    this.todoList = await this.db.getAllTodos();
  }

  build() {
    Column() {
      // 输入区域
      Row() {
        TextInput({ placeholder: '输入待办事项' })
          .onChange((value: string) => { this.inputText = value; })
          .layoutWeight(1)
        Button('添加')
          .onClick(async () => {
            if (this.inputText.trim() !== '') {
              await this.db.addTodo(this.inputText);
              this.todoList = await this.db.getAllTodos();
              this.inputText = '';
            }
          })
      }
      .width('90%')
      .margin({ top: 20 })

      // 列表展示
      List() {
        ForEach(this.todoList, (item: TodoItem) => {
          ListItem() {
            Row() {
              Text(item.title)
                .fontSize(16)
                .decoration({ type: item.done ? TextDecorationType.LineThrough : TextDecorationType.None })
                .layoutWeight(1)
              Button(item.done ? '撤销' : '完成')
                .onClick(async () => {
                  await this.db.toggleTodo(item.id, !item.done);
                  this.todoList = await this.db.getAllTodos();
                })
              Button('删除')
                .onClick(async () => {
                  await this.db.deleteTodo(item.id);
                  this.todoList = await this.db.getAllTodos();
                })
            }
            .width('100%')
            .padding(10)
          }
        })
      }
      .width('90%')
      .margin({ top: 20 })
    }
    .width('100%')
    .height('100%')
  }
}

五、常见误区与注意事项

误区 说明
建表漏掉 IF NOT EXISTS 应用第二次启动时表已存在,再次 CREATE TABLE 会报错
ResultSetclose() 查询返回的游标占用资源,用完必须关闭,否则内存泄漏
忘记主键自增 未设 AUTOINCREMENT,手动插入相同 id 会冲突
一条数据超过 2MB 超过 2MB 的数据可能插入成功但读取失败
数据库安全等级过低 涉及用户隐私数据应使用 S3 或 S4 等级

六、API 速查表

接口 说明
getRdbStore(context, config) 获取 RdbStore 实例
executeSql(sql) 执行不返回值的 SQL(如建表)
insert(table, values) 插入一行数据,返回行 ID
delete(predicates) 按条件删除数据
update(values, predicates) 按条件更新数据
query(predicates, columns) 按条件查询,返回 ResultSet
deleteRdbStore(context, name) 删除数据库文件

RdbPredicates 常用条件方法

方法 说明
equalTo(field, value) 等于
notEqualTo(field, value) 不等于
greaterThan(field, value) 大于
lessThan(field, value) 小于
like(field, value) 模糊匹配
beginsWith(field, value) 以某字符串开头
in(field, values) 在集合中
orderByAsc(field) 升序排列
orderByDesc(field) 降序排列
limitAs(offset, count) 分页限制

七、总结

通过关系型数据库实现数据持久化是 HarmonyOS 应用开发中处理结构化数据的核心方案。开发者只需掌握以下核心流程:

  1. 建库:通过 getRdbStore 获取数据库实例
  2. 建表:通过 executeSql 执行建表 SQL
  3. 增删改查:使用 ValuesBucket + RdbPredicates + 对应接口完成 CRUD
  4. 资源释放ResultSet 用完后务必调用 close()

合理运用关系型数据库,可以让应用的数据管理更加高效、可靠。

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐