鸿蒙(HarmoneyOS),封装一个通用关系型数据库操作类

鸿蒙(HarmoneyOS),封装一个通用关系型数据库操作类

ts 复制代码
import { relationalStore } from '@kit.ArkData';
import { BusinessError } from '@kit.BasicServicesKit';
import { LogUtil } from '../util/LogUtil';
import { GlobalContext } from '../util/GlobalContext';

const TAG = 'RdbHelper';

/**
 * 数据库升级回调类型
 * @param oldVersion 当前旧版本号
 * @param store RdbStore实例,可直接执行升级SQL
 */
export type UpgradeCallback = (oldVersion: number, transaction: relationalStore.Transaction) => Promise<void>;

/**
 * 数据库首次创建回调类型
 * @param store RdbStore实例,用于执行建表SQL
 */
export type CreateCallback = (transaction: relationalStore.Transaction) => Promise<void>;

/**
 * 查询结果行类型,键为列名,值为对应列的值
 */
export type QueryRow = Record<string, relationalStore.ValueType>;

/**
 * 关系型数据库通用工具类
 *
 * 功能特性:
 * 1. 默认数据库名称为 XXXXXX.db
 * 2. 支持基于 user_version 的数据库版本升级
 * 3. 提供完整的增删改查接口
 * 4. 内置事务支持
 */
export class RdbHelper {
  private static readonly DEFAULT_DB_NAME = 'XXXXXX.db';
  private static instance: RdbHelper | null = null;
  private rdbStore: relationalStore.RdbStore | null = null;
  private dbName: string;
  private initialized: boolean = false;

  private constructor(dbName: string = RdbHelper.DEFAULT_DB_NAME) {
    this.dbName = dbName;
  }

  /**
   * 获取单例实例
   * @param dbName 可选,自定义数据库名称,仅在首次调用时生效
   */
  static getInstance(dbName?: string): RdbHelper {
    if (!RdbHelper.instance) {
      RdbHelper.instance = new RdbHelper(dbName ?? RdbHelper.DEFAULT_DB_NAME);
    }
    return RdbHelper.instance;
  }

  /**
   * 释放单例(通常用于测试场景或需要重新初始化时调用)
   */
  static releaseInstance(): void {
    if (RdbHelper.instance) {
      RdbHelper.instance.rdbStore = null;
      RdbHelper.instance.initialized = false;
    }
    RdbHelper.instance = null;
  }

  /**
   * 初始化数据库,自动处理创建及版本升级
   *
   * @param targetVersion 目标数据库版本号(大于0的整数)
   * @param onCreate 数据库首次创建时的回调,用于执行建表语句
   * @param onUpgrade 数据库升级回调,参数为当前旧版本号
   */
  async initDatabase(targetVersion: number, onCreate?: CreateCallback,
    onUpgrade?: UpgradeCallback): Promise<void> {
    if (this.initialized && this.rdbStore) {
      LogUtil.info(TAG, 'Database already initialized, skip.');
      return;
    }

    // 检查分词器支持
    let tokenType = relationalStore.Tokenizer.ICU_TOKENIZER;
    let tokenizerSupported = relationalStore.isTokenizerSupported(tokenType);
    if (!tokenizerSupported) {
      tokenType = relationalStore.Tokenizer.NONE_TOKENIZER;
      LogUtil.info(TAG, 'ICU_TOKENIZER is not supported on this platform, use NONE_TOKENIZER.');
    }

    const config: relationalStore.StoreConfig = {
      name: this.dbName,
      securityLevel: relationalStore.SecurityLevel.S3,
      encrypt: false,
      isReadOnly: false,
      tokenizer: tokenType
    };

    try {
      this.rdbStore = await relationalStore.getRdbStore(GlobalContext.getContext(), config);
      LogUtil.info(TAG, `Succeeded in getting RdbStore: ${this.dbName}`);
    } catch (err) {
      let error = err as BusinessError;
      LogUtil.error(TAG, `Failed to get RdbStore. Code:${error.code}, message:${error.message}`);
      throw new Error(`Failed to get RdbStore: ${error.message}`);
    }

    // 处理数据库版本
    await this.handleVersionUpgrade(targetVersion, onCreate, onUpgrade);
    this.initialized = true;
    LogUtil.info(TAG, `Database initialized successfully, version: ${targetVersion}`);
  }

