HarmonyOS 鸿蒙Next中关系数据库使用

HarmonyOS 鸿蒙Next中关系数据库使用 一下午捣鼓了一下数据存储,目前是给TODO应用写了一个数据库工具,感觉是比较麻烦,不过也正常,但是就是希望可以多学习一些技巧和方法,帮助管理数据,因为之前就只搞过简单数据库,有点吃不下,请不吝赐教。

首先是看一下需要用到的接口:

// 用户创建的表
export interface ListTmp
{
  title: string, // 列表的名字
  // ...
}

// 枚举状态
export enum ItemStatus
{
  Finish, // 完成
  Overdue, // 逾期
  // ...
}

// 一条一条的todo项目
export interface TodoTmp
{
  title: string, // 条目的标题
  content: string, // 条目的内容
  TAG: string[], // 条目所属的tag
  isDone: boolean, // 完成状态
  status: ItemStatus,
  start: Date, // 起始时间
  deadLine: Date, // 结束时间
  // ...
}

然后就是具体数据库的工具:

//Util.ets
import { relationalStore } from '@kit.ArkData'
import { hilog } from '@kit.PerformanceAnalysisKit'
import { BusinessError } from '@kit.BasicServicesKit'
import { ListTmp, TodoTmp, ItemStatus } from '../model/AllInterface'

const TAG: string = 'RDBStoreUtil'

export class RDBStoreUtil
{
  objectiveRDB?: relationalStore.RdbStore

  createObjectiveRDB(context: Context)
  {
    const STORE_CONFIG: relationalStore.StoreConfig = {
      name: 'Objective.db',
      securityLevel: relationalStore.SecurityLevel.S1,
    }
    relationalStore.getRdbStore(context, STORE_CONFIG, (err: BusinessError, rdbStore: relationalStore.RdbStore) => {
      if (err)
      {
        hilog.error(0x0000, TAG, `Get RdbStore failed, code is ${err.code},message is ${err.message}`)
        return
      }
      this.objectiveRDB = rdbStore;
      hilog.info(0x0000, TAG, 'Get RdbStore successfully.')
      
      this.createListTable();
      this.createTodoTable();
    })
  }

  /**
   * 创建「列表」表(对应ListTmp接口)
   */
  createListTable()
  {
    const createListSql = `
      CREATE TABLE IF NOT EXISTS LISTS (
        ID INTEGER PRIMARY KEY AUTOINCREMENT,
        title TEXT NOT NULL -- 列表名称(对应ListTmp.title)
      )
    `
    this.objectiveRDB?.execute(createListSql)
      .then(() => hilog.info(0x0000, TAG, `Create lists table success`))
      .catch((err: BusinessError) =>
      hilog.error(0x0000, TAG, `Create lists table failed: ${err.code} - ${err.message}`)
      )
  }

  /**
   * 创建「待办项」表(对应TodoItem接口)
   */
  createTodoTable()
  {
    const createTodoSql = `
      CREATE TABLE IF NOT EXISTS TODOS (
        ID INTEGER PRIMARY KEY AUTOINCREMENT,
        title TEXT NOT NULL, -- 条目标题(对应TodoItem.title)
        content TEXT NOT NULL, -- 条目内容(对应TodoItem.content)
        tag TEXT NOT NULL, -- 标签数组(存JSON字符串,对应TodoItem.TAG)
        is_done INTEGER NOT NULL, -- 完成状态(0=false/1=true,对应TodoItem.isDone)
        status TEXT NOT NULL, -- 状态枚举(存字符串值,对应TodoItem.status)
        start TEXT NOT NULL, -- 开始时间(ISO字符串,对应TodoItem.start)
        deadline TEXT NOT NULL -- 截止时间(ISO字符串,对应TodoItem.deadLine)
      )
    `
    this.objectiveRDB?.execute(createTodoSql)
      .then(() => hilog.info(0x0000, TAG, `Create todos table success`))
      .catch((err: BusinessError) =>
      hilog.error(0x0000, TAG, `Create todos table failed: ${err.code} - ${err.message}`)
      )
  }

