HarmonyOS 應(yīng)用開發(fā)之通過關(guān)系型數(shù)據(jù)庫實(shí)現(xiàn)數(shù)據(jù)持久化

場景介紹

關(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)在兩種模型下均適用。

  1. 使用關(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ù)庫文件及臨時文件也會被移除。
  1. 獲取到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ù)插入即保存在持久化文件。

  1. 根據(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}`);
     })
   }
  1. 根據(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)存。

  1. 刪除數(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
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容