  /**
   * 处理数据库版本升级逻辑
   */
  private async handleVersionUpgrade(targetVersion: number, onCreate?: CreateCallback,
    onUpgrade?: UpgradeCallback): Promise<void> {
    if (!this.rdbStore) {
      throw new Error('RdbStore is not initialized');
    }

    let transaction: relationalStore.Transaction | null = null;
    try {
      transaction = await this.rdbStore.createTransaction({});
      let currentVersion = await this.fetchStoreVersion(transaction);

      LogUtil.info(TAG, `Current DB version: ${currentVersion}, Target version: ${targetVersion}`);

      if (currentVersion === 0) {
        // 数据库首次创建
        if (onCreate) {
          await onCreate(transaction);
        }
        await transaction.execute(`PRAGMA user_version = ${targetVersion}`);
        LogUtil.info(TAG, `Database created, set version to ${targetVersion}.`);
      } else if (currentVersion < targetVersion) {
        // 需要逐级升级
        if (onUpgrade) {
          for (let ver = currentVersion; ver < targetVersion; ver++) {
            LogUtil.info(TAG, `Upgrading database from version ${ver} to ${ver + 1}.`);
            await onUpgrade(ver, transaction);
          }
        }
        await transaction.execute(`PRAGMA user_version = ${targetVersion}`);
        LogUtil.info(TAG, `Database upgraded to version ${targetVersion}.`);
      } else if (currentVersion > targetVersion) {
        LogUtil.warn(TAG,
          `Current version ${currentVersion} is higher than target ${targetVersion}, skip upgrade.`);
      }

      await transaction.commit();
    } catch (err) {
      let error = err as BusinessError;
      LogUtil.error(TAG, `Failed during version upgrade. Code:${error.code}, message:${error.message}`);
      if (transaction) {
        try {
          await transaction.rollback();
        } catch (rollbackErr) {
          LogUtil.error(TAG, `Rollback failed: ${(rollbackErr as BusinessError).message}`);
        }
      }
      throw new Error(`Database upgrade failed: ${error.message}`);
    }
  }

  /**
   * 通过事务查询当前数据库版本号
   */
  private async fetchStoreVersion(transaction: relationalStore.Transaction): Promise<number> {
    let storeVersion = await transaction.execute('PRAGMA user_version');
    return storeVersion as number;
  }

  // ==================== 内部校验 ====================

  /**
   * 确保数据库已初始化,返回 RdbStore 实例
   */
  private ensureInitialized(): relationalStore.RdbStore {
    if (!this.rdbStore) {
      throw new Error('Database not initialized. Please call initDatabase() first.');
    }
    return this.rdbStore;
  }

  // ==================== CRUD 操作 ====================

  /**
   * 插入单条数据
   *
   * @param table 表名
   * @param values 待插入的键值对数据
   * @returns 插入行的 rowId,失败时返回 -1
   */
  async insert(table: string, values: relationalStore.ValuesBucket): Promise<number> {
    let store = this.ensureInitialized();
    try {
      let rowId = await store.insert(table, values);
      LogUtil.info(TAG, `Insert into '${table}' success, rowId: ${rowId}`);
      return rowId;
    } catch (err) {
      let error = err as BusinessError;
      LogUtil.error(TAG, `Insert into '${table}' failed. Code:${error.code}, message:${error.message}`);
      throw new Error(`Insert failed: ${error.message}`);
    }
  }

  /**
   * 批量插入数据
   *
   * @param table 表名
   * @param valuesList 待插入的键值对数据列表
   * @returns 实际插入的行数
   */
  async batchInsert(table: string, valuesList: relationalStore.ValuesBucket[]): Promise<number> {
    let store = this.ensureInitialized();
    try {
      let count = await store.batchInsert(table, valuesList);
      LogUtil.info(TAG, `Batch insert into '${table}' success, count: ${count}`);
      return count;
    } catch (err) {
      let error = err as BusinessError;
      LogUtil.error(TAG, `Batch insert into '${table}' failed. Code:${error.code}, message:${error.message}`);
      throw new Error(`Batch insert failed: ${error.message}`);
    }
  }



