HarmonyOS 鸿蒙Next 基于关系型数据库的数据持久化

发布于 1周前 作者 zlyuanteng 来自 鸿蒙OS

HarmonyOS 鸿蒙Next 基于关系型数据库的数据持久化
升级HarmonyOS后,感觉手机的整体性能都有了很大的提升。

关于HarmonyOS 鸿蒙Next 基于关系型数据库的数据持久化的问题,您也可以访问:https://www.itying.com/category-93-b0.html 联系官网客服。

12 回复

场景描述

关系型数据库(Relational Database,RDB)是一种基于关系模型来管理数据的数据库。关系型数据库基于SQLite组件提供了一套完整的对本地数据库进行管理的机制,对外提供了一系列的增、删、改、查等接口,也可以直接运行用户输入的SQL语句来满足复杂的场景需要。

场景一:基于RDB已提供API的数据库基础使用

场景二:基于executeSql、querySql执行增删改查复杂SQL语句

场景三:事务的使用

场景四:批量插入数据的不同实现方式及性能对比

场景五:数据库备份与恢复

场景六:全文检索(FTS)使用思路

方案描述

场景一:基于RDB已提供API的数据库基础使用

方案

通过insert、update、delete、query接口及关系型数据库谓词predicates的数据库基础操作。RdbPredicates:数据库中用来代表数据实体的性质、特征或者数据实体之间关系的词项,主要用来定义数据库的操作条件。常用的方法有equalTo,notEqualTo,or,and,isnull,between,orderby,groupby等。

核心代码

文中的操作主要基于如下的数据库,详细数据库使用步骤可参考:关系型数据库使用指南


//创建数据库及相关表

export default class Rdb{

rdbStore?: relationalStore.RdbStore;

context:Context = getContext();

constructor(storeName: string)

{

// 数据库配置

const STORE_CONFIG:relationalStore.StoreConfig = {

name: storeName,

securityLevel: relationalStore.SecurityLevel.S1,

};

// 获取数据库Store

relationalStore.getRdbStore(this.context, STORE_CONFIG, (err: BusinessError, rdbStore: relationalStore.RdbStore) => {

this.rdbStore = rdbStore;

if (err) {

console.error(Get RdbStore failed, code is ${err.code},message is ${err.message});

return;

}

console.info(Get ${storeName} RdbStore successfully.);

})

}

CreateTable()

{

//建表语句

const SQL_CREATE_TABLE = ‘CREATE TABLE IF NOT EXISTS STUDENT (ID INTEGER PRIMARY KEY AUTOINCREMENT,NAME TEXT,AGE INTEGER,SALARY REAL)’;

if(this.rdbStore){

this.rdbStore.executeSql(SQL_CREATE_TABLE);

console.info(CreateTable successfully.);

}

}

}<button style="position: absolute; padding: 4px 8px 0px; cursor: pointer; top: 8px; right: 8px; font-size: 14px;">复制</button>

通过ValuesBucket构建数据,insert接口插入。


InsertData(name:string,age:number,salary:number)

{

// 插入数据

const valueBucket: ValuesBucket = {

‘NAME’: name,

‘AGE’: age,

‘SALARY’: salary

};

if(this.rdbStore){

this.rdbStore.insert(‘STUDENT’, valueBucket, (err, rowId) => {

if (err) {

console.error(Failed to insert data. Code:${err.code}, message:${err.message});

return;

}

console.info(Succeeded <span class="hljs-keyword"><span class="hljs-keyword">in</span></span> inserting data. rowId:${rowId});

})

}

}<button style="position: absolute; padding: 4px 8px 0px; cursor: pointer; top: 8px; right: 8px; font-size: 14px;">复制</button>

通过predicates构建删除条件,删除name为Lisa的数据,delete接口执行删除。


DeleteData()

{

let predicates = new relationalStore.RdbPredicates(“STUDENT”);

predicates.equalTo(“NAME”, “Lisa”);

if(this.rdbStore != undefined) {

(this.rdbStore as relationalStore.RdbStore).delete(predicates, (err, rows) => {

if (err) {

console.error(Delete failed, code is ${err.code},message is ${err.message});

return;

}

console.info(Delete rows: ${rows});

})

}

}<button style="position: absolute; padding: 4px 8px 0px; cursor: pointer; top: 8px; right: 8px; font-size: 14px;">复制</button>

通过update接口,根据predicates构造的条件,及valueBucket传入的数据修改数据。


UpdateData(name:string,age:number,salary:number)