  /**
   * 插入一个列表
   */
  async insertList(listItem: ListTmp)
  {
    const listData: relationalStore.ValuesBucket = {
      'title': listItem.title
    }
    await this.objectiveRDB?.insert('LISTS', listData,
      relationalStore.ConflictResolution.ON_CONFLICT_REPLACE).then((rowId: number) => {
      hilog.info(0x0000, TAG, `Insert is successful, rowId = ${rowId}`);
    }).catch((err: BusinessError) => {
      hilog.error(0x0000, TAG, `Insert is failed, code is ${err.code},message is ${err.message}`);
    })
  }

  /**
   * 插入一个待办事项
   */
  async insertTodo(todoItem: TodoTmp)
  {
    const todoData: relationalStore.ValuesBucket = {
      'title': todoItem.title,
      'content': todoItem.content,
      'tag': todoItem.TAG.toString(),
      'is_done': todoItem.isDone,
      'status': todoItem.status,
      'start': todoItem.start.toString(),
      'deadline': todoItem.deadLine.toString()
    }
    await this.objectiveRDB?.insert('TODOS', todoData,
      relationalStore.ConflictResolution.ON_CONFLICT_REPLACE).then((rowId: number) => {
      hilog.info(0x0000, TAG, `Insert is successful, rowId = ${rowId}`);
    }).catch((err: BusinessError) => {
      hilog.error(0x0000, TAG, `Insert is failed, code is ${err.code},message is ${err.message}`);
    })
  }

  /**
   * 查询所有列表
   */
  async queryAllLists(): Promise<ListTmp[]>
  {
    let listsSet: Array<ListTmp> = []
    await this.objectiveRDB?.querySql(
      `SELECT title FROM LISTS`
    )
      .then((resultSet: relationalStore.ResultSet) => {
        while (resultSet.goToNextRow())
        {
          const id = resultSet.getValue(resultSet.getColumnIndex('ID')) as number
          const title = resultSet.getValue(resultSet.getColumnIndex('title')) as string;
        }
        resultSet.close();
      }).catch((err: BusinessError) => {
        hilog.error(0x0000, TAG, `Query failed, code is ${err.code},message is ${err.message}`)
      })
    return listsSet;
  }

  /**
   * 查询所有待办
   */
  async queryAllTodos(): Promise<TodoTmp[]>
  {
    let todosSet: Array<TodoTmp> = []
    await this.objectiveRDB?.querySql(
      `SELECT title, content, tag, is_done, status, start, deadline FROM TODOS`
    )
      .then((resultSet: relationalStore.ResultSet) => {
        while (resultSet.goToNextRow())
        {
          const id = resultSet.getValue(resultSet.getColumnIndex('ID')) as number
          const title = resultSet.getValue(resultSet.getColumnIndex('title')) as string
          const content = resultSet.getValue(resultSet.getColumnIndex('content')) as string
          const tagStr = resultSet.getValue(resultSet.getColumnIndex('tag')) as string;
          let tag: string[] = [];
          try
          {
            tag = JSON.parse(tagStr) as string[]; // 反向解析插入时的JSON字符串
          }
          catch (err)
          {
            hilog.error(0x0000, TAG, `Parse tag failed for todo ${id}: ${(err as Error).message}`);
          }

          const isDone = Boolean(resultSet.getValue(resultSet.getColumnIndex('is_done')) as number);

          const status = resultSet.getValue(resultSet.getColumnIndex('status')) as ItemStatus;
          const start = resultSet.getValue(resultSet.getColumnIndex('start')) as string;
          const deadLine = resultSet.getValue(resultSet.getColumnIndex('deadline')) as string;
        }
        resultSet.close();
      }).catch((err: BusinessError) => {
        hilog.error(0x0000, TAG, `Query failed, code is ${err.code},message is ${err.message}`)
      })
    return todosSet
  }