  /**
   * 根据条件删除数据
   *
   * @param predicates 删除条件(需指定表名)
   * @returns 删除的行数
   */
  async delete(predicates: relationalStore.RdbPredicates): Promise<number> {
    let store = this.ensureInitialized();
    try {
      let count = await store.delete(predicates);
      LogUtil.info(TAG, `Delete success, count: ${count}`);
      return count;
    } catch (err) {
      let error = err as BusinessError;
      LogUtil.error(TAG, `Delete failed. Code:${error.code}, message:${error.message}`);
      throw new Error(`Delete failed: ${error.message}`);
    }
  }

  /**
   * 根据条件更新数据
   *
   * @param values 待更新的键值对数据
   * @param predicates 更新条件(需指定表名)
   * @returns 更新的行数
   */
  async update(values: relationalStore.ValuesBucket, predicates: relationalStore.RdbPredicates): Promise<number> {
    let store = this.ensureInitialized();
    try {
      let count = await store.update(values, predicates);
      LogUtil.info(TAG, `Update success, count: ${count}`);
      return count;
    } catch (err) {
      let error = err as BusinessError;
      LogUtil.error(TAG, `Update failed. Code:${error.code}, message:${error.message}`);
      throw new Error(`Update failed: ${error.message}`);
    }
  }

  /**
   * 查询多条数据
   *
   * @param predicates 查询条件(需指定表名)
   * @param columns 要查询的列名数组,不传则查询所有列
   * @returns 查询结果列表
   */
  async query(predicates: relationalStore.RdbPredicates, columns?: string[]): Promise<QueryRow[]> {
    let store = this.ensureInitialized();
    let resultSet: relationalStore.ResultSet | null = null;
    try {
      resultSet = await store.query(predicates, columns);
      let results: QueryRow[] = [];
      let columnNames = resultSet.columnNames;
      while (resultSet.goToNextRow()) {
        let row: QueryRow = {};
        for (let col of columnNames) {
          let colIdx = resultSet.getColumnIndex(col);
          row[col] = await this.getValueFromResultSet(resultSet, colIdx);
        }
        results.push(row);
      }
      return results;
    } catch (err) {
      let error = err as BusinessError;
      LogUtil.error(TAG, `Query failed. Code:${error.code}, message:${error.message}`);
      throw new Error(`Query failed: ${error.message}`);
    } finally {
      if (resultSet) {
        resultSet.close();
      }
    }
  }

  /**
   * 查询单条数据
   *
   * @param predicates 查询条件(需指定表名)
   * @param columns 要查询的列名数组,不传则查询所有列
   * @returns 查询结果行,未找到返回 null
   */
  async queryOne(predicates: relationalStore.RdbPredicates, columns?: string[]): Promise<QueryRow | null> {
    let store = this.ensureInitialized();
    let resultSet: relationalStore.ResultSet | null = null;
    try {
      resultSet = await store.query(predicates, columns);
      if (resultSet.goToFirstRow()) {
        let row: QueryRow = {};
        let columnNames = resultSet.columnNames;
        for (let col of columnNames) {
          let colIdx = resultSet.getColumnIndex(col);
          row[col] = await this.getValueFromResultSet(resultSet, colIdx);
        }
        return row;
      }
      return null;
    } catch (err) {
      let error = err as BusinessError;
      LogUtil.error(TAG, `QueryOne failed. Code:${error.code}, message:${error.message}`);
      throw new Error(`QueryOne failed: ${error.message}`);
    } finally {
      if (resultSet) {
        resultSet.close();
      }
    }
  }