{

const valueBucket: ValuesBucket = {

‘NAME’: name,

‘AGE’: age,

‘SALARY’: salary

};

let predicates = new relationalStore.RdbPredicates(“EMPLOYEE”);

predicates.equalTo(“NAME”, “Lisa”);

if(this.rdbStore != undefined) {

(this.rdbStore as relationalStore.RdbStore).update(valueBucket, predicates, relationalStore.ConflictResolution.ON_CONFLICT_REPLACE, (err, rows) => {

if (err) {

console.error(Updated failed, code is ${err.code},message is ${err.message});

return;

}

console.info(Updated row count: ${rows});

})

}

}<button style="position: absolute; padding: 4px 8px 0px; cursor: pointer; top: 8px; right: 8px; font-size: 14px;">复制</button>

通过query接口查询数据,返回resultSet结果集,并解析查询到的数据。


QueryData(callback:Callback<string>)

{

// 配置谓词

let predicates = new relationalStore.RdbPredicates(“STUDENT”);

let jsonData:Array<ValuesBucket> = new Array<ValuesBucket>();

if(this.rdbStore){

this.rdbStore.query(predicates, [“ID”, ‘NAME’, ‘AGE’, ‘SALARY’], (err, resultSet) => {

if (err) {

console.error(Failed to query data. Code:${err.code}, message:${err.message});

return;

}

console.info(ResultSet column names: ${resultSet.columnNames}, row count: ${resultSet.rowCount});

if(resultSet.rowCount == -1){

console.info(“rowCount=-1”)

}

// resultSet是一个数据集合的游标,默认指向第-1个记录,有效的数据从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});

const valueBucket: ValuesBucket = {

‘ID’:id,

‘NAME’: name,

‘AGE’: age,

‘SALARY’: salary

};

jsonData.push(valueBucket)

}

// 释放数据集的内存

resultSet.close();

// console.info("JSON: " + JSON.stringify(jsonData))

callback(JSON.stringify(jsonData))

})

}

}<button style="position: absolute; padding: 4px 8px 0px; cursor: pointer; top: 8px; right: 8px; font-size: 14px;">复制</button>

向数据库添加数据

cke_78512.png

场景二:基于executeSql、querySql执行增删改查复杂SQL语句

方案

在实际使用过程中,复杂的SQL语句可能无法直接通过场景一提供的方式实现,此时需要开发者通过executeSql、querySql接口执行自定义SQL语句。executeSql能够执行包含指定参数但不返回值的SQL语句,如创建表、创建索引、数据库触发器等场景。querySql能够根据指定SQL语句查询数据库中的数据,并返回查询结果ResultSet结果集。如递归查询、子查询等场景。

核心代码


//创建表

const SQL_CREATE_TABLE = ‘CREATE TABLE IF NOT EXISTS EMPLOYEE (ID INTEGER PRIMARY KEY AUTOINCREMENT,NAME TEXT NOT NULL,AGE INTEGER,SALARY REAL)’;

//创建索引

const CTEATE_INDEX =‘CREATE INDEX idx_name ON EMPLOYEE (NAME)’

this.rdbStore.executeSql(SQL_CREATE_TABLE);

this.rdbStore.executeSql(CTEATE_INDEX);

//数据库触发器

const TRIGGER_SQL2 = “CREATE TRIGGER test AFTER INSERT ON EMPLOYEE” +

" BEGIN" +

" UPDATE EMPLOYEE SET SALARY = ‘123456’ WHERE ID = 1;" +

" END;"<button style="position: absolute; padding: 4px 8px 0px; cursor: pointer; top: 8px; right: 8px; font-size: 14px;">复制</button>


//子查询

const SubQuery_SQL = "SELECT * FROM STUDENT WHERE ID IN (SELECT ID FROM STUDENT WHERE NAME = ‘John’);"<button style="position: absolute; padding: 4px 8px 0px; cursor: pointer; top: 8px; right: 8px; font-size: 14px;">复制</button>


//联合查询

const JoinQuerySQL ="SELECT student.name, courses.name, courses.credits FROM students JOIN courses ON students.id = courses.student_id;"<button style="position: absolute; padding: 4px 8px 0px; cursor: pointer; top: 8px; right: 8px; font-size: 14px;">复制</button>

对于上述的复杂SQL语句,就可以使用executeSql、querySql接口执行,。


myExecuteSql(sql:string)

{

if(this.rdbStore != undefined) {

(this.rdbStore as relationalStore.RdbStore).executeSql(sql, (err) => {

if (err) {

console.error(ExecuteSql failed, code is ${err.code},message is ${err.message});

return;

}

console.info(‘ExecuteSql success’);

})

}

}

