动态

详情 返回 返回

【倉頡開發HarmonyOS系列】倉頡關係型數據庫基礎操作實戰 - 动态 详情

在HarmonyOS中ArkData數據管理模塊提供了用户首選項、鍵值型數據管理、關係型數據管理、分佈式數據對象、跨應用數據管理和統一數據管理框架。其中關係型數據管理(RelationalStore)提供了關係型數據庫的增刪改查、加密、手動備份以及訂閲通知能力;提供了向量數據庫的存儲、管理、向量數據檢索以及向量數據相似度計算的能力。應用需要使用關係型數據庫的分佈式能力時,RelationalStore部件會將同步請求發送給DatamgrService由其完成跨設備數據同步。ArkData數據管理架構圖如下:

image.png
在倉頡中也提供了對應的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<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 倉頡工程:
image.png

在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/下可以看到剛創建的數據庫:
image.png
image.png
將數據庫文件導出後,使用sqlite工具打開,可以看到剛才插入的數據:
image.png

接下來可以修改本條數據,比如將年齡改為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)  
}

image.png

總結

本文介紹了HarmonyOS關係型數據庫能力,以及倉頡提供的三大核心接口:RdbStore、RdbPredicates、ResultSet,並通過簡單的增刪改查Demo演示了倉頡接口的使用。後續繼續將繼續介紹多表、聯表、聯合組件等複雜操作,以及對象轉換框架的實現。

user avatar hlinleanring 头像 Yzi321 头像 starrocks 头像 q_bit 头像 mingtiaoiv 头像 qianzhou 头像
点赞 6 用户, 点赞了这篇动态!
点赞

Add a new 评论

Some HTML is okay.