鸿蒙数据持久化方案之 relationalStore

915 阅读4分钟

前言

在应用开发的过程中,我们经常会碰到需要将网络请求的数据持久化到本地的需求。如果数据结构比较简单,我们可以采用键值的方式(比如 iOS 中的 Userdefault、或者鸿蒙中的用户首选项)去存储,但如果数据结构比较复杂,且数据牵连很多需要分表存储的话,那就不得不使用关系型数据库来持久化网络数据了。

在 iOS 中,我们一般会使用将 SQLite 封装一层的 FMDB 三方库去进行数据管理。而在鸿蒙中,我们可以直接使用官方提供的 relationalStore 库去进行数据管理。该库底层也是基于 SQLite 去实现的,所以它的角色类似于 FMDB 的角色。这就是鸿蒙系统的一大优点:很多实用的功能不用去 Github 找三方库,官方已经给你实现好了😁。

下面,让我们看看 relationalStore 是如何使用的吧。

连接数据库

在使用 relationalStore 之前,我们需要首先导入该框架:

import relationalStore from '@ohos.data.relationalStore';

接着我们需要调用 getRdbStore 接口来获得一个实例来操作数据库:

// 声明数据库初始化函数
initDBConfig(name: string, securityLevel: relationalStore.SecurityLevel, customDir: string, context: common.UIAbilityContext) {
  const STORE_CONFIG: relationalStore.StoreConfig = {
    name: name,
    securityLevel: securityLevel,
    customDir: customDir,
    encrypt: true
  };
  this.config = STORE_CONFIG
  this.context = context
}
// 调用
this.initDBConfig("test.db", relationalStore.SecurityLevel.S1, '', this.context)

// 获取 rdbStore 实例
getRdbStore(callback: AsyncCallback<relationalStore.RdbStore>) {
  relationalStore.getRdbStore(this.context, this.config, callback)
}

该接口接受三个参数:

  • context:当前的上下文。
  • config:StoreConfig 类型包含下面几个参数:
    • name:数据库的名字。
    • securityLevel:数据库的安全级别。
    • encrypt:是否加密,默认不加密。
    • dataGroupId:应用的ID,需要跟官方申请。
    • customDir:数据库存放的路径,不填写默认放在沙箱的 rdb 目录下。
    • autoCleanDirtyData:是否自动清理云端删除后同步到本地的数据,默认为 true。
  • callback:执行完的回调。

需要注意的是:目前的版本,relationalStore 是不支持传入自定义的加密密钥的。而且对于开启了加密的数据库,也不支持导出查看,只能在项目中通过接口进行内容查询。

Tips:在 Index 文件中,可以用下面的方式获得 context :

import common from '@ohos.app.ability.common';
private context = getContext(this) as common.UIAbilityContext;

获得数据库实例之后,我们就可以建表了。

建表

在使用 relationalStore 时,可以通过调用 executeSql 执行 SQL 语句来进行建表:

// 声明建表函数
createTable(sql: string, callback: AsyncCallback<void>) {
  this.getRdbStore((err: BusinessError, store: relationalStore.RdbStore) => {
    if (err) {
      callback(err)
      return
    }
    store.executeSql(sql, (err) => {
      callback(err)
    })
  })
}

// 调用建表函数
this.createTable('create table if not exists db_user (name TEXT)', (err) => {
  if (!err) {
    promptAction.showToast({message: "建表成功"})
  } else {
    promptAction.showToast({message: `建表失败:error message: ${err.message}, error code: ${err.code}`})
  }
})

首先,我们需要调用自己定义的 getRdbStore 函数来获得 RdbStore 的实例 - store,然后调用 store 的executeSql 函数去执行 SQL 语句。

在调用处,我们传入的字符串代表我们需要创建一个表名为 db_user 的表,表内包含一个列名为 name 的 TEXT 类型的字段。

在回调处理中,我们使用了 promptAction ,它可以进行 toast 提示。这样在调试的时候可以更加的直观。使用 promptAction 需要提前导入:

import promptAction from '@ohos.promptAction';

建完表,我们就可以往里面填充数据了。

INSERT - 插入

对于插入操作,我们需要调用 insert 函数,该函数接受三个参数:

  • table:表名。
  • values:待插入数据,类型为 ValuesBucket。
  • callback:回调。
// 声明插入函数
insert(tableName: string, entity: ValuesBucket, callback: AsyncCallback<number>) {
  this.getRdbStore((err: BusinessError, store: relationalStore.RdbStore) => {
    if (err) {
      callback(err, -1)
      return
    }
    store.insert(tableName, entity, callback)
  })
}

// 调用
const valueBucket: ValuesBucket = {
  'name': "rose",
};
this.insert("db_user", valueBucket, (err, rowId) => {
  if (!err) {
    promptAction.showToast({message: `插入成功, rowid = ${rowId}`})
  } else {
    promptAction.showToast({message: `插入失败:error message: ${err.message}, error code: ${err.code}`})
  }
})

在我们自定义声明的 insert 函数中,依然是先调用 getRdbStore 获取实例 - store,然后调用 store 的 insert 函数。

在调用处,我们需要实例化一个类型为 ValuesBucket 的 valueBucket 去表示我们需要插入的数据,然后在回调中处理结果。

ValuesBucket 支持以下三种赋值方式:

