場景介紹
關(guān)系型數(shù)據(jù)庫基于SQLite組件,適用于存儲包含復(fù)雜關(guān)系數(shù)據(jù)的場景,比如一個班級的學(xué)生信息,需要包括姓名、學(xué)號、各科成績等,又或者公司的雇員信息,需要包括姓名、工號、職位等,由于數(shù)據(jù)之間有較強(qiáng)的對應(yīng)關(guān)系,復(fù)雜程度比鍵值型數(shù)據(jù)更高,此時需要使用關(guān)系型數(shù)據(jù)庫來持久化保存數(shù)據(jù)。
基本概念
謂詞:數(shù)據(jù)庫中用來代表數(shù)據(jù)實(shí)體的性質(zhì)、特征或者數(shù)據(jù)實(shí)體之間關(guān)系的詞項,主要用來定義數(shù)據(jù)庫的操作條件。
結(jié)果集:指用戶查詢之后的結(jié)果集合,可以對數(shù)據(jù)進(jìn)行訪問。結(jié)果集提供了靈活的數(shù)據(jù)訪問方式,可以更方便地拿到用戶想要的數(shù)據(jù)。
運(yùn)作機(jī)制
關(guān)系型數(shù)據(jù)庫對應(yīng)用提供通用的操作接口,底層使用SQLite作為持久化存儲引擎,支持SQLite具有的數(shù)據(jù)庫特性,包括但不限于事務(wù)、索引、視圖、觸發(fā)器、外鍵、參數(shù)化查詢和預(yù)編譯SQL語句。
圖1 關(guān)系型數(shù)據(jù)庫運(yùn)作機(jī)制

