在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): 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演示了仓颉接口的使用。后续继续将继续介绍多表、联表、联合组件等复杂操作,以及对象转换框架的实现。

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