写点什么

【仓颉开发 HarmonyOS 系列】仓颉关系型数据库基础操作实战

作者:轻口味
  • 2025-11-05
    北京
  • 本文字数:6379 字

    阅读完需:约 21 分钟

【仓颉开发HarmonyOS系列】仓颉关系型数据库基础操作实战

在 HarmonyOS 中 ArkData 数据管理模块提供了用户首选项、键值型数据管理、关系型数据管理、分布式数据对象、跨应用数据管理和统一数据管理框架。其中关系型数据管理(RelationalStore)提供了关系型数据库的增删改查、加密、手动备份以及订阅通知能力;提供了向量数据库的存储、管理、向量数据检索以及向量数据相似度计算的能力。应用需要使用关系型数据库的分布式能力时,RelationalStore 部件会将同步请求发送给 DatamgrService 由其完成跨设备数据同步。ArkData 数据管理架构图如下:


在仓颉中也提供了对应的 relationalStore 模块实现关系型数据库各种操作,也提供了分布式相关能力,本文介绍仓颉中 relationalStore 关系型数据库相关的 API。

关系型数据库介绍

关系型数据库​以关系模型为基础,用二维表(行/列)​存储结构化数据,表与表通过主键/外键建立关联;采用 SQL 进行数据的定义、查询与更新,并以 ACID 事务(原子性、一致性、隔离性、持久性)​保障关键业务的数据一致性与可靠性;支持索引、约束、事务隔离级别等机制以提升查询与并发性能,典型产品包括 MySQL、PostgreSQL、Oracle、SQL Server、IBM Db2、SQLite,广泛应用于电商、金融、ERP/CRM 等对数据一致性与复杂查询要求较高的场景。在移动端关系型数据库一般采用 sqllite,HarmonyOS 关系型数据库基于 SQLite 组件提供了一套完整的对本地数据库进行管理的机制,对外提供了一系列的增、删、改、查等接口,也可以直接运行用户输入的 SQL 语句来满足复杂的场景需要。


仓颉侧支持的基本数据类型:Int64、Float64、String、二进制类型数据、Bool。为保证插入并读取数据成功,建议一条数据不要超过 2M。超出该大小,插入成功,读取失败。

仓颉核心对象

在仓颉 API 中使用关系型数据库需要导入包mport ohos.relational_store.*,仓颉 API 提供了管理关系数据库方法的接口 RdbStore,条件对象 RdbPredicates,以及查询结果对象 ResultSet。

RdbStore

可以通过 getRdbStore 方法获取 RdbStore 对象,函数原型如下:


public func getRdbStore(context: StageContext, config: StoreConfig): RdbStore
复制代码


context 是应用的上下文,StoreConfig 结构定义如下:


public struct StoreConfig {    public let name: String    public let securityLevel: SecurityLevel    public let encrypt: Bool    public let dataGroupId: String    public let customDir: String    public let autoCleanDirtyData: Bool    public init(name: String, securityLevel: SecurityLevel, encrypt!: Bool = false, dataGroupId!: String = "", customDir!: String = "", autoCleanDirtyData!: Bool = true)}
复制代码


参数说明如下:


  • name:数据库文件名

  • securityLevel:设置数据库安全级别,SecurityLevel 类型,从低到高支持 S1 到 S4

  • encrypt:指定数据库是否加密,默认不加密。true:加密,false:非加密。

  • dataGroupId:应用组 ID,需要向应用市场获取(此属性仅在 Stage 模型下可用。指定在此 dataGroupId 对应的沙箱路径下创建 RdbStore 实例,当此参数不填时,默认在本应用沙箱目录下创建 RdbStore 实例。)

  • customDir:数据库自定义路径,数据库路径大小限制为 128 字节,如果超过该大小会开库失败,返回错误。数据库将在如下的目录结构中被创建:context.databaseDir + "/rdb/" + customDir,其中 context.databaseDir 是应用沙箱对应的路径,"/rdb/"表示创建的是关系型数据库,customDir 表示自定义的路径。当此参数不填时,默认在本应用沙箱目录下创建 RdbStore 实例。

  • autoCleanDirtyData:指定是否自动清理云端删除后同步到本地的数据,true 表示自动清理,false 表示手动清理,默认自动清理。对于端云协同的数据库,当云端删除的数据同步到设备端时,可通过该参数设置设备端是否自动清理。手动清理可以通过 cleanDirtyData 接口清理。