myQuerySql(sql:string)

{

if(this.rdbStore){

(this.rdbStore as relationalStore.RdbStore).querySql(sql, (err, resultSet) => {

if (err) {

console.error(Query failed, code is ${err.code},message is ${err.message});

return;

}

console.info(ResultSet column names: ${resultSet.columnNames}, column count: ${resultSet.columnCount});

// resultSet是一个数据集合的游标,默认指向第-1个记录,有效的数据从0开始。

while (resultSet.goToNextRow()) {

const name = resultSet.getString(resultSet.getColumnIndex(“NAME”));

const age = resultSet.getLong(resultSet.getColumnIndex(“AGE”));

console.info(name=${name}, age=${age});

}

// 释放数据集的内存

resultSet.close();

})

}

}<button style="position: absolute; padding: 4px 8px 0px; cursor: pointer; top: 8px; right: 8px; font-size: 14px;">复制</button>

场景三:事务的使用

方案

数据库事务可以保证指一组数据库操作要么全部执行成功,要么全部回滚。鸿蒙关系型数据库提供了事务相关接口 beginTransaction、commit、rollBack。本例通过模拟一组操作中,有一条操作失败后,回滚已经执行的SQL语句。

核心代码


// 插入数据

if (this.rdbStore != undefined) {

//构建两条数据,其中第二条数据与表结构不符,会操作失败,数据库会回滚。

const valueBucket: ValuesBucket = {

NAME: name,

AGE: age,

SALARY: salary

};

const valueBucket2: ValuesBucket = {

NAME: ‘scd’,

SEX:‘男’,

AGE: 18,

SALARY: 18888

};

try {

//开启事务

this.rdbStore.beginTransaction();

await this.rdbStore.insert(‘STUDENT’, valueBucket)

await this.rdbStore.insert(‘STUDENT’, valueBucket2)

//提交事务

this.rdbStore.commit();

}

catch (err) {

//回滚事务

let code = (err as BusinessError).code;

let message = (err as BusinessError).message

console.error(Transaction failed, code is ${code},message is ${message});

this.rdbStore.rollBack()

return;

}

}

}<button style="position: absolute; padding: 4px 8px 0px; cursor: pointer; top: 8px; right: 8px; font-size: 14px;">复制</button>

上述代码中第一条数据插入成功,第二条数据与表结构不符,插入时会失败,此时数据库会回滚,第一条插入的数据被撤销,数据库不会发生变化。

cke_54704.png

场景四:批量插入数据的不同实现方式及性能对比

方案

分别使用insert、事务insert、batchInsert三种方式各自插入5000条数据,统计各自任务耗时

核心代码

直接使用insert插入5000条数据。


InsertDatas(name:string,age:number,salary:number)

{

for (let i = 1; i <= 5000; i++) {

// 插入数据

const valueBucket: ValuesBucket = {

‘NAME’: name+i,

‘AGE’: age,

‘SALARY’: salary

};

if(this.rdbStore){

this.rdbStore.insert(‘STUDENT’, valueBucket, (err, rowId) => {

if (err) {

console.error(Failed to insert data. Code:${err.code}, message:${err.message});

return;

}

console.info(Succeeded <span class="hljs-keyword"><span class="hljs-keyword">in</span></span> inserting data. rowId:${rowId});

})

}

}

}<button style="position: absolute; padding: 4px 8px 0px; cursor: pointer; top: 8px; right: 8px; font-size: 14px;">复制</button>

在事务中使用insert插入5000条数据


InsertDataByTransaction(name:string,age:number,salary:number){

if (this.rdbStore != undefined) {

try {

//开启事务

this.rdbStore.beginTransaction();

for (let i = 1; i <= 5000; i++) {

// 插入数据

const valueBucket: ValuesBucket = {

‘NAME’: name+i,

‘AGE’: age,

‘SALARY’: salary

};

if(this.rdbStore){

this.rdbStore.insert(‘STUDENT’, valueBucket, (err, rowId) => {

if (err) {

console.error(Failed to insert data. Code:${err.code}, message:${err.message});

return;

}

console.info(Succeeded <span class="hljs-keyword"><span class="hljs-keyword">in</span></span> inserting data. rowId:${rowId});

})

}

}

//提交事务

this.rdbStore.commit();

}

catch (err) {

//回滚事务

let code = (err as BusinessError).code;

let message = (err as BusinessError).message

console.error(Transaction failed, code is ${code},message is ${message});

this.rdbStore.rollBack()

return;

}

}

}<button style="position: absolute; padding: 4px 8px 0px; cursor: pointer; top: 8px; right: 8px; font-size: 14px;">复制</button>