  // ========================================
  // 【新增】更新列表(匹配示例风格)
  // ========================================
  /**
   * The method of updating a list information.
   * @param listID 列表主键ID
   * @param listItem 待更新的列表信息
   */
  async updateList(listID: number, listItem: ListTmp)
  {
    if (!this.objectiveRDB)
    {
      const errMsg = 'RDBStore未初始化,无法更新列表'
      hilog.error(0x0000, TAG, errMsg)
      throw new Error(errMsg)
    }
    // 1. 构建更新字段(ListTmp仅含title)
    const listData: relationalStore.ValuesBucket = {
      'title': listItem.title
    }
    // 2. 构建条件:通过ID定位
    const predicates = new relationalStore.RdbPredicates('LISTS')
    predicates.equalTo('ID', listID)
    // 3. 执行更新
    await this.objectiveRDB.update(
      listData,
      predicates,
      relationalStore.ConflictResolution.ON_CONFLICT_REPLACE
    ).then(async (rows: Number) => {
      hilog.info(0x0000, TAG, `Updated list row count: ${rows}`)
    }).catch((err: BusinessError) => {
      hilog.error(0x0000, TAG, `Update list failed, code is ${err.code}, message is ${err.message}`)
    })
  }

  // ========================================
  // 【新增】更新待办(匹配示例风格 + 字段转换)
  // ========================================
  /**
   * The method of updating a todo information.
   * @param todoID 待办主键ID
   * @param todoItem 待更新的待办信息
   */
  async updateTodo(todoID: number, todoItem: TodoTmp)
  {
    if (!this.objectiveRDB)
    {
      const errMsg = 'RDBStore未初始化,无法更新待办'
      hilog.error(0x0000, TAG, errMsg)
      throw new Error(errMsg)
    }
    // 1. 构建更新字段(处理数组/布尔/时间格式)
    const todoData: relationalStore.ValuesBucket = {
      'title': todoItem.title,
      'content': todoItem.content,
      'tag': JSON.stringify(todoItem.TAG), // 数组转JSON字符串
      'is_done': todoItem.isDone ? 1 : 0, // 布尔值转数字
      'status': todoItem.status,
      'start': todoItem.start.toISOString(), // 时间转ISO格式
      'deadline': todoItem.deadLine.toISOString() // 时间转ISO格式
    }
    // 2. 构建条件:通过ID定位
    const predicates = new relationalStore.RdbPredicates('TODOS')
    predicates.equalTo('ID', todoID)
    // 3. 执行更新
    await this.objectiveRDB.update(
      todoData,
      predicates,
      relationalStore.ConflictResolution.ON_CONFLICT_REPLACE
    ).then(async (rows: Number) => {
      hilog.info(0x0000, TAG, `Updated todo row count: ${rows}`)
    }).catch((err: BusinessError) => {
      hilog.error(0x0000, TAG, `Update todo failed, code is ${err.code}, message is ${err.message}`)
    })
  }

  // ========================================
  // 【新增】删除列表(匹配示例风格)
  // ========================================
  /**
   * The method of deleting a list information.
   * @param listID 列表主键ID
   */
  async deleteList(listID: number)
  {
    if (!this.objectiveRDB)
    {
      const errMsg = 'RDBStore未初始化,无法删除列表'
      hilog.error(0x0000, TAG, errMsg)
      throw new Error(errMsg)
    }
    // 1. 构建条件:通过ID定位
    const predicates = new relationalStore.RdbPredicates('LISTS')
    predicates.equalTo('ID', listID)
    // 2. 执行删除
    await this.objectiveRDB.delete(predicates)
      .then((rows: Number) => {
        hilog.info(0x0000, TAG, `Delete list row count: ${rows}`)
      }).catch((err: BusinessError) => {
        hilog.error(0x0000, TAG, `Delete list failed, code is ${err.code}, message is ${err.message}`)
      })
  }

  // ========================================
  // 【新增】删除待办(匹配示例风格)
  // ========================================
  /**
   * The method of deleting a todo information.
   * @param todoID 待办主键ID
   */
  async deleteTodo(todoID: number)
  {
    if (!this.objectiveRDB)
    {
      const errMsg = 'RDBStore未初始化,无法删除待办'
      hilog.error(0x0000, TAG, errMsg)
      throw new Error(errMsg)
    }
    // 1. 构建条件:通过ID定位
    const predicates = new relationalStore.RdbPredicates('TODOS')
    predicates.equalTo('ID', todoID)
    // 2. 执行删除
    await this.objectiveRDB.delete(predicates)
      .then((rows: Number) => {
        hilog.info(0x0000, TAG, `Delete todo row count: ${rows}`)
      }).catch((err: BusinessError) => {
        hilog.error(0x0000, TAG, `Delete todo failed, code is ${err.code}, message is ${err.message}`)
      })
  }
}

