一、鸿蒙数据库概述
(一)鸿蒙数据库类型
鸿蒙系统中包含多种类型的数据库,以满足不同的开发需求,下面为大家介绍几种常见的类型及其特点与适用场景。
1. 轻量级偏好数据库
轻量级偏好数据库是一种轻量级存储方式,主要用于保存应用的一些常用配置。它具有以下特点:
- 数据存储形式:以键值对的形式储存数据,键的类型为字符串型,值的存储数据类型包括整型、字符串型、布尔型、浮点型、长整型、字符串型 Set 集合,通过这种简单的形式方便地对少量数据进行管理。
- 数据访问速度:应用运行时全量数据将会被加载在内存中,使得访问速度更快,存取效率更高,便于快速获取配置信息。
- 持久化存储:数据可以持久化的存储在设备上,不过需要注意的是,其最终会落盘到文本文件中,所以在开发过程中建议减少落盘频率,即减少对持久化文件的读写次数。
适用场景方面,它并不适合存储大量数据以及频繁改变数据的情况,比如应用的主题设置、用户的一些简单个性化偏好(如语言选择、字体大小偏好等)就可以使用轻量级偏好数据库来存储。
2. 关系型数据库
关系型数据库基于 SQLite 组件,适用于存储包含复杂关系数据的场景。例如一个班级的学生信息,需要包括姓名、学号、各科成绩等,又或者公司的雇员信息,需要包括姓名、工号、职位等,由于数据之间有较强的对应关系,复杂程度比键值型数据更高,此时就需要使用关系型数据库来持久化保存数据。它的特点如下:
- 运作机制:对外提供通用的操作接口,底层使用 SQLite 作为持久化存储引擎,支持 SQLite 具有的所有数据库特性,包括但不限于事务、索引、视图、触发器、外键、参数化查询和预编译 SQL 语句。
- 操作便利性:HarmonyOS 关系型数据库提供了一套完整的对本地数据库进行管理的机制,对外提供了一系列的增、删、改、查接口,也可以直接运行用户输入的 SQL 语句来满足复杂的场景需要,功能更加完善,查询效率也比较高。
不过,在大数据量场景下查询数据可能会导致耗时长甚至应用卡死的情况,如有相关操作可参考文档批量数据写数据库场景,并且有如下建议:单次查询数据量不超过 5000 条;在 TaskPool 中查询;拼接 SQL 语句尽量简洁;合理地分批次查询。
3. 对象关系映射数据库
HarmonyOS 对象关系映射(Object Relational Mapping,ORM)数据库是一款基于 SQLite 的数据库框架,屏蔽了底层 SQLite 数据库的 SQL 操作,针对实体和关系提供了增删改查等一系列的面向对象接口。其具有以下优势:
- 简化操作:开发者不必再去编写复杂的 SQL 语句,以操作对象的形式来操作数据库,提升效率的同时也能聚焦于业务开发,让开发变得更简单方便,例如一个类就是一个表,如果需要添加信息直接更新实体类就行。
- 基于关系型扩展:它的操作是基于关系型数据库操作接口完成的,实际是在关系型数据库操作的基础上又实现了对象关系映射等特性,跟关系型数据库一样,都使用 SQLite 作为持久化引擎,底层使用的是同一套数据库连接池和数据库连接机制。
开发者使用对象关系映射数据库需要先配置实体模型与关系映射文件,应用数据管理框架提供的类生成工具会解析这些文件,生成数据库帮助类,进而在运行时根据配置创建好数据库,并在存储过程中自动完成对象关系映射,然后通过对象数据操作接口(如 OrmContext 接口和谓词接口等)操作持久化数据库。
(二)数据库在鸿蒙开发中的重要性
在鸿蒙应用开发中,数据库起着至关重要的作用。
首先,数据库承担着数据存储的重要功能。随着应用功能的日益丰富,需要保存的数据量和种类也越来越多,比如用户的个人信息、使用应用过程中产生的各类记录(如购物记录、浏览历史等),数据库能够将这些数据有条理地存储起来,方便后续随时调用查看。
其次,数据库对于数据的管理也十分关键。它可以通过各种操作接口实现对数据的增删改查,让开发者能够灵活地根据应用逻辑和用户需求来处理数据。例如,社交类应用中好友列表的增加、删除好友,或者修改好友备注等操作,都依赖于数据库对数据的有效管理。
再者,数据库是实现各类功能的基础支撑。以具有搜索功能的应用为例,数据库能够快速响应搜索请求,从大量存储的数据中筛选出符合条件的数据反馈给用户,实现精准搜索;又如在具有推荐功能的应用中,通过对用户过往数据的分析(这些数据从数据库中获取)来实现个性化推荐。
总之,掌握鸿蒙数据库开发实践对于开发者来说是十分必要的,只有熟练运用数据库相关知识,才能开发出功能完善、体验良好的鸿蒙应用。
二、开发前准备工作
(一)环境搭建
进行鸿蒙数据库开发,首先要搭建好相应的软件环境与准备好硬件设备。
1. 软件环境
- DevEco Studio:这是鸿蒙开发的官方集成开发环境(IDE),基于 IntelliJ IDEA Community 开源版本打造,面向华为终端全场景多设备,为开发者提供工程模板创建、开发、编译、调试、发布等一站式服务。DevEco Studio 支持 Windows 系统和 macOS 系统,不过要保证其正常运行,电脑配置最好满足如下要求:操作系统为 Windows10 64 位,内存 8GB 及以上,硬盘 100GB 及以上,分辨率 1280*800 像素及以上。其编译构建依赖 JDK,DevEco Studio 预置了 Open JDK,版本为 1.8,安装过程中会自动安装 JDK。可以进入 HUAWEI DevEco Studio 产品页,点击下载列表后的按钮,下载 DevEco Studio,下载完成后,双击下载的 “deveco-studio-xxxx.exe”,进入 DevEco Studio 安装向导,在安装选项界面勾选 64-bit launcher(Windows 系统下)后,点击 Next,直至安装完成。
- OpenHarmony SDK:DevEco Studio 提供 SDK Manager 统一管理 SDK 及工具链,下载各种编程语言的 SDK 包时,SDK Manager 会自动下载该 SDK 包依赖的工具链。首次下载 HarmonyOS SDK 时,默认会下载 HarmonyOS Java SDK、JS SDK、Previewer 和 Toolchains。可以通过 DevEco Studio 向导指引下载 HarmonyOS SDK,默认情况下,SDK 会下载到 user 目录下,也可以指定对应的存储路径(注意 SDK 存储路径不支持中文字符),然后点击 Next,在弹出的 License Agreement 窗口,点击 Accept 开始下载 SDK。如果还需要使用 JS 或 C/C++ 语言开发应用时,需手动下载对应的 SDK 包,在菜单栏点击 Configure > Settings 或者默认快捷键 Ctrl+Alt+S(Mac 系统为 Configure > Preferences,快捷键 Command+,),打开 Settings 配置界面,进入 Appearance&Behavior > System Settings > HarmonyOS SDK 菜单界面,点击 Edit 按钮设置 HarmonyOS SDK 存储路径后,进行下载操作。
2. 硬件要求
通常可以选择适配的开发板来辅助开发与测试,例如润和 RK3568 开发板就是常用的一种,不同的开发场景和项目需求可能会对开发板的系统版本等有相应要求,像有的项目可能要求 OpenHarmony 系统为 3.2 Release 或者 4.0 Release 等版本,具体根据实际开发情况来准备合适的开发板硬件设备。开发环境配置完成后,可以通过运行 HelloWorld 工程来验证环境设置是否正确,后续便可以基于搭建好的环境开展鸿蒙数据库开发工作了。
(二)相关模块导入
在鸿蒙数据库开发中,依据使用的数据库类型不同,需要导入相应的模块,以下为常见数据库模块导入的操作步骤及相关注意事项。
1. 轻量级偏好数据库 SDK 导入
轻量级偏好数据库主要用于保存应用的一些常用配置,在开发前需要将其 SDK 导入开发环境。以 Java 开发为例,一般先创建好项目工程后,在代码中通过相应的类来获取 Preferences 实例进行后续操作,如利用DatabaseHelper类(DatabaseHelper databaseHelper = new DatabaseHelper(context);,其中context入参类型为ohos.app.Context),接着指定文件名(String fileName = "name";,文件名取值不能为空,也不能包含路径,默认存储目录可以通过context.getPreferencesDir()获取),然后通过databaseHelper.getPreferences(fileName)获取到Preferences实例,进而借助Preferences提供的方法来完成如数据的读写、观察数据变化等数据库相关操作。
2. 关系型数据库相关模块导入
若使用关系型数据库(基于 SQLite 组件)来存储包含复杂关系的数据,首先要导入相关模块,一般使用import relationalStore from '@ohos.data.relationalStore';语句来导入关系型数据库模块。后续操作中,例如要获取一个RdbStore实例来操作关系型数据库,代码大致如下:
let context: Context = getContext(this) as Context;
relationalStore.getRdbStore(context, CommonConstants.STORE_CONFIG, (err, rdb) => {
if (err) {
Logger.error(`${RDB_TAG}`, 'gerRdbStore() failed, err: ' + err);
return;
}
this.rdbStore = rdb;
// 获取到RdbStore后,需使用executeSql接口初始化数据库表结构和相关数据
this.rdbStore.executeSql(this.sqlCreateTable);
Logger.verbose(`${RDB_TAG}`, 'getRdbStore() finished.');
callback();
});
其中涉及到的配置参数(如CommonConstants.STORE_CONFIG)等要按照实际开发情况进行准确设置,配置好之后便可以利用其提供的增、删、改、查等接口(像insert()、delete()、update()等接口)来对数据库进行相应操作了。
3. 对象关系映射数据库模块导入
对于 HarmonyOS 对象关系映射(Object Relational Mapping,ORM)数据库,在进行开发之前,首先要进行build.gradle文件的配置,否则无法识别相关数据库的类的包。如果使用的注解处理器的模块为com.huawei.ohos.hap模块,需要在模块的build.gradle文件的ohos节点中添加以下配置:
compileOptions{
annotationEnabled true
}
若是使用注解处理器的模块为com.huawei.ohos.library,则需要在模块的build.gradle文件dependencies节点中配置注解处理器,此外,还需要在本地的 HUAWEI SDK 中找到orm_annotations_java.jar、orm_annotations_processor_java.jar和javapoet_java.jar这 3 个jar包的对应目录,并将这 3 个jar包的路径导入,代码如下:
dependencies {
compile files("orm_annotations_java.jar的路径","orm_annotations_processor_java.jar的路径","javapoet_java.jar的路径")
annotationProcessor files("orm_annotations_java.jar的路径","orm_annotations_processor_java.jar的路径","javapoet_java.jar的路径")
}
要是使用注解处理器的模块为java-library,则还需要导入ohos.jar的路径,具体代码类似上述添加依赖的格式。配置完成这些之后,就可以按照相应规则来创建数据库、构造数据表以及进行数据库的各类操作了,比如新建数据库时,要先定义一个表示数据库的类,继承OrmDatabase,再通过@Database注解内的entities属性指定哪些数据模型类属于这个数据库,version属性指明数据库版本号等操作步骤来开展后续开发。
总之,在导入这些数据库相关模块时,要严格按照对应的要求和规范进行操作,确保模块导入正确,为后续顺利开展鸿蒙数据库开发实践奠定基础。
三、轻量级偏好数据库开发实践
(一)获取 Preferences 实例
在鸿蒙系统中进行轻量级偏好数据库开发时,获取 Preferences 实例是操作数据库的首要步骤。以下是具体的代码实现方式及相关细节:
首先,需要导入相应的类来辅助获取实例,通常在 Java 开发环境下,利用DatabaseHelper类(DatabaseHelper databaseHelper = new DatabaseHelper(context);,这里的context入参类型为ohos.app.Context)。例如在应用的Ability或AbilitySlice中,可以通过调用getContext()方法来获得所需的context。
接着,要指定文件名,代码示例为String fileName = "name";,需要注意的是,文件名取值不能为空,也不能包含路径,其默认存储目录可以通过context.getPreferencesDir()获取。
完成上述准备工作后,就可以通过databaseHelper.getPreferences(fileName)语句来获取到Preferences实例了。获取到实例后,便能借助Preferences提供的一系列方法来完成如数据的读写、观察数据变化等数据库相关操作。示例代码如下:
// 假设在某个AbilitySlice类中获取Preferences实例
public class MyAbilitySlice extends AbilitySlice {
@Override
public void onStart(Intent intent) {
super.onStart(intent);
// 获取context
DatabaseHelper databaseHelper = new DatabaseHelper(this.getContext());
String fileName = "my_preferences";
try {
Preferences preferences = databaseHelper.getPreferences(fileName);
// 后续可在这里使用preferences实例进行数据操作,比如读取或写入数据等
// 此处仅为获取实例成功后的示例,可按需添加具体操作逻辑
new ToastDialog(this.getContext()).setText("获取Preferences实例成功").show();
} catch (Exception e) {
new ToastDialog(this.getContext()).setText("获取Preferences实例失败:" + e.getMessage()).show();
}
}
}
通过以上清晰的步骤,开发者就可以顺利获取到轻量级偏好数据库的 Preferences 实例,为后续的数据读取与操作奠定基础。
(二)数据读取与操作
获取到轻量级偏好数据库的 Preferences 实例后,就可以借助 Preferences API 进行数据读取与相关操作了。
1. 数据读取
轻量级偏好数据库以键值对的形式储存数据,键的类型为字符串型,值的存储数据类型包括整型、字符串型、布尔型、浮点型、长整型、字符串型 Set 集合。在读取数据时,针对不同的数据类型,有着相应的读取方法。
例如,要读取整型数据,可以使用getInt方法,示例代码如下:
Preferences preferences = // 假设已经通过前面介绍的方式获取到了实例
int value = preferences.getInt("int_key", 0); // 第一个参数为键名,第二个参数为键不存在时返回的默认值
如果要读取字符串类型的数据,代码如下:
String strValue = preferences.getString("str_key", ""); // 同样,第一个参数是键名,第二个是默认值,若键不存在则返回空字符串
2. 数据操作(增删改查)
- 数据增加(插入) :
通过put相关方法将数据写入 Preferences 实例,然后使用flush或者flushSync将 Preferences 实例持久化。比如插入一个整型数据和一个字符串数据:
preferences.putInt("new_int_key", 10); // 将键为"new_int_key",值为10的整型数据插入
preferences.putString("new_str_key", "Hello World"); // 插入字符串数据
preferences.flush(); // 异步将更改写入磁盘,flush方法会立即更改内存中的Preferences对象,但更新是异步写入磁盘的。注意,若对数据一致性要求较高,可考虑使用flushSync,但由于flushSync是同步操作,建议不要从主线程调用它,以免造成界面卡顿。
- 数据修改:
修改数据的操作和插入类似,也是通过put方法来更新键对应的值,示例如下:
preferences.putString("str_key", "Updated String"); // 假设"str_key"原本已有对应的值,这里将其更新为新的字符串
preferences.flush();
- 数据删除:
若要删除某个键值对,可以使用delete方法,例如:
preferences.delete("int_key"); // 删除键为"int_key"的键值对
- 数据查询:
除了前面提到的读取单个键对应值的情况外,还可以通过遍历等方式来查询多个数据或者根据业务逻辑进行更复杂的查询判断。比如,判断某个键是否存在,可以结合contains方法来实现:
boolean isExist = preferences.contains("check_key"); // 返回该键是否存在于Preferences实例中
总之,借助 Preferences API 可以方便地对轻量级偏好数据库中的数据进行各种操作,开发者可以根据具体的应用需求灵活运用这些方法,实现轻量级数据的高效管理。
四、关系型数据库开发实践
(一)数据库配置
在鸿蒙系统中进行关系型数据库开发时,首先要进行数据库配置,以下是具体的配置步骤及各参数意义与作用。
通常使用 StoreConfig 类来配置数据库信息,示例代码如下:
StoreConfig.Builder builder = new StoreConfig.Builder();
builder.setName("your_database_name.db") // 设置数据库文件名,根据实际需求命名,注意要符合文件名规范
.setSecurityLevel(relationalStore.SecurityLevel.S1) // 设置数据库安全级别,例如S1一般用于存储个人信息、应用配置、网络状态等,可按需选择合适的级别,不同级别对应不同的数据敏感程度
.setEncrypt(true) // 指定是否加密数据库,true表示加密,加密可以增强数据安全性,防止数据被未授权访问,若对数据保密性要求高可设置为加密状态
.build();
在上述代码中,setName 方法用于定义数据库的文件名,它决定了数据库在存储设备上的实际名称,后续操作将基于此文件名来识别和访问数据库。
setSecurityLevel 参数用于确定数据库的安全级别,鸿蒙系统提供了多个安全级别选项(如 S4 用于支付、财务、健康相关数据,S3 用于运动信息、位置信息、用户生成数据等),开发者要依据存储数据的敏感程度进行合理选择,确保数据安全与合规性。
setEncrypt 参数则关乎数据库的加密与否,若设置为 true,数据库会以加密形式存储,这样在数据存储和传输过程中,即使数据被非法获取,没有对应的解密手段也无法查看其真实内容,为数据增加了一道重要的安全防线。
完成上述配置后,便可依据配置参数获取数据库实例,进而开展后续的数据库操作。
(二)获取 RdbStore 实例
获取 RdbStore 实例是操作鸿蒙关系型数据库的关键步骤之一,下面介绍具体的获取方法及相关错误处理方式。
首先,需要获取应用上下文(Context),在不同的鸿蒙组件(如 Ability 或 AbilitySlice)中,可以通过相应的方法来获取,例如在 Ability 类中可以通过 this.getContext() 获取上下文对象。
接着,利用 relationalStore.getRdbStore 方法来获取 RdbStore 实例,代码示例如下:
let context: Context = getContext(this) as Context;
relationalStore.getRdbStore(context, config, (err, rdb) => {
if (err) {
Logger.error(`${RDB_TAG}`, 'getRdbStore() failed, err: ' + err);
// 此处可根据具体的错误类型进行针对性处理,比如提示用户检查配置参数是否正确、网络连接是否正常等
return;
}
this.rdbStore = rdb;
// 获取到RdbStore实例后,就可以使用它提供的接口进行后续数据库操作了,比如创建表格等
});
在上述获取过程中,如果出现错误,会通过回调函数的 err 参数返回具体的错误信息。常见的错误可能包括配置参数错误(如数据库文件名不符合规范、安全级别设置不合法等)、权限不足无法访问数据库,或者系统资源不足等原因导致获取失败。开发者需要根据返回的错误信息进行分析排查,确保能够正确获取到 RdbStore 实例,为后续的数据表操作等打下基础。
(三)数据表操作
1. 创建表格
在鸿蒙关系型数据库中,利用 SQL 语句来创建表格是常见且重要的操作,以下通过代码示例来讲解具体的创建方式。
假设要创建一个名为 students 的表格,用于存储学生的信息(包含学号、姓名、年龄、成绩等字段),代码如下:
let createTableSql = "CREATE TABLE IF NOT EXISTS students (" +
"id INTEGER PRIMARY KEY AUTOINCREMENT," + // 学号字段,设置为主键且自增长,确保每条记录有唯一标识
"name TEXT NOT NULL," + // 姓名字段,类型为文本且不能为空
"age INTEGER," + // 年龄字段,整数类型
"score REAL" + // 成绩字段,浮点型,用于存储分数
")";
this.rdbStore.executeSql(createTableSql);
在上述 SQL 语句中,CREATE TABLE 是创建表格的关键字,IF NOT EXISTS 表示如果表格不存在则创建,避免重复创建导致的错误。括号内是表格各字段的定义,每个字段定义包含字段名、字段类型以及一些可选的约束条件(如 NOT NULL 表示该字段不能为空)。
开发者在实际应用中,要根据具体业务需求合理定义表格结构,确定好各字段的名称、类型以及相应的约束,确保能够准确地存储和管理相关数据。
2. 插入数据
使用 RdbStore 类的 insert() 方法可以向已经创建好的表格中插入数据,以下介绍具体的操作步骤和注意事项。
首先,需要准备要插入的数据,使用 ValuesBucket 类来存储数据,示例如下:
let valuesBucket = new ValuesBucket();
valuesBucket.putInteger("id", 1); // 插入学号为1的数据
valuesBucket.putString("name", "张三"); // 对应的学生姓名为张三
valuesBucket.putInteger("age", 18); // 年龄为18岁
valuesBucket.putDouble("score", 90.5); // 成绩为90.5分
在上述代码中,通过 putInteger、putString、putDouble 等方法分别向 ValuesBucket 中添加不同类型的数据,要确保添加的数据类型与数据库表中对应字段的类型相匹配。
准备好数据后,调用 RdbStore 类的 insert() 方法插入数据,示例如下:
let rowId = this.rdbStore.insert("students", valuesBucket);
if (rowId > 0) {
Logger.info(`${RDB_TAG}`, '插入数据成功,插入行号为: ' + rowId);
} else {
Logger.error(`${RDB_TAG}`, '插入数据失败');
}
调用 insert() 方法时,第一个参数传入要插入数据的表名(如上述示例中的 students 表),第二个参数传入包含数据的 ValuesBucket 对象。插入成功会返回插入数据所在的行号(大于 0 的值),若插入失败则返回 -1,开发者可以根据返回值判断插入操作是否成功,并进行相应的后续处理。
同时要注意,插入的数据量如果较大,可能需要考虑分批插入等策略,避免一次性插入过多数据导致性能问题或者内存溢出等情况发生。
3. 查询数据
使用 RdbStore 类的 query() 方法可以实现对关系型数据库中数据的查询操作,下面介绍具体的查询流程。
首先,要获取可读的数据库实例,示例代码如下:
let readableRdbStore = this.rdbStore.getReadableRdbStore();
获取到可读实例后,通过构造合适的查询条件(可以是简单的查询全部数据,也可以根据具体业务需求设置筛选条件),利用 query() 方法进行查询,例如查询 students 表中所有学生的信息:
let resultSet = readableRdbStore.query("SELECT * FROM students");
while (resultSet.goToNextRow()) {
let id = resultSet.getInt("id"); // 获取学号字段值
let name = resultSet.getString("name"); // 获取姓名字段值
let age = resultSet.getInt("age"); // 获取年龄字段值
let score = resultSet.getDouble("score"); // 获取成绩字段值
Logger.info(`${RDB_TAG}`, `查询到学生信息:学号 ${id},姓名 ${name},年龄 ${age},成绩 ${score}`);
}
resultSet.close();
在上述代码中,query() 方法返回的 ResultSet 类用于处理查询结果,通过 goToNextRow() 方法可以遍历结果集中的每一行数据,再利用 getInt、getString、getDouble 等方法根据字段名获取具体的字段值,获取到值后就可以进行相应的业务处理(如展示数据、进行数据分析等)。最后,记得调用 resultSet.close() 方法关闭结果集,释放相关资源。
4. 删除数据
通过 RdbStore 类的 delete() 方法可以删除关系型数据库中的数据,以下是具体的操作细节。
首先,要构造删除条件,比如要删除 students 表中学号为 1 的学生信息,示例代码如下:
let predicates = new RdbPredicates("students"); // 指定要操作的表名
predicates.equalTo("id", 1); // 设置删除条件,这里是学号等于1
构造好删除条件后,调用 delete() 方法执行删除操作,示例如下:
let row = this.rdbStore.delete(predicates);
if (row > 0) {
Logger.info(`${RDB_TAG}`, '删除数据成功,删除行数为: ' + row);
} else {
Logger.error(`${RDB_TAG}`, '删除数据失败,未找到匹配的数据行');
}
调用 delete() 方法时传入构造好的删除条件对象,方法执行后会返回删除的行数,如果返回值大于 0,表示删除成功,且返回实际删除的行数;若返回 0,则表示没有找到符合条件的数据行,即删除操作未生效,开发者可以根据返回结果进行相应的提示或后续逻辑处理,确保数据删除操作符合预期。
五、分布式关系型数据库开发实践
(一)分布式功能介绍
分布式关系型数据库在鸿蒙系统的多设备交互场景中有着独特且重要的作用。
首先,它具备数据同步功能,能让数据分散存储于多个相互连接的设备节点上,并且保持这些数据在各个设备间实时同步更新。例如在智能家居场景中,用户通过手机对智能灯的亮度、颜色等设置进行调整后,这些设置数据能借助分布式关系型数据库的同步功能,即时同步到其他相关控制设备(如智能音箱、智能开关等)上,使得用户无论使用哪一个接入的设备,都能获取到最新且一致的数据状态。
与普通关系型数据库不同,普通关系型数据库通常将数据集中存储在单一的存储介质(如某一台服务器的硬盘)上,主要面向单机环境下的数据管理与操作,而分布式关系型数据库通过网络把多个独立的节点连接起来协同工作。这使得它在可扩展性方面表现突出,面对大量数据和高并发访问时,通过增加节点就能轻松处理更多的数据和请求,不会像普通关系型数据库那样容易面临性能瓶颈。
在容错性方面,分布式关系型数据库优势明显,当其中某个节点发生故障时,其他正常的节点可以继续提供服务,保障整个系统的数据可用以及业务的正常运转,而普通关系型数据库一旦所在节点出现故障,往往就会受到影响,甚至无法正常提供服务。
其应用场景十分广泛,除了上述提到的智能家居,在智能办公场景中,用户可以在不同的办公设备(如电脑、平板、手机)间同步文档编辑相关的数据库信息;在智能出行领域,车载系统与手机之间也能通过分布式关系型数据库实现行程记录、车辆状态等数据的同步共享,极大地方便了用户在不同设备间的无缝切换使用。
(二)相关接口使用
在鸿蒙系统中,实现分布式关系型数据库的增删改查及数据同步操作需要借助特定的接口来完成,以下为你介绍相关代码实现思路与步骤。
1. 导入必要模块
使用 import { relationalStore } from '@kit.ArkData'; 语句导入关系型数据库模块,同时可能还需要导入设备管理相关模块,例如 import { distributedDeviceManager } from '@kit.DistributedServiceKit';,用于后续识别和管理分布式环境中的不同设备。
2. 请求相关权限
要进行分布式数据同步操作,需要申请 ohos.permission.DISTRIBUTED_DATASYNC 权限。配置方式可以在应用的权限配置文件中按照相应规则声明该权限,并且在应用首次启动时,通常要弹窗向用户申请授权,示例代码如下(仅为示意,实际使用需结合具体框架进行调整):
import abilityAccessCtrl, { Permissions } from '@ohos.abilityAccessCtrl';
import common from '@ohos.app.ability.common';
async function getPermission_DISTRIBUTED_DATASYNC(context: Context): Promise<boolean> {
let array: Array<Permissions> = ['ohos.permission.DISTRIBUTED_DATASYNC'];
let atManager = abilityAccessCtrl.createAtManager();
let PermissionRequestResult = await atManager.requestPermissionsFromUser(context, array);
let authResults0 = PermissionRequestResult.authResults[0];
return ((authResults0 == 0));
}
3. 创建关系型数据库并设置分布式表
例如,创建一个名为 RdbTest.db 的数据库,并设置其安全级别等参数,代码如下:
import { UIAbility } from '@kit.AbilityKit';
import { BusinessError } from '@kit.BasicServicesKit';
import { window } from '@kit.ArkUI';
class EntryAbility extends UIAbility {
onWindowStageCreate(windowStage: window.WindowStage) {
const STORE_CONFIG: relationalStore.StoreConfig = {
name: "RdbTest.db",
securityLevel: relationalStore.SecurityLevel.S1
};
relationalStore.getRdbStore(this.context, STORE_CONFIG, (err: BusinessError, store: relationalStore.RdbStore) => {
if (err) {
console.error(`Failed to get RdbStore. Code:${err.code}, message:${err.message}`);
return;
}
// 假设创建一个名为EMPLOYEE的表用于演示,可根据实际需求修改表结构和名称
store.executeSql('CREATE TABLE IF NOT EXISTS EMPLOYEE (ID INTEGER PRIMARY KEY AUTOINCREMENT, NAME TEXT NOT NULL, AGE INTEGER, SALARY REAL, CODES BLOB)', (err) => {
if (err) {
console.error(`Failed to create table. Code:${err.code}, message:${err.message}`);
return;
}
// 设置分布式同步表,这里将EMPLOYEE表设置为可分布式同步
store.setDistributedTables(['EMPLOYEE']);
// 后续可在此处进行数据的相关操作,比如插入、查询等
});
});
}
}
4. 数据插入操作
准备好要插入的数据,使用 ValuesBucket 类来存储数据,例如插入一条员工信息数据:
let valuesBucket = new ValuesBucket();
valuesBucket.putInteger("ID", 1);
valuesBucket.putString("NAME", "张三");
valuesBucket.putInteger("AGE", 25);
valuesBucket.putDouble("SALARY", 8000.0);
let rowId = store.insert("EMPLOYEE", valuesBucket);
if (rowId > 0) {
console.info('插入数据成功,插入行号为: ', rowId);
} else {
console.error('插入数据失败');
}
5. 数据查询操作
先获取可读的数据库实例,再通过构造合适的查询条件进行查询,以下是查询 EMPLOYEE 表中所有数据的示例:
let readableRdbStore = store.getReadableRdbStore();
let resultSet = readableRdbStore.query("SELECT * FROM EMPLOYEE");
while (resultSet.goToNextRow()) {
let id = resultSet.getInt("ID");
let name = resultSet.getString("NAME");
let age = resultSet.getInt("AGE");
let salary = resultSet.getDouble("SALARY");
console.info(`查询到员工信息:工号 ${id},姓名 ${name},年龄 ${age},薪资 ${salary}`);
}
resultSet.close();
6. 数据删除操作
构造删除条件,比如删除工号为 1 的员工信息:
let predicates = new relationalStore.RdbPredicates('EMPLOYEE');
predicates.equalTo("ID", 1);
let row = store.delete(predicates);
if (row > 0) {
console.info('删除数据成功,删除行数为: ', row);
} else {
console.error('删除数据失败,未找到匹配的数据行');
}
7. 数据同步操作
使用 SYNC_MODE_PUSH 触发同步后,数据将从本设备向组网内的其它所有设备同步,示例如下:
// 构造用于同步分布式表的谓词对象
let predicates = new relationalStore.RdbPredicates('EMPLOYEE');
// 调用同步数据的接口
if (store!= undefined) {
(store as relationalStore.RdbStore).sync(relationalStore.SyncMode.SYNC_MODE_PUSH, predicates, (err, result) => {
// 判断数据同步是否成功
if (err) {
console.error(`Failed to sync data. Code:${err.code},message:${err.message}`);
return;
}
console.info('Succeeded in syncing data.');
for (let i = 0; i < result.length; i++) {
console.info(`device:${result[i][0]},status:${result[i][1]}`);
}
});
}
通过以上这些接口的合理使用以及相应代码步骤的实现,就能在鸿蒙系统中较好地完成分布式关系型数据库的各类操作,满足多设备交互场景下的数据管理与同步需求。
六、数据库版本升级实践
(一)升级方案选择
在鸿蒙数据库的版本升级实践中,通常存在多种升级方案可供选择,不同的方案有着各自的优缺点,适用于不同的场景需求,下面为大家对比介绍两种常见的数据库版本升级方案。
1. 直接升级
直接升级方案指的是直接实现各版本到目标版本的所有逻辑。例如,若要从版本 V1 升级至版本 V3,就需要将 V1 到 V3 过程中涉及的所有数据结构变化、功能调整等逻辑一次性全部实现。
- 优点:
其性能表现较好,因为避免了多次中间步骤的处理,能较为快速地将数据库升级到目标版本。特别是在对升级时效性要求较高,且数据库结构变化相对不是特别复杂的情况下,直接升级可以减少不必要的过渡操作,高效完成升级任务。
- 缺点:
然而,这种方案的逻辑复杂程度较高,需要开发者全面考虑各个版本之间的差异以及可能出现的兼容性问题等,实现起来难度较大。并且后续的维护工作也颇具挑战性,一旦出现问题,排查和修复的范围涉及整个升级逻辑,不利于快速定位和解决故障。
2. 逐步升级
逐步升级则是一步一步地将数据库升级到最新版本,只需要实现紧邻版本之间的升级逻辑即可。比如从 V1 升级到 V3,会先从 V1 升级到 V2,再从 V2 升级到 V3,分阶段进行。
- 优点:
逻辑相对简单,便于开发者理解和实现,每次只需关注相邻版本之间的变化,代码的编写和调试难度都相对较低。在后续的维护过程中,也更容易定位问题所在,因为每个阶段的升级逻辑相对独立,若出现异常,可以较快地确定是哪一个相邻版本升级环节出了问题,便于及时修复和改进。
- 缺点:
不过,由于涉及多个中间步骤,相比直接升级方案,性能方面会稍差一些,整体升级过程可能会花费更多的时间和系统资源,尤其是在数据库版本跨度较大,需要经过多个中间版本逐步过渡时,这种性能损耗可能会更加明显。
在实际的鸿蒙数据库版本升级实践中,开发者需要综合考虑数据库的规模、当前版本与目标版本之间的差异、对性能的要求以及后续维护的便利性等多方面因素,来选择合适的升级方案,确保数据库升级工作顺利进行。
(二)具体升级步骤
接下来,我们以一个具体的示例来展示鸿蒙数据库版本升级的实际操作步骤,这里主要基于关系型数据库进行讲解,涉及临时表方案使用、数据迁移、版本回退等关键操作的代码实现及逻辑说明。
1. 使用临时表方案进行数据迁移
首先,在升级时我们会采用临时表方案,具体操作如下:
// 开启事务
dbUtils.beginTransaction();
// 获取当前数据库版本
const oldVersion: number = dbUtils.getStoreVersion();
// 将旧表改为临时表
await dbUtils.updateTableName(Constants.STUDENT_TABLE, Constants.STUDENT_TABLE + oldVersion);
// 创建新表,这里需要根据目标版本获取对应的表结构信息(字段类型等)来创建新表
const newTableColumnTypeMap: string[][] = CommonUtils.getTableColumnTypeMapByVersion(targetVersion);
await dbUtils.createTable(Constants.STUDENT_TABLE, newTableColumnTypeMap);
dbUtils.commit();
// 自定义迁移逻辑,这里通常需要按照业务需求将临时表中的数据分批次取出并进行转换后存入新表
let lastId: number = -1;
let flag: boolean = false;
await this.migrate(lastId, oldVersion, targetVersion, flag);
// 删除旧表(即之前重命名的临时表),注意要根据版本等条件判断确保可以正确删除
await dbUtils.dropTable(Constants.STUDENT_TABLE + oldVersion, targetVersion);
在上述代码中,先是开启一个事务,保证数据操作的一致性和完整性。接着获取当前数据库的版本号,以便后续进行针对性的处理。然后将旧表重命名为临时表,再创建一张与旧表未重命名前名字相同的新表,这一步是为了后续能把数据从旧的结构迁移到新的结构中。创建好新表后,通过自定义的迁移逻辑,分批次取出临时表中的数据,按照新表的结构要求进行必要的转换后,存入新表中,待数据迁移完毕,最后删除临时表,完成数据的迁移过程。
2. 便于扩展迭代的逻辑设计
为了方便后续进行扩展迭代,我们可以在父类中定义整体的升级逻辑框架,子类只需继承父类,并重写特定的字段转换逻辑即可,示例代码如下:
/**
* TODO: 知识点: 新老版本字段转换逻辑(版本升级只需要实现这个方法即可)
* @param resultSet 查询结果集
* @param targetVersion 目标版本
* @returns 目标版本格式数据
*/
abstract transform(resultSet: relationalStore.ResultSet, targetVersion: number): ValuesBucket;
通过这种方式,后续如果数据库再有版本升级需求,涉及到字段结构变化等情况时,开发人员只需要在子类中关注具体的字段转换逻辑实现,而不用去改动整体的升级流程框架,提高了代码的可维护性和扩展性。
3. 根据版本选择对应升级逻辑
在实际执行升级操作时,需要根据数据库当前的版本以及要升级到的目标版本,选择对应的升级逻辑来执行,代码示例如下:
switch (currentVersion) {
case Constants.V1:
await this.v1DataService.onUpgrade(version);
break;
case Constants.V2:
await this.v2DataService.onUpgrade(version);
break;
default:
throw new Error('The database version to which you need to upgrade is incorrect');
}
上述代码中,通过 switch 语句判断当前数据库版本,如果是 Constants.V1 版本,就调用对应的 v1DataService.onUpgrade(version) 方法来执行升级到目标版本的操作;若是 Constants.V2 版本,则执行相应的 v2DataService.onUpgrade(version) 方法,以此类推。若传入的目标版本不符合预设的逻辑,就会抛出错误提示开发者检查版本设置是否正确,确保升级逻辑按预期执行。
4. 版本回退操作
在某些情况下,可能需要进行数据库版本回退,例如升级后出现兼容性问题或者功能异常等情况。以下是一个简单的版本回退思路及部分示例代码(假设基于备份数据进行回退):
// 假设之前在升级时已经对原数据进行了备份存储,这里先获取备份数据相关的实例等操作(具体根据备份实现方式而定)
let backupData = getBackupDataInstance();
// 根据备份数据恢复数据库到之前的版本状态,可能涉及到数据的重新写入等操作,示例如下(伪代码示意)
restoreDatabaseFromBackup(backupData);
// 回退后,根据业务需求可能还需要进行一些其他的清理工作、状态更新等,比如关闭临时资源、更新版本标识等
cleanupAfterRollback();
版本回退的关键在于要有可靠的备份机制以及对应的恢复逻辑,在实际应用中,需要根据具体的业务场景和数据库架构来详细设计和实现版本回退功能,