使用barchInsert插入5000条数据


//批量添加

BatchInsert(name:string,age:number,salary:number){

{

if (this.rdbStore == null) {

console.error(Create Store1.db failed! store1 is <span class="hljs-literal"><span class="hljs-literal">null</span></span>);

return;

}

}

let valueBucketArray: Array<relationalStore.ValuesBucket> = new Array();

for (let i=1; i <=5000; i++) {

const valueBucket: relationalStore.ValuesBucket = {

‘NAME’: name+i,

‘AGE’: age,

‘SALARY’: salary

}

valueBucketArray.push(valueBucket);

}

try {

this.rdbStore.batchInsert(“STUDENT”, valueBucketArray ); // 该接口内部使用事务

console.info(Insert data successfully!);

} catch (err) {

console.error(Insert datae failed! err code:${err.code}, err message:${err.message})

}

}<button style="position: absolute; padding: 4px 8px 0px; cursor: pointer; top: 8px; right: 8px; font-size: 14px;">复制</button>

cke_41887.png

对上述三个方法进行耗时统计,根据测试结果,事务中批量添加数据与直接for循环添加数据耗时相差无几,而使用batchInsert时,时间明显更快,且batchInsert接口内部使用了事务,因此建议批量插入场景使用batchInsert。

场景五:数据库备份与恢复

方案

在数据库的使用过程中,数据库可能会因为数据丢失、数据损坏、脏数据等而不可用情况,为了预防这种情况,可以通过backup接口,提前备份数据库数据到本地文件中,当发生意外后,可以通过restore接口,从指定的数据库备份文件恢复数据库。

核心代码


//备份数据库

myBackup(){

if(this.rdbStore){

(this.rdbStore as relationalStore.RdbStore).backup(“dbBackup.db”, (err) => {

if (err) {

console.error(Backup failed, code is ${err.code},message is ${err.message});

return;

}

console.info(Backup success.);

})

}

}

//恢复数据库

myRestore(backupFileName:string){

// value:备份数据库名

if(this.rdbStore){

(this.rdbStore as relationalStore.RdbStore).restore(backupFileName, (err) => {

if (err) {

console.error(Restore failed, code is ${err.code},message is ${err.message});

return;

}

console.info(Restore success.);

})

}

}<button style="position: absolute; padding: 4px 8px 0px; cursor: pointer; top: 8px; right: 8px; font-size: 14px;">复制</button>

测试过程中,先通过myBackup备份数据库,数据库文件路径生成数据库备份文件dbBackup.db。

cke_29322.png

随后使用delete删除name为testA、testB数据后,此时数据库已无相关数据。

cke_22889.png

再使用myRestore恢复数据库后,数据库恢复到备份时状态。

cke_17551.png

注:数据库文件路径可通过context.databaseDir获取。如:/data/app/el2/100/database/{bundleName}/entry/rdb

场景六:全文检索(FTS)使用思路

方案

HarmonyOS关系型数据库底层使用的是Sqlite,在Sqite中FTS的核心是倒排索引,它是一种将词汇映射到出现该词汇的文档集合的数据结构。在创建FTS虚拟表时,SQLite会为每个词汇生成一个倒排索引,记录该词汇在哪些文档(即数据库记录)中出现。倒排索引使得全文搜索能够快速找到包含特定词汇的文档,而无需遍历整个数据库。

FTS虚拟表(Full-Text Search Virtual Table)是SQLite中实现全文搜索的一种特殊表结构。它用于存储全文索引数据,包括倒排索引的信息。虽然FTS虚拟表在查询时表现得像普通的SQLite表,但其实现和存储方式与普通表有很大不同。

使用全文检索时,创建FTS表需要使用CREATE VIRTUAL TABLE语句,执行全文搜索需要使用MATCH关键字,当前HarmonyOS关系型数据库并没有直接提供相关接口,但是数据库底层是支持的,因此可以通过executeSql、querySql执行相关SQL语句。

核心代码


//备份数据库

myBackup(){

if(this.rdbStore){

(this.rdbStore as relationalStore.RdbStore).backup(“dbBackup.db”, (err) => {

if (err) {

console.error(Backup failed, code is ${err.code},message is ${err.message});

return;

}

console.info(Backup success.);

})

}

}

//恢复数据库