const valueBucket1: ValuesBucket = {
  'name': "rose"
};
const valueBucket2: ValuesBucket = {
  name: "rose"
};
const valueBucket3: ValuesBucket = {
  "name": "rose"
};

在实际开发中,我们都会自定义模型去代表我们的数据,所以,对于开发者来说最好是可以有一种方式将模型转为 ValuesBucket,示例代码如下:

convertToValuesBucket(entity: Object): ValuesBucket {
  const bucket: ValuesBucket = {};
  for (const element of Object.keys(entity)) {
    if (typeof Reflect.get(entity, element) === 'string' ||
      typeof Reflect.get(entity, element) === 'number' ||
      typeof Reflect.get(entity, element) === 'boolean' ||
    Array.isArray(Reflect.get(entity, element))) {
      bucket[element] = Reflect.get(entity, element) as ValueType
    } else {
      bucket[element] = null; // 其他情况处理为 null
    }
  }
  return bucket;
}

因为 ValuesBucket 只支持字符串、数字、布尔和数组四种类型,所以我们需要进行类型检查。插入代码修改如下:

// 定义模型
class User {
  name: string
  constructor(name: string) {
    this.name = name
  }
}

//声明
insert(tableName: string, entity: Object, callback: AsyncCallback<number>) {
  const valueBucket = this.convertToValuesBucket(entity)
  this.getRdbStore((err: BusinessError, store: relationalStore.RdbStore) => {
    if (err) {
      callback(err, -1)
      return
    }
    store.insert(tableName, valueBucket, callback)
  })
}

// 调用
const user = new User("rose")
this.insert("db_user", user, (err, rowId) => {
  if (!err) {
    promptAction.showToast({message: `插入成功, rowid = ${rowId}`})
  } else {
    promptAction.showToast({message: `插入失败:error message: ${err.message}, error code: ${err.code}`})
  }
})

DELETE - 删除

删除我们需要调用 RdbStore 的 delete 接口:

// 声明
delete(predicates: relationalStore.RdbPredicates, callback: AsyncCallback<number>) {
  this.getRdbStore((err: BusinessError, store: relationalStore.RdbStore) => {
    if (err) {
      callback(err, -1)
      return
    }
    store.delete(predicates, callback)
  })
}
// 调用

let predicates = new relationalStore.RdbPredicates("db_user");
predicates.equalTo("name", "rose");
this.delete(predicates, (err, rowId) => {
  if (!err) {
    promptAction.showToast({message: `删除成功, rowId = ${rowId}`})
  } else {
    promptAction.showToast({message: `删除失败:error message: ${err.message}, error code: ${err.code}`})
  }
})

对于删除符合某个条件的数据,我们可以使用系统提供的 equalTo 函数进行处理。它的效果相当于以下 SQL 语句:WHERE name = 'rose',官方封装了很多有用的函数来代替我们自己写 SQL 语句,大家可以自行去查看官方文档。

UPDATE - 修改

将数据库中 name 字段值为 rose 的所有行,值都改为 jack。代码如下:

// 声明
update(tableName: string, entity: Object, callback: AsyncCallback<number>) {
  const valueBucket = this.convertToValuesBucket(entity)
  let predicates = new relationalStore.RdbPredicates(tableName);
  predicates.equalTo("name", "rose");

  this.getRdbStore((err: BusinessError, store: relationalStore.RdbStore) => {
    if (err) {
      callback(err, -1)
      return
    }
    store.update(valueBucket, predicates, callback)
  })
}

// 调用
const user = new User("jack")
this.update("db_user", user, (err, rowId) => {
  if (!err) {
    promptAction.showToast({message: `更新成功, rowid = ${rowId}`})
  } else {
    promptAction.showToast({message: `更新失败:error message: ${err.message}, error code: ${err.code}`})
  }
})

QUERY - 查询

查询表中的所有数据,示例代码如下:

// 声明
entities(predicates: relationalStore.RdbPredicates, callback: AsyncCallback<Object[]>) {
  this.getRdbStore((err: BusinessError, store: relationalStore.RdbStore) => {
    let resultEntities: Object[] = []
    if (err) {
      callback(err, resultEntities)
      return
    }
    store.query(predicates, (err, resultSet) => {
      if (err) {
        callback(err, resultEntities)
        return
      }

      const row = resultSet.rowCount
      if (row <= 0) {
        callback(err, resultEntities)
        return
      }

      resultSet.goToFirstRow()
      do {
        if(resultSet != undefined) {
          const row = (resultSet as relationalStore.ResultSet).getRow();
          resultEntities.push(row)
        }
      } while (resultSet.goToNextRow());
      if(resultSet != undefined) {
        (resultSet as relationalStore.ResultSet).close();
      }
      callback(err, resultEntities)
    })
  })
}

allEntities(tableName: string, callback: AsyncCallback<Object []>) {
  let predicates = new relationalStore.RdbPredicates(tableName);
  this.entities(predicates, callback)
}

// 调用
this.allEntities("db_user", (err, results) => {
  if (!err) {
    promptAction.showToast({message: `查询成功, result = ${results}`})
  } else {
    promptAction.showToast({message: `查询失败:error message: ${err.message}, error code: ${err.code}`})
  }
})

总结

本篇文章讲解了如何连接数据库、创建表格、数据库的增删改查操作以及插入数据更友好的做法,希望本篇文章能给大家带来帮助。