创建完成 RdbStore 对象就可以执行数据库的增删改查了。

执行 sql 语句

RdbStore 提供了func executeSql(sql: String): Unitpublic func executeSql(sql: String, bindArgs: Array<ValueType>): Unit执行包含指定参数但不返回值的 SQL 语句。比如创建数据库表:


rdbStore.executeSql("CREATE TABLE User(ID int NOT NULL, NAME varchar(255) NOT NULL, AGE int, PRIMARY KEY (Id))")
复制代码


创建完数据库表可以继续介绍增删改查。

插入数据提供了如下方法:


//向目标表中插入一行数据。public func insert(table: String, values: Map<String, ValueType>): Int64//向目标表中插入一行数据。public func insert(table: String, values: Map<String, ValueType>, conflict: ConflictResolution): Int64//向目标表中插入一组数据。public func batchInsert(table: String, values: Array<Map<String, ValueType>>): Int64
复制代码


其中 ConflictResolution 指定冲突解决方式,比如 ON_CONFLICT_REPLACE,表示替换。

删除数据仓颉 API 提供了下面方法:


public func delete(predicates: RdbPredicates): Int64
复制代码


根据 RdbPredicates 的指定实例对象从数据库中删除数据,放回受影响的行数,删除条件通过构造谓词对象 RdbPredicates 来限定条件。

更新数据提供了下面两个 API:


//根据RdbPredicates的指定实例对象更新数据库中的数据。public func update(values: Map<String, ValueType>, predicates: RdbPredicates): Int64public func update(values: Map<String, ValueType>, predicates: RdbPredicates, conflict: ConflictResolution): Int64
复制代码


第二个方法增加了冲突解决方式。

查找仓颉提供了下面的两个方法:


//根据指定SQL语句查询数据库中的数据。public func query(predicates: RdbPredicates, columns: Array<String>): ResultSetpublic func querySql(sql: String, bindArgs!: Array<ValueType> = Array<ValueType>()): ResultSet
复制代码


查找参数是谓词对象 RdbPredicates,返回结果是 ResultSet。

事务

仓颉 API 提供了开始事务,提交事务,回滚事务的方法:


//1、在开始执行SQL语句之前,开始事务。本方法不支持在多进程或多线程中使用。public func beginTransaction(): Unit//2、执行sql语句//3、提交已执行的SQL语句。public func commit(): Unit//3、或者回滚已经执行的SQL语句。public func rollBack(): Unit
复制代码
备份与回滚

仓颉 API 提供了下面方法备份数据库:


public func backup(destName: String): Unit
复制代码


以及从指定的数据库备份文件恢复数据库。


public func restore(srcName: String): Unit
复制代码

RdbPredicates

RdbPredicates类作为构建数据库查询条件的关键类,允许我们定义各种查询条件和排序规则,从而精准地获取所需的数据。本文将详细介绍RdbPredicates类的用法,包括其构造函数及各种配置方法。RdbPredicates类表示关系型数据库(RDB)的谓词,用于确定 RDB 中条件表达式的值是true还是false。通过该类,我们可以配置各种查询条件,如等于、不等于、包含、排序等,以构建复杂的查询语句。需要注意的是,RdbPredicates类型不是多线程安全的。如果应用中存在多线程同时操作该类派生出的实例,务必加锁保护以确保数据的一致性和线程安全。


ResultSet

ResultSet 是处理数据库查询结果的重要对象,为开发者提供了丰富的方法来访问和操作查询返回的数据。ResultSet类提供了通过查询数据库生成的数据库结果集的访问方法。当用户调用关系型数据库查询接口后,返回的结果集合就是通过ResultSet对象来访问的。它提供了多种灵活的数据访问方式,使开发者能够方便地获取各项数据。


要使用ResultSet,首先需要通过查询操作获取该对象。以下是一个基本的示例:


let predicates = RdbPredicates("User")predicates.equalTo("AGE", ValueType.integer(18))let resultSet: ResultSet =  rdbStore.query(predicates, ["ID", "NAME", "AGE", "SALARY", "CODES"])
复制代码