  /**
   * 查询记录总数
   *
   * @param predicates 查询条件(需指定表名)
   * @returns 匹配的记录数
   */
  async count(predicates: relationalStore.RdbPredicates): Promise<number> {
    let store = this.ensureInitialized();
    let resultSet: relationalStore.ResultSet | null = null;
    try {
      resultSet = await store.query(predicates, ['COUNT(*) AS cnt']);
      if (resultSet.goToFirstRow()) {
        let colIdx = resultSet.getColumnIndex('cnt');
        return resultSet.getLong(colIdx);
      }
      return 0;
    } catch (err) {
      let error = err as BusinessError;
      LogUtil.error(TAG, `Count failed. Code:${error.code}, message:${error.message}`);
      throw new Error(`Count failed: ${error.message}`);
    } finally {
      if (resultSet) {
        resultSet.close();
      }
    }
  }

  /**
   * 执行非查询 SQL 语句(INSERT / UPDATE / DELETE / DDL 等)
   *
   * @param sql SQL 语句
   * @param bindArgs 绑定参数(与 ? 占位符一一对应)
   */
  async executeSql(sql: string, bindArgs?: relationalStore.ValueType[]): Promise<void> {
    let store = this.ensureInitialized();
    try {
      await store.executeSql(sql, bindArgs);
      LogUtil.info(TAG, 'Execute SQL success.');
    } catch (err) {
      let error = err as BusinessError;
      LogUtil.error(TAG, `Execute SQL failed. Code:${error.code}, message:${error.message}`);
      throw new Error(`Execute SQL failed: ${error.message}`);
    }
  }

  /**
   * 执行查询 SQL 语句
   *
   * @param sql 查询 SQL 语句
   * @param bindArgs 绑定参数(与 ? 占位符一一对应)
   * @returns 查询结果列表
   */
  async querySql(sql: string, bindArgs?: relationalStore.ValueType[]): Promise<QueryRow[]> {
    let store = this.ensureInitialized();
    let resultSet: relationalStore.ResultSet | null = null;
    try {
      resultSet = await store.querySql(sql, bindArgs);
      let results: QueryRow[] = [];
      let columnNames = resultSet.columnNames;
      while (resultSet.goToNextRow()) {
        let row: QueryRow = {};
        for (let col of columnNames) {
          let colIdx = resultSet.getColumnIndex(col);
          row[col] = await this.getValueFromResultSet(resultSet, colIdx);
        }
        results.push(row);
      }
      return results;
    } catch (err) {
      let error = err as BusinessError;
      LogUtil.error(TAG, `Query SQL failed. Code:${error.code}, message:${error.message}`);
      throw new Error(`Query SQL failed: ${error.message}`);
    } finally {
      if (resultSet) {
        resultSet.close();
      }
    }
  }

  /**
   * 从 ResultSet 中根据列类型安全地提取值
   */
  private async getValueFromResultSet(resultSet: relationalStore.ResultSet, columnIndex: number): Promise<relationalStore.ValueType> {
    if (resultSet.isColumnNull(columnIndex)) {
      return '';
    }
    let columnType = await resultSet.getColumnType(columnIndex);
    switch (columnType) {
      case relationalStore.ColumnType.INTEGER:
        return resultSet.getLong(columnIndex);
      case relationalStore.ColumnType.FLOAT_VECTOR:
        return resultSet.getDouble(columnIndex);
      case relationalStore.ColumnType.TEXT:
        return resultSet.getString(columnIndex);
      case relationalStore.ColumnType.BLOB:
        return resultSet.getBlob(columnIndex);
      default:
        return resultSet.getString(columnIndex);
    }
  }

  // ==================== 事务操作 ====================

  /**
   * 在事务中执行操作,自动 commit / rollback
   *
   * @param action 事务内要执行的操作,回调参数为当前 RdbStore 实例
   *
   * 使用示例:
   * ```
   * await helper.runInTransaction(async (store) => {
   *   await store.executeSql('INSERT INTO ...');
   *   await store.executeSql('UPDATE ...');
   * });
   * ```
   */
  async runInTransaction(action: (store: relationalStore.RdbStore) => Promise<void>): Promise<void> {
    let store = this.ensureInitialized();
    let transaction: relationalStore.Transaction | null = null;
    try {
      transaction = await store.createTransaction({});
      await action(store);
      await transaction.commit();
      LogUtil.info(TAG, 'Transaction committed successfully.');
    } catch (err) {
      let error = err as BusinessError;
      LogUtil.error(TAG, `Transaction failed. Code:${error.code}, message:${error.message}`);
      if (transaction) {
        try {
          await transaction.rollback();
          LogUtil.info(TAG, 'Transaction rolled back.');
        } catch (rollbackErr) {
          LogUtil.error(TAG, `Rollback failed: ${(rollbackErr as BusinessError).message}`);
        }
      }
      throw new Error(`Transaction failed: ${error.message}`);
    }
  }