export default new RDBStoreUtil();

感觉写的比较冗余(shit mountain),也担心会有数据隐患,很怕到时候调试数据库出问题都不知道怎么处理,走一步看一步了,非常感谢。

参考就是Codelab里面的例子和官方文档。


更多关于HarmonyOS 鸿蒙Next中关系数据库使用的实战教程也可以访问 https://www.itying.com/category-93-b0.html

3 回复

学会了,多谢楼主分享

更多关于HarmonyOS 鸿蒙Next中关系数据库使用的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html


鸿蒙Next中关系数据库使用

基于关系型数据库(RDB)组件。该组件提供SQLite本地数据库管理能力,支持标准SQL语法进行数据操作。开发时需导入@ohos.data.relationalStore模块,通过getRdbStore()获取数据库实例。主要操作包括创建表、增删改查数据、事务处理等。数据存储于应用沙箱路径下,安全隔离。

你的代码结构清晰,已经覆盖了关系型数据库的基本操作。针对冗余和数据隐患的问题,可以从以下几个方面优化:

1. 使用ORM思想简化数据映射 可以封装一个通用的BaseEntity类来处理ValuesBucket的转换,避免每个表都重复编写字段映射代码。

abstract class BaseEntity<T> {
  abstract toValuesBucket(): relationalStore.ValuesBucket;
  static fromResultSet<T>(resultSet: relationalStore.ResultSet): T;
}

2. 统一SQL执行与错误处理 创建executeSql方法统一处理执行和日志记录:

private async executeSql(sql: string, params?: any[]): Promise<relationalStore.ResultSet> {
  return this.objectiveRDB?.querySql(sql, params).then((resultSet) => {
    hilog.info(0x0000, TAG, `Execute SQL success: ${sql}`);
    return resultSet;
  }).catch((err: BusinessError) => {
    hilog.error(0x0000, TAG, `Execute SQL failed: ${sql}, error: ${err.code} - ${err.message}`);
    throw err;
  });
}

3. 使用RdbPredicates构建查询条件 对于条件查询,建议统一使用RdbPredicates而非拼接SQL字符串:

const predicates = new relationalStore.RdbPredicates('TODOS');
predicates.equalTo('is_done', 0)
         .greaterThan('deadline', new Date().toISOString())
         .orderByAsc('deadline');

4. 事务处理关键操作 对关联操作使用事务保证数据一致性:

async deleteListWithTodos(listId: number) {
  await this.objectiveRDB?.transaction(async () => {
    await this.deleteList(listId);
    const predicates = new relationalStore.RdbPredicates('TODOS');
    predicates.equalTo('list_id', listId);
    await this.objectiveRDB.delete(predicates);
  });
}

5. 数据库升级管理 实现onUpgrade方法处理表结构变更:

const STORE_CONFIG: relationalStore.StoreConfig = {
  name: 'Objective.db',
  securityLevel: relationalStore.SecurityLevel.S1,
  version: 2, // 版本号
  onUpgrade: (currentVersion, targetVersion) => {
    // 执行表结构变更
  }
};

6. 类型安全增强 为查询结果添加更严格的类型校验:

interface TodoRecord extends TodoTmp {
  id: number;
  created_at: string;
}

const todo = await this.queryOne<TodoRecord>(predicates);

7. 连接池与生命周期管理EntryAbilityonCreate中初始化,在onDestroy中释放资源:

// EntryAbility.ets
onCreate() {
  RDBStoreUtil.createObjectiveRDB(this.context);
}

onDestroy() {
  RDBStoreUtil.close();
}

这些优化方向能提升代码的可维护性和数据安全性,同时保持HarmonyOS关系型数据库的最佳实践。

回到顶部