在这个示例中,我们创建了一个查询条件(RdbPredicates),筛选年龄为 18 岁的员工,然后执行查询并获取包含指定列的结果集。ResultSet提供了多个属性,用于获取结果集的基本信息和状态:



这些属性帮助我们了解当前结果集的状态,从而进行相应的操作。


ResultSet类提供了丰富的方法,用于导航和获取结果集中的数据。以下是主要方法的详细说明:


根据列名或索引获取信息


  • getColumnIndex(columnName: String): Int32​根据指定的列名获取列索引。


    let id = resultSet.getLong(resultSet.getColumnIndex("ID"))    let name = resultSet.getString(resultSet.getColumnIndex("NAME"))    let age = resultSet.getLong(resultSet.getColumnIndex("AGE"))    let salary = resultSet.getDouble(resultSet.getColumnIndex("SALARY"))
复制代码


  • getColumnName(columnIndex: Int32): String​根据指定的列索引获取列名。


    let id = resultSet.getColumnName(0)    let name = resultSet.getColumnName(1)    let age = resultSet.getColumnName(2)
复制代码


ResultSet 还提供了导航结果集的方法:


  • goTo(offset: Int32): Bool​ 向前或向后转至结果集的指定行,相对于其当前位置偏移。

  • goToRow(position: Int32): Bool​ 转到结果集的指定行。

  • goToFirstRow(): Bool​ 转到结果集的第一行。

  • goToLastRow(): Bool​ 转到结果集的最后一行。

  • goToNextRow(): Bool​ 转到结果集的下一行。

  • goToPreviousRow(): Bool​ 转到结果集的上一行。


ResultSet 提供了获取当前行的数据


  • getBlob(columnIndex: Int32): Array<UInt8>​ 以字节数组的形式获取当前行中指定列的值。

  • ​getString(columnIndex: Int32): String ​以字符串形式获取当前行中指定列的值。

  • ​getLong(columnIndex: Int32): Int64​ 以 Long 形式获取当前行中指定列的值。

  • ​getDouble(columnIndex: Int32): Float64 ​ 以 double 形式获取当前行中指定列的值。

  • ​getAsset(columnIndex: Int32): Asset ​ 以 Asset 形式获取当前行中指定列的值。

  • getAssets(columnIndex: Int32): Array<Asset>​ 以 Assets 形式获取当前行中指定列的值。

  • getRow(): Map<String, ValueType>​​ 获取当前行的所有列及其值。

  • ​isColumnNull(columnIndex: Int32): Bool​ 检查当前行中指定列的值是否为 null。


ResultSet是仓颉语言中操作关系型数据库查询结果的核心类,提供了丰富的属性和方法,使得数据的获取和操作变得灵活且高效。通过掌握ResultSet的使用,开发者可以轻松地处理数据库查询结果,实现复杂的数据交互逻辑。在实际开发中,合理使用ResultSet的各种方法,结合错误码的处理,可以有效提升应用的稳定性和用户体验。希望本文对你在使用仓颉开发 HarmonyOS 应用中的关系型数据库操作有所帮助。

数据操作实战

了解了仓颉提供的关系型数据库接口能力后,在 HarmonyOS 仓颉工程中进行数据库基础的增删改查操作。


首先创建 HarmonyOS 仓颉工程:


在 main_ability.cj 中创建创建上下文变量:


public var globalAbilityContext: Option<AbilityContext> = Option<AbilityContext>.None
复制代码


在 onCreate 中建上下文赋值给 globalAbilityContext:


public override func onCreate(want: Want, launchParam: LaunchParam): Unit {      globalAbilityContext = Option<AbilityContext>.Some(this.context)}
复制代码


接下来封装一个数据库操作类 RdbStoreManager,在构造方法中完成 RdbStore 对象的创建:


private RdbStoreManager() {      rdbStore = getRdbStore(getStageContext(globalAbilityContext.getOrThrow()), StoreConfig("MyRdb.db", SecurityLevel.S1))  }
复制代码


Context 使用在 MainAbility 获取的,StoreConfig 使用最小构造,传入数据库名称和等级。