約束限制
系統(tǒng)默認(rèn)日志方式是WAL(Write Ahead Log)模式,系統(tǒng)默認(rèn)落盤方式是FULL模式。
數(shù)據(jù)庫中有4個讀連接和1個寫連接,線程獲取到空閑讀連接時,即可進(jìn)行讀取操作。當(dāng)沒有空閑讀連接且有空閑寫連接時,會將寫連接當(dāng)做讀連接來使用。
為保證數(shù)據(jù)的準(zhǔn)確性,數(shù)據(jù)庫同一時間只能支持一個寫操作。
當(dāng)應(yīng)用被卸載完成后,設(shè)備上的相關(guān)數(shù)據(jù)庫文件及臨時文件會被自動清除。
ArkTS側(cè)支持的基本數(shù)據(jù)類型:number、string、二進(jìn)制類型數(shù)據(jù)、boolean。
為保證插入并讀取數(shù)據(jù)成功,建議一條數(shù)據(jù)不要超過2M。超出該大小,插入成功,讀取失敗。
接口說明
以下是關(guān)系型數(shù)據(jù)庫持久化功能的相關(guān)接口,大部分為異步接口。異步接口均有callback和Promise兩種返回形式,下表均以callback形式為例。
| 接口名稱 | 描述 |
|---|---|
| getRdbStore(context: Context, config: StoreConfig, callback: AsyncCallback<RdbStore>): void | 獲得一個相關(guān)的RdbStore,操作關(guān)系型數(shù)據(jù)庫,用戶可以根據(jù)自己的需求配置RdbStore的參數(shù),然后通過RdbStore調(diào)用相關(guān)接口可以執(zhí)行相關(guān)的數(shù)據(jù)操作。 |
| executeSql(sql: string, bindArgs: Array<ValueType>, callback: AsyncCallback<void>):void | 執(zhí)行包含指定參數(shù)但不返回值的SQL語句。 |
| insert(table: string, values: ValuesBucket, callback: AsyncCallback<number>):void | 向目標(biāo)表中插入一行數(shù)據(jù)。 |
| update(values: ValuesBucket, predicates: RdbPredicates, callback: AsyncCallback<number>):void | 根據(jù)RdbPredicates的指定實(shí)例對象更新數(shù)據(jù)庫中的數(shù)據(jù)。 |
| delete(predicates: RdbPredicates, callback: AsyncCallback<number>):void | 根據(jù)RdbPredicates的指定實(shí)例對象從數(shù)據(jù)庫中刪除數(shù)據(jù)。 |
| query(predicates: RdbPredicates, columns: Array<string>, callback: AsyncCallback<ResultSet>):void | 根據(jù)指定條件查詢數(shù)據(jù)庫中的數(shù)據(jù)。 |
| deleteRdbStore(context: Context, name: string, callback: AsyncCallback<void>): void | 刪除數(shù)據(jù)庫。 |
開發(fā)步驟
因Stage模型、FA模型的差異,個別示例代碼提供了在兩種模型下的對應(yīng)示例;示例代碼未區(qū)分模型或沒有對應(yīng)注釋說明時默認(rèn)在兩種模型下均適用。
-
使用關(guān)系型數(shù)據(jù)庫實(shí)現(xiàn)數(shù)據(jù)持久化,需要獲取一個RdbStore,其中包括建庫、建表、升降級等操作。示例代碼如下所示:
Stage模型示例:
import relationalStore from '@ohos.data.relationalStore'; // 導(dǎo)入模塊 import UIAbility from '@ohos.app.ability.UIAbility'; import { BusinessError } from '@ohos.base'; import window from '@ohos.window'; // 此處示例在Ability中實(shí)現(xiàn),使用者也可以在其他合理場景中使用 class EntryAbility extends UIAbility { onWindowStageCreate(windowStage: window.WindowStage) { const STORE_CONFIG :relationalStore.StoreConfig= { name: 'RdbTest.db', // 數(shù)據(jù)庫文件名 securityLevel: relationalStore.SecurityLevel.S1, // 數(shù)據(jù)庫安全級別 encrypt: false, // 可選參數(shù),指定數(shù)據(jù)庫是否加密,默認(rèn)不加密 dataGroupId: 'dataGroupID', // 可選參數(shù),僅可在Stage模型下使用,表示為應(yīng)用組ID,需要向應(yīng)用市場獲取。指定在此Id對應(yīng)的沙箱路徑下創(chuàng)建實(shí)例,當(dāng)此參數(shù)不填時,默認(rèn)在本應(yīng)用沙箱目錄下創(chuàng)建。 customDir: 'customDir/subCustomDir' // 可選參數(shù),數(shù)據(jù)庫自定義路徑。數(shù)據(jù)庫將在如下的目錄結(jié)構(gòu)中被創(chuàng)建:context.databaseDir + '/rdb/' + customDir,其中context.databaseDir是應(yīng)用沙箱對應(yīng)的路徑,'/rdb/'表示創(chuàng)建的是關(guān)系型數(shù)據(jù)庫,customDir表示自定義的路徑。當(dāng)此參數(shù)不填時,默認(rèn)在本應(yīng)用沙箱目錄下創(chuàng)建RdbStore實(shí)例。 }; // 判斷數(shù)據(jù)庫版本,如果不匹配則需進(jìn)行升降級操作 // 假設(shè)當(dāng)前數(shù)據(jù)庫版本為3,表結(jié)構(gòu):EMPLOYEE (NAME, AGE, SALARY, CODES) const SQL_CREATE_TABLE = 'CREATE TABLE IF NOT EXISTS EMPLOYEE (ID INTEGER PRIMARY KEY AUTOINCREMENT, NAME TEXT NOT NULL, AGE INTEGER, SALARY REAL, CODES BLOB)'; // 建表Sql語句 relationalStore.getRdbStore(this.context, STORE_CONFIG, (err, store) => { if (err) { console.error(`Failed to get RdbStore. Code:${err.code}, message:${err.message}`); return; } console.info('Succeeded in getting RdbStore.'); // 當(dāng)數(shù)據(jù)庫創(chuàng)建時,數(shù)據(jù)庫默認(rèn)版本為0 if (store.version === 0) { store.executeSql(SQL_CREATE_TABLE); // 創(chuàng)建數(shù)據(jù)表 // 設(shè)置數(shù)據(jù)庫的版本,入?yún)榇笥?的整數(shù) store.version = 3; } // 如果數(shù)據(jù)庫版本不為0且和當(dāng)前數(shù)據(jù)庫版本不匹配,需要進(jìn)行升降級操作 // 當(dāng)數(shù)據(jù)庫存在并假定版本為1時,例應(yīng)用從某一版本升級到當(dāng)前版本,數(shù)據(jù)庫需要從1版本升級到2版本 if (store.version === 1) { // version = 1:表結(jié)構(gòu):EMPLOYEE (NAME, SALARY, CODES, ADDRESS) => version = 2:表結(jié)構(gòu):EMPLOYEE (NAME, AGE, SALARY, CODES, ADDRESS) if (store !== undefined) { (store as relationalStore.RdbStore).executeSql('ALTER TABLE EMPLOYEE ADD COLUMN AGE INTEGER'); store.version = 2; } } // 當(dāng)數(shù)據(jù)庫存在并假定版本為2時,例應(yīng)用從某一版本升級到當(dāng)前版本,數(shù)據(jù)庫需要從2版本升級到3版本 if (store.version === 2) { // version = 2:表結(jié)構(gòu):EMPLOYEE (NAME, AGE, SALARY, CODES, ADDRESS) => version = 3:表結(jié)構(gòu):EMPLOYEE (NAME, AGE, SALARY, CODES) if (store !== undefined) { (store as relationalStore.RdbStore).executeSql('ALTER TABLE EMPLOYEE DROP COLUMN ADDRESS TEXT'); store.version = 3; } } }); // 請確保獲取到RdbStore實(shí)例后,再進(jìn)行數(shù)據(jù)庫的增、刪、改、查等操作 } }FA模型示例:
import relationalStore from '@ohos.data.relationalStore'; // 導(dǎo)入模塊
import featureAbility from '@ohos.ability.featureAbility';
let context = featureAbility.getContext()
const STORE_CONFIG :relationalStore.StoreConfig = {
name: 'RdbTest.db', // 數(shù)據(jù)庫文件名
securityLevel: relationalStore.SecurityLevel.S1 // 數(shù)據(jù)庫安全級別
};
// 假設(shè)當(dāng)前數(shù)據(jù)庫版本為3,表結(jié)構(gòu):EMPLOYEE (NAME, AGE, SALARY, CODES)
const SQL_CREATE_TABLE = 'CREATE TABLE IF NOT EXISTS EMPLOYEE (ID INTEGER PRIMARY KEY AUTOINCREMENT, NAME TEXT NOT NULL, AGE INTEGER, SALARY REAL, CODES BLOB)'; // 建表Sql語句
relationalStore.getRdbStore(context, STORE_CONFIG, (err, store) => {
if (err) {
console.error(`Failed to get RdbStore. Code:${err.code}, message:${err.message}`);
return;
}
console.info('Succeeded in getting RdbStore.');
// 當(dāng)數(shù)據(jù)庫創(chuàng)建時,數(shù)據(jù)庫默認(rèn)版本為0
if (store.version === 0) {
store.executeSql(SQL_CREATE_TABLE); // 創(chuàng)建數(shù)據(jù)表
// 設(shè)置數(shù)據(jù)庫的版本,入?yún)榇笥?的整數(shù)
store.version = 3;
}
// 如果數(shù)據(jù)庫版本不為0且和當(dāng)前數(shù)據(jù)庫版本不匹配,需要進(jìn)行升降級操作
// 當(dāng)數(shù)據(jù)庫存在并假定版本為1時,例應(yīng)用從某一版本升級到當(dāng)前版本,數(shù)據(jù)庫需要從1版本升級到2版本
if (store.version === 1) {
// version = 1:表結(jié)構(gòu):EMPLOYEE (NAME, SALARY, CODES, ADDRESS) => version = 2:表結(jié)構(gòu):EMPLOYEE (NAME, AGE, SALARY, CODES, ADDRESS)
store.executeSql('ALTER TABLE EMPLOYEE ADD COLUMN AGE INTEGER');
store.version = 2;
}
// 當(dāng)數(shù)據(jù)庫存在并假定版本為2時,例應(yīng)用從某一版本升級到當(dāng)前版本,數(shù)據(jù)庫需要從2版本升級到3版本
if (store.version === 2) {
// version = 2:表結(jié)構(gòu):EMPLOYEE (NAME, AGE, SALARY, CODES, ADDRESS) => version = 3:表結(jié)構(gòu):EMPLOYEE (NAME, AGE, SALARY, CODES)
store.executeSql('ALTER TABLE EMPLOYEE DROP COLUMN ADDRESS TEXT');
store.version = 3;
}
});
// 請確保獲取到RdbStore實(shí)例后,再進(jìn)行數(shù)據(jù)庫的增、刪、改、查等操作
說明:
- 應(yīng)用創(chuàng)建的數(shù)據(jù)庫與其上下文(Context)有關(guān),即使使用同樣的數(shù)據(jù)庫名稱,但不同的應(yīng)用上下文,會產(chǎn)生多個數(shù)據(jù)庫,例如每個UIAbility都有各自的上下文。
- 當(dāng)應(yīng)用首次獲取數(shù)據(jù)庫(調(diào)用getRdbStore)后,在應(yīng)用沙箱內(nèi)會產(chǎn)生對應(yīng)的數(shù)據(jù)庫文件。使用數(shù)據(jù)庫的過程中,在與數(shù)據(jù)庫文件相同的目錄下可能會產(chǎn)生以-wal和-shm結(jié)尾的臨時文件。此時若開發(fā)者希望移動數(shù)據(jù)庫文件到其它地方使用查看,則需要同時移動這些臨時文件,當(dāng)應(yīng)用被卸載完成后,其在設(shè)備上產(chǎn)生的數(shù)據(jù)庫文件及臨時文件也會被移除。
- 獲取到RdbStore后,調(diào)用insert()接口插入數(shù)據(jù)。示例代碼如下所示:
import { ValuesBucket } from '@ohos.data.ValuesBucket';
let store: relationalStore.RdbStore | undefined = undefined;
let value1 = 'Lisa';
let value2 = 18;
let value3 = 100.5;
let value4 = new Uint8Array([1, 2, 3, 4, 5]);
// 以下三種方式可用
const valueBucket1: ValuesBucket = {
'NAME': value1,
'AGE': value2,
'SALARY': value3,
'CODES': value4,
};
const valueBucket2: ValuesBucket = {
NAME: value1,
AGE: value2,
SALARY: value3,
CODES: value4,
};
const valueBucket3: ValuesBucket = {
"NAME": value1,
"AGE": value2,
"SALARY": value3,
"CODES": value4,
};
if (store !== undefined) {
(store as relationalStore.RdbStore).insert('EMPLOYEE', valueBucket1, (err: BusinessError, rowId: number) => {
if (err) {
console.error(`Failed to insert data. Code:${err.code}, message:${err.message}`);
return;
}
console.info(`Succeeded in inserting data. rowId:${rowId}`);
})
}
說明:
關(guān)系型數(shù)據(jù)庫沒有顯式的flush操作實(shí)現(xiàn)持久化,數(shù)據(jù)插入即保存在持久化文件。
-
根據(jù)謂詞指定的實(shí)例對象,對數(shù)據(jù)進(jìn)行修改或刪除。
調(diào)用update()方法修改數(shù)據(jù),調(diào)用delete()方法刪除數(shù)據(jù)。示例代碼如下所示:
// 修改數(shù)據(jù)
let value1 = 'Rose';
let value2 = 22;
let value3 = 200.5;
let value4 = new Uint8Array([1, 2, 3, 4, 5]);
// 以下三種方式可用
const valueBucket1: ValuesBucket = {
'NAME': value1,
'AGE': value2,
'SALARY': value3,
'CODES': value4,
};
const valueBucket2: ValuesBucket = {
NAME: value1,
AGE: value2,
SALARY: value3,
CODES: value4,
};
const valueBucket3: ValuesBucket = {
"NAME": value1,
"AGE": value2,
"SALARY": value3,
"CODES": value4,
};
// 修改數(shù)據(jù)
let predicates = new relationalStore.RdbPredicates('EMPLOYEE'); // 創(chuàng)建表'EMPLOYEE'的predicates
predicates.equalTo('NAME', 'Lisa'); // 匹配表'EMPLOYEE'中'NAME'為'Lisa'的字段
if (store !== undefined) {
(store as relationalStore.RdbStore).update(valueBucket1, predicates, (err: BusinessError, rows: number) => {
if (err) {
console.error(`Failed to update data. Code:${err.code}, message:${err.message}`);
return;
}
console.info(`Succeeded in updating data. row count: ${rows}`);
})
}
// 刪除數(shù)據(jù)
predicates = new relationalStore.RdbPredicates('EMPLOYEE');
predicates.equalTo('NAME', 'Lisa');
if (store !== undefined) {
(store as relationalStore.RdbStore).delete(predicates, (err: BusinessError, rows: number) => {
if (err) {
console.error(`Failed to delete data. Code:${err.code}, message:${err.message}`);
return;
}
console.info(`Delete rows: ${rows}`);
})
}
- 根據(jù)謂詞指定的查詢條件查找數(shù)據(jù)。
調(diào)用query()方法查找數(shù)據(jù),返回一個ResultSet結(jié)果集。示例代碼如下所示:
let predicates = new relationalStore.RdbPredicates('EMPLOYEE');
predicates.equalTo('NAME', 'Rose');
if (store !== undefined) {
(store as relationalStore.RdbStore).query(predicates, ['ID', 'NAME', 'AGE', 'SALARY'], (err: BusinessError, resultSet) => {
if (err) {
console.error(`Failed to query data. Code:${err.code}, message:${err.message}`);
return;
}
console.info(`ResultSet column names: ${resultSet.columnNames}, column count: ${resultSet.columnCount}`);
// resultSet是一個數(shù)據(jù)集合的游標(biāo),默認(rèn)指向第-1個記錄,有效的數(shù)據(jù)從0開始。
while (resultSet.goToNextRow()) {
const id = resultSet.getLong(resultSet.getColumnIndex('ID'));
const name = resultSet.getString(resultSet.getColumnIndex('NAME'));
const age = resultSet.getLong(resultSet.getColumnIndex('AGE'));
const salary = resultSet.getDouble(resultSet.getColumnIndex('SALARY'));
console.info(`id=${id}, name=${name}, age=${age}, salary=${salary}`);
}
// 釋放數(shù)據(jù)集的內(nèi)存
resultSet.close();
})
}
說明:
當(dāng)應(yīng)用完成查詢數(shù)據(jù)操作,不再使用結(jié)果集(ResultSet)時,請及時調(diào)用close方法關(guān)閉結(jié)果集,釋放系統(tǒng)為其分配的內(nèi)存。
-
刪除數(shù)據(jù)庫。
調(diào)用deleteRdbStore()方法,刪除數(shù)據(jù)庫及數(shù)據(jù)庫相關(guān)文件。示例代碼如下:
Stage模型示例:
import UIAbility from '@ohos.app.ability.UIAbility';
class EntryAbility extends UIAbility {
onWindowStageCreate(windowStage: window.WindowStage) {
relationalStore.deleteRdbStore(this.context, 'RdbTest.db', (err: BusinessError) => {
if (err) {
console.error(`Failed to delete RdbStore. Code:${err.code}, message:${err.message}`);
return;
}
console.info('Succeeded in deleting RdbStore.');
});
}
}
FA模型示例:
import featureAbility from '@ohos.ability.featureAbility';
let context = getContext(this);
relationalStore.deleteRdbStore(context, 'RdbTest.db', (err: BusinessError) => {
if (err) {
console.error(`Failed to delete RdbStore. Code:${err.code}, message:${err.message}`);
return;
}
console.info('Succeeded in deleting RdbStore.');
});
寫在最后
- 如果你覺得這篇內(nèi)容對你還蠻有幫助,我想邀請你幫我三個小忙:
- 點(diǎn)贊,轉(zhuǎn)發(fā),有你們的 『點(diǎn)贊和評論』,才是我創(chuàng)造的動力。
- 關(guān)注小編,同時可以期待后續(xù)文章ing??,不定期分享原創(chuàng)知識。
- 想要獲取更多完整鴻蒙最新學(xué)習(xí)知識點(diǎn),請移步前往小編:
https://gitee.com/MNxiaona/733GH/blob/master/jianshu
