场景介绍
应用中,我们有些数据比较复杂,而且需要频繁的增删改查,这时候就不适合使用首选项来进行存储和管理了,HarmonyOS NEXT中的关系型数据库基于SQLite组件,适用于存储包含复杂关系数据。
基本概念
-
谓词:数据库中用来代表数据实体的性质、特征或者数据实体之间关系的词项,主要用来定义数据库的操作条件。
-
结果集:指用户查询之后的结果集合,可以对数据进行访问。结果集提供了灵活的数据访问方式,可以更方便地拿到用户想要的数据
运作机制
关系型数据库对应用提供通用的操作接口,底层使用SQLite作为持久化存储引擎,支持SQLite具有的数据库特性,包括但不限于事务、索引、视图、触发器、外键、参数化查询和预编译SQL语句。
约束限制
-
系统默认日志方式是WAL(Write Ahead Log)模式,系统默认落盘方式是FULL模式。
-
数据库中有4个读连接和1个写连接,线程获取到空闲读连接时,即可进行读取操作。当没有空闲读连接且有空闲写连接时,会将写连接当做读连接来使用。
-
为保证数据的准确性,数据库同一时间只能支持一个写操作。
-
当应用被卸载完成后,设备上的相关数据库文件及临时文件会被自动清除。
-
ArkTS侧支持的基本数据类型:number、string、二进制类型数据、boolean。
-
为保证插入并读取数据成功,建议一条数据不要超过2M。超出该大小,插入成功,读取失败。
开发步骤
- 通过getRdbStore获取一个relationalStore.RdbStore类型的对象,用于管理数据库,对数据库的建库,建表,升降级等操作。
- 获取到RdbStore后可以调用execute方法创建表,execute接收一条SQL语句。
- 调用insert方法插入一条数据或者使用batchInsert批量插入数据。
- 根据谓词指定的实例对象,对数据进行修改或删除。
- 使用ResultSet获取执行完操作的查询结果。
1.创建RdbStore类型的对象,用于对数据库的建库,建表,升降级等操作
TypeScript
export class DBUtils {
appRDB?: relationalStore.RdbStore;
// 创建数据库
creatRDB(context: Context) {
const STORE_CONFIG: relationalStore.StoreConfig = {
name: "projectRdb.db",//数据库文件名
securityLevel: relationalStore.SecurityLevel.S1,// 数据库安全级别
encrypt: false, // 可选参数,指定数据库是否加密,默认不加密
customDir: 'dbDir', // 可选参数,数据库自定义路径。数据库将在如下的目录结构中被创建:context.databaseDir + '/rdb/' + dbDir,其中context.databaseDir是应用沙箱对应的路径,
// '/rdb/'表示创建的是关系型数据库,dbDir表示自定义的路径。当此参数不填时,默认在本应用沙箱目录下创建RdbStore实例。
isReadOnly: false // 可选参数,指定数据库是否以只读方式打开。该参数默认为false,表示数据库可读可写。该参数为true时,只允许从数据库读取数据,不允许对数据库进行写操作,否则会返回错误码801。
};
relationalStore.getRdbStore(context, STORE_CONFIG, (error: BusinessError, store: relationalStore.RdbStore) => {
this.appRDB = store;
if (error) {
hilog.error(0x0000, TAG, `Get RdbStore failed, code is ${error.code}, message is ${error.message}`);
return;
}
if (store.version === 0) {
//创建表
this.createUserTable();
store.version = 1;
}
});
}
}
2.通过execute创建表
TypeScript
// 创建用户表
createUserTable() {
this.appRDB?.execute(CommonConstants.CREATE_USER_TABLE_SQL).then(() => {
hilog.info(0x0000, TAG, `execute create user table sql success`);
//初始化表
this.initTable();
}).catch((error: BusinessError) => {
hilog.error(0x0000, TAG, `execute sql failed, code is ${error.code}, message is ${error.message}`);
});
}
3.调用insert方法插入一条数据或者使用batchInsert批量插入数据
假设需要创建一个学生数据表,刚刚建表完成后,我们现在批量插入一批数据,批量插入需要传入表名,和一个List<ValuesBucket>类型的参数,代码如下:
TypeScript
//初始化表
initTable() {
const user1: relationalStore.ValuesBucket = {
'ID': 0,
'NAME': 'Jack',
'AGE': 18,
'SCORE': 90,
}
const user2: relationalStore.ValuesBucket = {
'ID': 1,
'NAME': 'Tonny',
'AGE': 19,
'SCORE': 100,
}
const user3: relationalStore.ValuesBucket = {
'ID': 2,
'NAME': 'kirk',
'AGE': 20,
'SCORE': 60,
}
let valueBuckets = new Array(user1, user2, user3);
//向表中批量添加数据,关系型数据库没有显式的flush操作实现持久化,数据插入即保存在持久化文件。
this.appRDB?.batchInsert('USER', valueBuckets).then((insertNum: number) => {
hilog.info(0x0000, TAG, `Insert is successful, rows number : ${insertNum}`);
}).catch((error: BusinessError) => {
hilog.error(0x0000, TAG, `Insert is failed, code is ${error.code},message is ${error.message}`);
})
}
如果是插入单挑数据可以用insert,代码如下:
TypeScript
//插入一条数据
async insertUser() {
const user: relationalStore.ValuesBucket = {
'NAME': 'mutang',
'AGE': 8,
'SCORE': 100
};
await this.appRDB?.insert('USER', user,
relationalStore.ConflictResolution.ON_CONFLICT_REPLACE).then((rowId: number) => {
hilog.info(0x0000, TAG, `Insert is successful, rowId = ${rowId}`);
}).catch((error: BusinessError) => {
hilog.error(0x0000, TAG, `Insert is failed, code is ${error.code},message is ${error.message}`);
})
}
4.根据谓词指定的实例对象,对数据进行修改或删除
首先使用RdbPredicates确定数据库操作条件,然后根据筛选条件来执行修改或者删除
TypeScript
async updateUser(id:number,score:number){
const tempDate:relationalStore.ValuesBucket ={
'SCORE':score
}
//使用RdbPredicates确定数据库操作条件
let predicates = new relationalStore.RdbPredicates('USER');
predicates.equalTo('ID',id);
//4.执行数据库操作
this.appRDB?.update(tempDate,predicates,relationalStore.ConflictResolution.ON_CONFLICT_REPLACE).then(async (rows:Number)=>{
hilog.info(0x0000, TAG, `Updated row count: ${rows}`);
}).catch((error:BusinessError)=>{
})
}
async deletePlan(planID: number) {
//使用RdbPredicates确定数据库操作条件
let predicates = new relationalStore.RdbPredicates('USER');
predicates.equalTo('ID', planID);
await this.appRDB?.delete(predicates).then((rows: Number) => {
hilog.info(0x0000, TAG, `Delete rows: ${rows}`);
}).catch((err: BusinessError) => {
hilog.error(0x0000, TAG, `Delete failed, code is ${err.code},message is ${err.message}`);
})
}
5.使用ResultSet获取执行完操作的查询结果
查询数据库表大概步骤如下:
- 在回调函数中会返回一个ResultSet类型的值。
- 我们可以通过goToNextRow进行遍历,获取到一行数据后,可以使用getColumnIndex()方法,参数传入数据表中的字段名,即可获取到该字段名对应的索引值,然后通过getValue()方法传入字段索引,即可获取到该字段名对应的值,getValue()的返回值类型是ValueType,我们可以将其强制类型转化为具体的数据类型。
- 再将取出的数据保存至实体类中,将所有的实体类对象存储在实体类数组中。
- 关闭ResultSet,释放资源并返回实体类数组。
TypeScript
//查询所有用户名数据
async queryAllUsers(): Promise<Userinfo[]> {
let userList: Array<Userinfo> = [];
//查询数据
await this.appRDB?.querySql(CommonConstants.QUERY_ALL_NAME_SQL).then((resultSet: relationalStore.ResultSet) => {
//使用ResultSet获取执行完操作的查询结果
while (resultSet.goToNextRow()) {
const id = resultSet.getValue(resultSet.getColumnIndex('ID')) as number;
const name = resultSet.getValue(resultSet.getColumnIndex('NAME')) as string;
const age = resultSet.getValue(resultSet.getColumnIndex('AGE')) as number;
const score = resultSet.getValue(resultSet.getColumnIndex('SCORE')) as number;
//将所有的实体类对象存储在实体类数组中
userList.push(new Userinfo(id, name, age,score));
}
//关闭ResultSet,释放资源
resultSet.close();
},(error:BusinessError)=>{
hilog.error(0x0000, TAG, `Query failed, code is ${error.code},message is ${error.message}`);
})
//将该实体类数组作为该方法的返回值返回
return userList;
}
项目实践
实践背景:现有一批学员信息,包含学员姓名,年龄,成绩。满足对学员信息的增删改查
首先在UIAbliity中初始化数据库
TypeScript
import { AbilityConstant, ConfigurationConstant, UIAbility, Want } from '@kit.AbilityKit';
import { hilog } from '@kit.PerformanceAnalysisKit';
import { window } from '@kit.ArkUI';
import DBUtils from '../utils/DBUtils'
export default class EntryAbility extends UIAbility {
onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
this.context.getApplicationContext().setColorMode(ConfigurationConstant.ColorMode.COLOR_MODE_NOT_SET);
hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onCreate');
}
onDestroy(): void {
hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onDestroy');
}
onWindowStageCreate(windowStage: window.WindowStage): void {
// Main window is created, set main page for this ability
hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onWindowStageCreate');
windowStage.loadContent('pages/Index', (err) => {
if (err.code) {
hilog.error(0x0000, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err) ?? '');
return;
}
hilog.info(0x0000, 'testTag', 'Succeeded in loading the content.');
});
//创建数据库
DBUtils.creatRDB(this.context)
}
onWindowStageDestroy(): void {
// Main window is destroyed, release UI related resources
hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onWindowStageDestroy');
}
onForeground(): void {
// Ability has brought to foreground
hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onForeground');
}
onBackground(): void {
// Ability has back to background
hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onBackground');
}
}
上面的开发步骤中,其实已经将大部分功能完成了,我们接下来要做的就是页面该如何调用,以及效果展示
在开发步骤中,获取到RdbStore对象后进行了创建表,而后初始化了数据,所以我们UI展示只需要调用查询所有数据的方法:
TypeScript
@State plansSet: Array<Userinfo> = [];
async aboutToAppear(): Promise<void> {
await DBUtils.queryAllUsers().then((value) => {
this.plansSet = value;
});
}
页面效果如下:

点击新增学员,这里为了方便,我就不做编辑信息的UI了
TypeScript
Button('新增学员')
.width('100%')
.margin({ bottom: '10vp' }).onClick(async() => {
await DBUtils.insertUser().then(() => {
DBUtils.queryAllUsers().then((value) => {
this.plansSet = value;
});
});
})
执行完插入操作后调用查询所有信息的方法,最新的结果会更新在UI上,效果如下:

修改:修改学员ID为0的成绩为100分:
TypeScript
Button('修改第一名学员成绩为100分')
.width('100%')
.margin({ bottom: '10vp' }).onClick(async() => {
await DBUtils.updateUser(0,100).then(() => {
DBUtils.queryAllUsers().then((value) => {
this.plansSet = value;
});
});
})
执行效果如下:

删除:
TypeScript
await DBUtils.deletePlan(0).then(() => {
DBUtils.queryAllUsers().then((value) => {
this.plansSet = value;
});
});
效果如下:

总结
鸿蒙关系型数据库的开发步骤和简单使用就到这了,还有更多的API和高阶使用请参考文档