在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<string> | 是 | 獲取結果集中所有列的名稱。 |
| 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演示了倉頡接口的使用。後續繼續將繼續介紹多表、聯表、聯合組件等複雜操作,以及對象轉換框架的實現。