  // ==================== 工具方法 ====================

  /**
   * 获取原始 RdbStore 实例,用于直接调用原生 API
   */
  getRawStore(): relationalStore.RdbStore | null {
    return this.rdbStore;
  }

  /**
   * 获取当前数据库名称
   */
  getDatabaseName(): string {
    return this.dbName;
  }

  /**
   * 检查数据库是否已初始化
   */
  isInitialized(): boolean {
    return this.initialized && this.rdbStore !== null;
  }

  /**
   * 关闭数据库(清除实例引用)
   */
  async close(): Promise<void> {
    this.rdbStore = null;
    this.initialized = false;
    LogUtil.info(TAG, 'Database store reference cleared.');
  }

  /**
   * 删除数据库文件
   * 注意:调用前请确保没有正在进行的数据库操作
   */
  async deleteDatabase(): Promise<void> {
    await this.close();
    try {
      await relationalStore.deleteRdbStore(GlobalContext.getContext(), this.dbName);
      LogUtil.info(TAG, `Database '${this.dbName}' deleted.`);
    } catch (err) {
      let error = err as BusinessError;
      LogUtil.error(TAG, `Failed to delete database. Code:${error.code}, message:${error.message}`);
      throw new Error(`Delete database failed: ${error.message}`);
    }
  }
}

使用示例

ts 复制代码
const TAG = 'AppDBController';

export class AppDBController {
  private static readonly DB_VERSION = 1;
  private static instance: AppDBController;
  private helper: RdbHelper;

  private constructor() {
    this.helper = RdbHelper.getInstance();
  }

  public static getInstance(): AppDBController {
    if (!AppDBController.instance) {
      AppDBController.instance = new AppDBController();
    }
    return AppDBController.instance;
  }

  /**
   * 初始化数据库(首次创建建全部表,后续按版本升级)
   */
  async init(): Promise<void> {
    await this.helper.initDatabase(
      AppDBController.DB_VERSION,
      // onCreate:首次创建时建表
        LogUtil.info(TAG, `Table ${AppRecord.TABLE_NAME} created.`);
      },
      // onUpgrade:逐级升级
      async (oldVersion: number, transaction: relationalStore.Transaction) => {
        // v2 -> v3:新增 XXX
        /*if (oldVersion === 2) {
          await transaction.execute(``);
        }*/
      }
    );
  }
}
相关推荐
先吃饱再说12 小时前
存储的进化:从 MySQL 到浏览器缓存,数据到底住在哪?
数据库
Nturmoils12 小时前
字段太多看不全,ksql 的展开模式和输出控制怎么用
数据库·后端
网易云信13 小时前
全框架覆盖!网易智企IM鸿蒙生态适配再进一步
人工智能·aigc·harmonyos
Databend15 小时前
Agent 轨迹分析与归因的数据工程实践
大数据·数据库·agent
这个DBA有点耶15 小时前
SQL改写进阶:标量子查询的“隐形代价”与消除实战
数据库·mysql·架构
smallyoung16 小时前
数据库乐观锁深度解析:MySQL、PostgreSQL 实战 + Spring Boot 集成指南
数据库·mysql·postgresql
parade岁月16 小时前
MySQL JOIN解析:朴实无华但食之有味
数据库·后端
用户31693538118317 小时前
MySQL服务无法启动问题解决全记录
数据库
vivo互联网技术20 小时前
从 10 分钟到 1 秒:ES 深度分页任意跳页的三轮优化实战
服务器·数据库·redis·elasticsearch·深度分页
TrisighT21 小时前
我用 AI 逆向了 ArkTS @Builder 的编译产物,看完再也不敢乱写嵌套了
ai编程·harmonyos·arkts