接下来调用 executeSql 创建一张用户表:


public func createUserTable(){      rdbStore.executeSql("CREATE TABLE USER(ID int NOT NULL, NAME varchar(255) NOT NULL, AGE int, PHONE int NOT NULL, PRIMARY KEY (Id))")  }
复制代码


创建完表后插入数据:


public func insert(){      var values = HashMap<String, ValueType>()      values.put("ID", ValueType.integer(1))      values.put("NAME", ValueType.string("Lisa"))      values.put("AGE", ValueType.integer(18))      values.put("PHONE", ValueType.integer(11113332201))      rdbStore.insert("USER", values)  }
复制代码


插入完成后从模拟器设备/data/app/el2/100/database/包名/entry/下可以看到刚创建的数据库:



将数据库文件导出后,使用 sqlite 工具打开,可以看到刚才插入的数据:


接下来可以修改本条数据,比如将年龄改为 19:


public func update(){      let predicates = RdbPredicates("USER")      predicates.equalTo("NAME", ValueType.string("Lisa"))        var values = HashMap<String, ValueType>()      values.put("NAME", ValueType.string("Lisa"))      values.put("AGE", ValueType.integer(19))      values.put("PHONE", ValueType.integer(11113332201))        rdbStore.update(values, predicates)  }
复制代码


接着查询 NAME 为 Lisa 的行数据并打印:


public func query(){      let predicates = RdbPredicates("USER")      predicates.equalTo("NAME", ValueType.string("Lisa"))      let columns = ["ID", "NAME", "AGE", "PHONE"]      let resultSet = rdbStore.query(predicates, columns)      resultSet.goToNextRow()      let id = resultSet.getLong(resultSet.getColumnIndex("ID"));      let name = resultSet.getString(resultSet.getColumnIndex("NAME"));      let age = resultSet.getLong(resultSet.getColumnIndex("AGE"));      let phone = resultSet.getDouble(resultSet.getColumnIndex("PHONE"));      LogUtil.i("RdbStoreManager", 'id:${id},name:${name},age:${age},phone:${phone}')  }
复制代码



成功查询到一行内容,并且 age 也已经被改为 19 了。


最后删除 NAME 为 Lisa 的行:


public func delete(){      let predicates = RdbPredicates("USER")      predicates.equalTo("NAME", ValueType.string("Lisa"))      rdbStore.delete(predicates)  }
复制代码


最后再查询已经没有该条记录了。


调试入口简单加了几个按钮,代码如下:


func build() {      Row {          Column {              Button('创建表').fontSize(20).height(50)              .onClick({                  event=>                  RdbStoreManager.getInstance().createUserTable()              })              .margin(top:20)              Button('插入数据').fontSize(20).height(50)              .onClick({                  event=>                  RdbStoreManager.getInstance().insert()              })              .margin(top:20)              Button('修改数据').fontSize(20).height(50)              .onClick({                  event=>                  RdbStoreManager.getInstance().update()              })              .margin(top:20)              Button('查询数据').fontSize(20).height(50)              .onClick({                  event=>                  RdbStoreManager.getInstance().query()              })              .margin(top:20)              Button('删除数据').fontSize(20).height(50)              .onClick({                  event=>                  RdbStoreManager.getInstance().delete()              })              .margin(top:20)          }.width(100.percent)      }.height(100.percent)  }
复制代码



总结

本文介绍了 HarmonyOS 关系型数据库能力,以及仓颉提供的三大核心接口:RdbStore、RdbPredicates、ResultSet,并通过简单的增删改查 Demo 演示了仓颉接口的使用。后续继续将继续介绍多表、联表、联合组件等复杂操作,以及对象转换框架的实现。


发布于: 54 分钟前阅读数: 6
用户头像

轻口味

关注

🏆2021年InfoQ写作平台-签约作者 🏆 2017-10-17 加入

Android、音视频、AI相关领域从业者。 欢迎加我微信wodekouwei拉您进InfoQ音视频沟通群 邮箱:qingkouwei@gmail.com

评论

发布
暂无评论
【仓颉开发HarmonyOS系列】仓颉关系型数据库基础操作实战_鸿蒙_轻口味_InfoQ写作社区