myRestore(backupFileName:string){

// value:备份数据库名

if(this.rdbStore){

(this.rdbStore as relationalStore.RdbStore).restore(backupFileName, (err) => {

if (err) {

console.error(Restore failed, code is ${err.code},message is ${err.message});

return;

}

console.info(Restore success.);

})//创建虚拟表

const sql1:string = “CREATE VIRTUAL TABLE products_fts USING fts5(NAME , AGE)”

this.rdbTest.myExecuteSql(sql1)

//虚拟表添加数据 InsertDataToVIRTUAL(){

let sql:string = “INSERT INTO products_fts(NAME , AGE) SELECT NAME,AGE FROM STUDENT”

this.myExecuteSql(sql)

}

//使用全文检索查询数据

let sql:string = “SELECT * FROM products_fts WHERE AGE MATCH 18”

this.rdbTest.myQuerySql(sql)

myExecuteSql(sql:string)

{

if(this.rdbStore != undefined) {

(this.rdbStore as relationalStore.RdbStore).executeSql(sql, (err) => {

if (err) {

console.error(ExecuteSql failed, code is ${err.code},message is ${err.message});

return;

}

console.info(‘ExecuteSql success’);

})

}

}

myQuerySql(sql:string)

{

if(this.rdbStore){

(this.rdbStore as relationalStore.RdbStore).querySql(sql, (err, resultSet) => {

if (err) {

console.error(Query failed, code is ${err.code},message is ${err.message});

return;

}

console.info(ResultSet column names: ${resultSet.columnNames}, column count: ${resultSet.columnCount});

// resultSet是一个数据集合的游标,默认指向第-1个记录,有效的数据从0开始。

while (resultSet.goToNextRow()) {

const name = resultSet.getString(resultSet.getColumnIndex(“NAME”));

const age = resultSet.getLong(resultSet.getColumnIndex(“AGE”));

console.info(name=${name}, age=${age});

}

// 释放数据集的内存

resultSet.close();

})

}

}

}

}<button style="position: absolute; padding: 4px 8px 0px; cursor: pointer; top: 8px; right: 8px; font-size: 14px;">复制</button>

查询虚拟表中AGE"字段匹配关键词"18"的记录,虚拟表查询结果:

cke_7875.png

参考链接

@ohos.data.relationalStore (关系型数据库)

找HarmonyOS工作还需要会Flutter的哦,有需要Flutter教程的可以学学大地老师的教程,很不错,B站免费学的哦:https://www.bilibili.com/video/BV1S4411E7LY/?p=17

关系型数据库有什么用啊,比如我网络请求加载首页数据,是先获取到关系型数据库的数据渲染页面,再axios请求网络将关系型数据库的数据覆盖吗,没搞懂这个关系型数据库的使用场景

举个例子:有道笔记、印象笔记,在没网络时也可以写笔记。 这个时候写的笔记数据就会存到关系型数据库, 在有网络时就会从关系型数据库取出这些数据同步到云端服务器。 这种使用场景,在离在线应用中很常见的做法。

关系型数据库基于SQLite组件,适用于存储包含复杂关系数据的场景,比如一个班级的学生信息,需要包括姓名、学号、各科成绩等,又或者公司的雇员信息,需要包括姓名、工号、职位等,由于数据之间有较强的对应关系,复杂程度比键值型数据更高,此时需要使用关系型数据库来持久化保存数据。

微信本地聊天记录就是sqlite存的

我也不太懂关系数据库的使用场景。能可视化操作吗?像SSMS或者MySql的

找HarmonyOS工作还需要会Flutter的哦,有需要Flutter教程的可以学学大地老师的教程,很不错,B站免费学的哦:https://www.bilibili.com/video/BV1S4411E7LY/?p=17

关系型数据库基于SQLite组件,适用于存储包含复杂关系数据的场景,比如一个班级的学生信息,需要包括姓名、学号、各科成绩等,又或者公司的雇员信息,需要包括姓名、工号、职位等,由于数据之间有较强的对应关系,复杂程度比键值型数据更高,此时需要使用关系型数据库来持久化保存数据。 目前暂不支持可视化操作

你好!怎么把创建的数据库文件取出来呢?

关系型数据库文件并查看可参考如下是步骤

  1. hdc shell // 进入设备
  2. find /data -name 数据库名字 // 查找数据库实际路径
  3. exit // 退出
  4. hdc file recv 数据库所在目录 导出的目的路径 // 导出数据库文件(db文件+wal文件+shm文件)
  5. 使用sqliteStudio或者其他工具打开db文件
回到顶部