【仓颉开发HarmonyOS系列】仓颉关系型数据库基础操作实战
摘要:HarmonyOS的ArkData数据管理模块提供多种数据管理能力,包括关系型数据库操作。仓颉API中的relationalStore模块实现了关系型数据库的增删改查、事务、备份恢复等功能,支持分布式数据同步。RdbStore对象用于执行SQL语句和管理数据,RdbPredicates用于构建查询条件,ResultSet处理查询结果。关系型数据库基于SQLite,支持ACID事务,适合移动端
在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): Unit和public 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): Int64
public func update(values: Map<String, ValueType>, predicates: RdbPredicates, conflict: ConflictResolution): Int64
第二个方法增加了冲突解决方式。
查
查找仓颉提供了下面的两个方法:
//根据指定SQL语句查询数据库中的数据。
public func query(predicates: RdbPredicates, columns: Array<String>): ResultSet
public 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类型不是多线程安全的。如果应用中存在多线程同时操作该类派生出的实例,务必加锁保护以确保数据的一致性和线程安全。
| 方法签名 | 函数介绍 |
|---|---|
init(name: String) |
构造函数,用于创建一个针对指定数据库表的RdbPredicates实例。 |
inAllDevices(): RdbPredicates |
同步分布式数据库时连接到组网内所有的远程设备。 |
equalTo(field: String, value: ValueType): RdbPredicates |
配置谓词以匹配数据表的指定列中值为给定值的字段。 |
notEqualTo(field: String, value: ValueType): RdbPredicates |
配置谓词以匹配数据表的指定列中值不为给定值的字段。 |
beginWrap(): RdbPredicates |
向谓词添加左括号。 |
endWrap(): RdbPredicates |
向谓词添加右括号。 |
or(): RdbPredicates |
将或条件添加到谓词中。 |
and(): RdbPredicates |
向谓词添加和条件。 |
contains(field: String, value: String): RdbPredicates |
配置谓词以匹配数据表的指定列中包含给定子字符串的字段。 |
beginsWith(field: String, value: String): RdbPredicates |
配置谓词以匹配数据表的指定列中以给定前缀开头的字段。 |
endsWith(field: String, value: String): RdbPredicates |
配置谓词以匹配数据表的指定列中以给定后缀结尾的字段。 |
isNull(field: String): RdbPredicates |
配置谓词以匹配数据表的指定列中值为null的字段。 |
isNotNull(field: String): RdbPredicates |
配置谓词以匹配数据表的指定列中值不为null的字段。 |
like(field: String, value: String): RdbPredicates |
配置谓词以匹配数据表的指定列中值类似于给定模式的字段。 |
glob(field: String, value: String): RdbPredicates |
配置谓词以匹配数据表的指定列中值符合给定模式的字段,支持通配符。 |
between(field: String, lowValue: ValueType, highValue: ValueType): RdbPredicates |
配置谓词以匹配数据表的指定列中值在给定范围内(包含边界)的字段。 |
notBetween(field: String, lowValue: ValueType, highValue: ValueType): RdbPredicates |
配置谓词以匹配数据表的指定列中值超出给定范围(不包含边界)的字段。 |
greaterThan(field: String, value: ValueType): RdbPredicates |
配置谓词以匹配数据表的指定列中值大于给定值的字段。 |
lessThan(field: String, value: ValueType): RdbPredicates |
配置谓词以匹配数据表的指定列中值小于给定值的字段。 |
greaterThanOrEqualTo(field: String, value: ValueType): RdbPredicates |
配置谓词以匹配数据表的指定列中值大于或等于给定值的字段。 |
lessThanOrEqualTo(field: String, value: ValueType): RdbPredicates |
配置谓词以匹配数据表的指定列中值小于或等于给定值的字段。 |
orderByAsc(field: String): RdbPredicates |
配置谓词以按指定列的值升序排序。 |
orderByDesc(field: String): RdbPredicates |
配置谓词以按指定列的值降序排序。 |
distinct(): RdbPredicates |
配置谓词以过滤重复记录,仅保留唯一的记录。 |
limitAs(value: Int32): RdbPredicates |
设置查询结果的最大记录数。 |
offsetAs(rowOffset: Int32): RdbPredicates |
配置谓词以指定返回结果的起始位置,通常与limitAs方法一起使用以实现分页查询。 |
groupBy(fields: Array<String>): RdbPredicates |
配置谓词以按指定列对查询结果进行分组。 |
in(field: String, values: Array<ValueType>): RdbPredicates |
配置谓词以匹配数据表的指定列中值在给定值集合中的字段。 |
notIn(field: String, values: Array<ValueType>): 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提供了多个属性,用于获取结果集的基本信息和状态:
| 名称 | 类型 | 必填 | 说明 |
|---|---|---|---|
| columnNames | Array | 是 | 获取结果集中所有列的名称。 |
| columnCount | Int32 | 是 | 获取结果集中的列数。 |
| rowCount | Int32 | 是 | 获取结果集中的行数。 |
| rowIndex | Int32 | 是 | 获取结果集当前行的索引。 |
| isAtFirstRow | Bool | 是 | 检查结果集是否位于第一行。 |
| isAtLastRow | Bool | 是 | 检查结果集是否位于最后一行。 |
| isEnded | Bool | 是 | 检查结果集是否位于最后一行之后。 |
| isStarted | Bool | 是 | 检查指针是否移动过。 |
| isClosed | Bool | 是 | 检查当前结果集是否关闭。 |
这些属性帮助我们了解当前结果集的状态,从而进行相应的操作。
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}')
}
![[【仓颉开发HarmonyOS系列】仓颉关系型数据库基础操作实战-5.png]]
成功查询到一行内容,并且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演示了仓颉接口的使用。后续继续将继续介绍多表、联表、联合组件等复杂操作,以及对象转换框架的实现。
更多推荐



所有评论(0)