基于Data Ability的关系型数据库操作方法介绍
1. 介绍
HarmonyOS支持应用以Ability为单位进行部署,Ability可以分为FA(Feature Ability)和PA(Particle Ability)两种类型,其中PA支持Data Ability(即Data模板),用于对应用内及应用间提供统一的数据访问抽象。使用Data Ability(以下简称"Data")有助于应用管理自身和其他应用存储数据的访问,并提供与其他应用共享数据的方法。Data既可用于同设备应用间数据共享,也支持跨设备应用间数据共享。Data支持的数据存储方式,包括文件数据(如文本、图片、音乐等)和结构化数据(如数据库等)。
同时,HarmonyOS提供多种数据管理能力,例如:关系型数据库、对象关系映射数据库、轻量级偏好数据库、分布式数据服务等。
本教程以大家熟悉的关系型数据库为例,基于Data Ability介绍数据库的创建、数据的增加/删除/修改/查询等操作方法,让您快速了解Data Ability和数据管理能力并能够开发数据库相关应用服务。关于HarmonyOS Data Ability和关系型数据库的详细介绍,请参考Data Ability 和关系型数据库。
通过本教程,您将实现基于Data Ability创建数据库服务,对外提供访问数据库的服务端接口(类PersonDataAbility),并在MainAbilitySlice中通过DataAbilityHelper与提供方的Data Ability进行通信,最后通过日志查看数据库操作结果。
2. 创建一个Data Ability
在工程中添加Empty Data Ability,用于创建数据库并提供API接口。例如,本教程创建的数据库用于存储人员信息,将创建的Empty Data Ability命名为PersonDataAbility,其操作过程为:在HUAWEI DevEco Studio"Project"窗口当前工程的主目录("entry > src > main > java > com.xxx.xxx")选择"File > New > Ability > Empty Data Ability",设置"Data Name"为PersonDataAbility并完成创建。此时,HUAWEI DevEco Studio将自动生成类PersonDataAbility及相关方法,其继承类Ability(默认实现了数据库的增/删/改/查API方法),示例代码如下:
public class PersonDataAbility extends Ability {
private static final HiLogLabel LABEL_LOG = new HiLogLabel(3, 0xD001100, "Demo");
@Override
public void onStart(Intent intent) {
super.onStart(intent);
HiLog.info(LABEL_LOG, "PersonDataAbility onStart");
}
@Override
public ResultSet query(Uri uri, String[] columns, DataAbilityPredicates predicates) {
return null;
}
@Override
public int insert(Uri uri, ValuesBucket value) {
HiLog.info(LABEL_LOG, "PersonDataAbility insert");
return 999;
}
@Override
public int delete(Uri uri, DataAbilityPredicates predicates) {
return 0;
}
@Override
public int update(Uri uri, ValuesBucket value, DataAbilityPredicates predicates) {
return 0;
}
}
同时,HUAWEI DevEco Studio将自动在工程配置文件config.json中添加如下"data"信息:
{
"permissions": [
"com.huawei.codelab.DataAbilityShellProvider.PROVIDER"
],
"name": "com.huawei.codelab.PersonDataAbility",
"icon": "$media:icon",
"description": "$string:userdataability_description",
"type": "data",
"uri": "dataability://com.huawei.codelab.PersonDataAbility"
}
默认情况下隐藏"visible"字段(值为false),表示仅本应用可访问该Data,开发人员可根据需求修改permissions、visible值、uri等内容。当外部应用需要访问/控制此数据库字段时,在该Data Ability配置中增加"visible": true,并在外面应用的配置文件config.json中申请permissions权限。
3. 定义Data Ability数据库相关常量
为了方便操作,在PersonDataAbility类中定义数据库相关常量(实际编码过程中,可以单独定义常量类),包括数据库名、表名、表字段名、数据库版本号等。例如,定义一个名为person的表,其包含四列:一个唯一的身份id 、名字name 、性别gender和年龄age,示例代码如下:
public class PersonDataAbility extends Ability {
......
private static final String DB_NAME = "persondataability.db";
private static final String DB_TAB_NAME = "person";
private static final String DB_COLUMN_PERSON_ID = "id";
private static final String DB_COLUMN_NAME = "name";
private static final String DB_COLUMN_GENDER = "gender";
private static final String DB_COLUMN_AGE = "age";
private static final int DB_VERSION = 1;
......
}
4. 创建关系型数据库
参考关系型数据库开发指导中的开发步骤,在PersonDataAbility类中定义RdbStore变量,并通过RdbOpenCallback创建数据库persondataability.db及表person,示例代码如下:
private StoreConfig config = StoreConfig.newDefaultConfig(DB_NAME);
private RdbStore rdbStore;
private RdbOpenCallback rdbOpenCallback = new RdbOpenCallback() {
@Override
public void onCreate(RdbStore store) {
store.executeSql("create table if not exists "
+ DB_TAB_NAME + " ("
+ DB_COLUMN_PERSON_ID + " integer primary key, "
+ DB_COLUMN_NAME + " text not null, "
+ DB_COLUMN_GENDER + " text not null, "
+ DB_COLUMN_AGE + " integer)");
}
@Override
public void onUpgrade(RdbStore store, int oldVersion, int newVersion) {
}
};
5. 初始化数据库连接
在应用启动时,系统会调用Data Ability的onStart()方法创建Data实例。在此方法中,您需要创建数据库连接,并获取连接对象,以便后续对数据库进行操作。为了避免影响应用启动速度,请勿在此方法中执行所有任务的初始化,对于非必要的耗时任务,应推迟到使用阶段执行。
在PersonDataAbility初始化的时候连接数据库,示例代码如下:
@Override
public void onStart(Intent intent) {
super.onStart(intent);
HiLog.info(LABEL_LOG, "PersonDataAbility onStart");
DatabaseHelper databaseHelper = new DatabaseHelper(this);
rdbStore = databaseHelper.getRdbStore(config, DB_VERSION, rdbOpenCallback, null);
}
6. 重写数据库操作方法
创建Data Ability时,HUAWEI DevEco Studio自动生成数据库增/删/改/查空方法,开发者按需重写相关方法即可。
query():查询方法。该方法接收三个参数,分别是查询的目标路径,查询的列名,以及查询条件,查询条件由类DataAbilityPredicates构建。根据传入的列名和查询条件查询数据库的示例代码如下:
@Override
public void onStart(Intent intent) {
super.onStart(intent);
HiLog.info(LABEL_LOG, "PersonDataAbility onStart");
DatabaseHelper databaseHelper = new DatabaseHelper(this);
rdbStore = databaseHelper.getRdbStore(config, DB_VERSION, rdbOpenCallback, null);
}
insert():新增方法。该方法接收两个参数,分别是插入的目标路径和插入的数据值。其中,插入的数据由ValuesBucket封装,服务端可以从该参数中解析出对应的属性,然后插入到数据库中。此方法返回一个int类型的值用于标识结果。接收到客户端传过来的人员信息并把它保存到数据库中的示例代码如下:
@Override
public void onStart(Intent intent) {
super.onStart(intent);
HiLog.info(LABEL_LOG, "PersonDataAbility onStart");
DatabaseHelper databaseHelper = new DatabaseHelper(this);
rdbStore = databaseHelper.getRdbStore(config, DB_VERSION, rdbOpenCallback, null);
}
说明:
当对多个表格进行操作时可根据path进行差异处理,且实际编码过程中还需要对操作异常进行处理,可捕获rdbStore.insert操作的异常,例如catch (DataAbilityRemoteException | IllegalStateException exception);
当表格数据插入成功时,可执行DataAbilityHelper.creator(this, uri).notifyChange(uri),通知该表格数据的订阅者。
delete():删除方法。该方法接收两个参数,分别是删除的目标路径和删除条件。删除条件由类DataAbilityPredicates构建,服务端在接收到该删除条件参数之后可以从中解析出要删除的数据,然后到数据库中执行。此方法返回一个int类型的值用于标识结果。根据传入的条件删除数据库数据的示例代码如下:
@Override
public void onStart(Intent intent) {
super.onStart(intent);
HiLog.info(LABEL_LOG, "PersonDataAbility onStart");
DatabaseHelper databaseHelper = new DatabaseHelper(this);
rdbStore = databaseHelper.getRdbStore(config, DB_VERSION, rdbOpenCallback, null);
}
update():更新方法。该方法接收三个参数,分别是更新的目标路径,更新的数据值以及更新条件。您可以在ValuesBucket参数中指定要更新的数据,在DataAbilityPredicates中构建更新的条件等。此方法返回一个int类型的值用于标识结果。更新person表数据的示例代码如下:
@Override
public void onStart(Intent intent) {
super.onStart(intent);
HiLog.info(LABEL_LOG, "PersonDataAbility onStart");
DatabaseHelper databaseHelper = new DatabaseHelper(this);
rdbStore = databaseHelper.getRdbStore(config, DB_VERSION, rdbOpenCallback, null);
}
另外,开发者还可以根据需求重写batchInsert()和executeBatch()方法来执行批量操作。
7. 访问Data Ability
开发者可以通过DataAbilityHelper类来访问当前应用或其他应用提供的共享数据。DataAbilityHelper作为客户端,与提供方的Data进行通信。Data接收到请求后,执行相应的处理(如上一个章节中重写的insert等方法),并返回结果。DataAbilityHelper提供了一系列与Data Ability通信的方法。下面介绍DataAbilityHelper具体的使用步骤。
声明使用权限
如果待访问的Data Ability声明了访问需要权限,则访问此Data Ability需要在工程配置文件config.json中声明需要此权限(权限声明请参考权限申请字段说明),如果待访问的Data Ability是由本应用创建,则可以不声明该权限。
"reqPermissions": [
{
"name": " com.huawei.codelab.DataAbilityShellProvider.PROVIDER "
}
]
创建DataAbilityHelper
DataAbilityHelper为开发者提供了creator()方法来创建DataAbilityHelper实例,该方法为静态方法,有多个重载。最常见的方法是通过传入一个context对象来创建DataAbilityHelper对象。
为了方便操作,直接在MainAbilitySlice中定义DataAbilityHelper变量,并在onStart()方法中创建其实例,示例代码如下:
public class MainAbilitySlice extends AbilitySlice {
private static final HiLogLabel LABEL_LOG = new HiLogLabel(3, 0xD001100, "MainAbilitySlice");
......
private DataAbilityHelper databaseHelper;
@Override
public void onStart(Intent intent) {
super.onStart(intent);
super.setUIContent(ResourceTable.Layout_ability_main);
databaseHelper = DataAbilityHelper.creator(this);
}
......
}
访问DataAbility
DataAbilityHelper为开发者提供了增、删、改、查以及批量处理等方法来操作数据库。同样的,为了方便操作,我们直接在MainAbilitySlice中定义相关的常量及数据库增、删、改、查方法,并通过系统提供的日志接口HiLog.info记录相关结果。
客户端访问数据库常量定义:
@Override
public void onStart(Intent intent) {
super.onStart(intent);
HiLog.info(LABEL_LOG, "PersonDataAbility onStart");
DatabaseHelper databaseHelper = new DatabaseHelper(this);
rdbStore = databaseHelper.getRdbStore(config, DB_VERSION, rdbOpenCallback, null);
}
说明:
因为本示例仅需要访问本地数据库,故BASE_URI为"dataability:///com.huawei.codelab.PersonDataAbility"。
query():查询方法,其中uri为目标资源路径,columns为想要查询的字段(即数据表格具体的列),开发者的查询条件可以通过DataAbilityPredicates来构建。开发者无需编写复杂的SQL语句,仅通过调用该类中条件相关的方法,如between、equalTo、notEqualTo、groupBy、orderByAsc、beginsWith等,就可自动完成SQL语句拼接,方便用户聚焦业务操作。根据年龄区间、名字及性别等查询条件,查询数据库的示例代码如下:
@Override
public void onStart(Intent intent) {
super.onStart(intent);
HiLog.info(LABEL_LOG, "PersonDataAbility onStart");
DatabaseHelper databaseHelper = new DatabaseHelper(this);
rdbStore = databaseHelper.getRdbStore(config, DB_VERSION, rdbOpenCallback, null);
}
insert():新增方法,其中uri为目标资源路径,ValuesBucket为要新增的对象。插入一条人员信息的示例代码如下:
@Override
public void onStart(Intent intent) {
super.onStart(intent);
HiLog.info(LABEL_LOG, "PersonDataAbility onStart");
DatabaseHelper databaseHelper = new DatabaseHelper(this);
rdbStore = databaseHelper.getRdbStore(config, DB_VERSION, rdbOpenCallback, null);
}
update():更新方法,更新数据ValuesBuckett传入,更新条件由DataAbilityPredicates来构建。例如,将id为102的表格数据,姓名修改为"ZhangSanPlus",年龄修改为28,示例代码如下:
@Override
public void onStart(Intent intent) {
super.onStart(intent);
HiLog.info(LABEL_LOG, "PersonDataAbility onStart");
DatabaseHelper databaseHelper = new DatabaseHelper(this);
rdbStore = databaseHelper.getRdbStore(config, DB_VERSION, rdbOpenCallback, null);
}
delete():删除方法,删除条件由DataAbilityPredicates来构建。例如,删除id为100的数据项,示例代码如下:
@Override
public void onStart(Intent intent) {
super.onStart(intent);
HiLog.info(LABEL_LOG, "PersonDataAbility onStart");
DatabaseHelper databaseHelper = new DatabaseHelper(this);
rdbStore = databaseHelper.getRdbStore(config, DB_VERSION, rdbOpenCallback, null);
}
另外,DataAbilityHelper为开发者提供了批量插入batchInsert、批量操作executeBatch方法,具体的方法使用可以参考访问Data。
8. 订阅数据变化
通常情况下,当数据库表格的内容产生变化时,需要主动通知与该表格数据相关联的进程或者应用,从而使得相关进程或者应用接收到数据变化后完成相应的处理。如前所述,对于数据提供方,当数据库表格内容变化,可以通过如下方法通知数据订阅者:
@Override
insert/delete/update() { // 为了方便,此处将插入、删除、更新方法写在一起
......
DataAbilityHelper.creator(this, uri).notifyChange(uri);
......
}
对于数据接收方,可以通过DataAbilityHelper提供的IDataAbilityObserver方法注册一个数据订阅者,并重写onChange()方法即可。订阅者接收person变化的示例代码如下:
IDataAbilityObserver dataAbilityObserver;
private void personDatabaseObserver() {
dataAbilityObserver = new IDataAbilityObserver() {
@Override
public void onChange() {
// 订阅者接收目标数据表格产生变化的通知,通过查询获取最新的数据
query();
}
};
// 根据uri指定订阅的数据表
databaseHelper.registerObserver(Uri.parse(BASE_URI + DATA_PATH), dataAbilityObserver);
}
当数据订阅者不再需要订阅Data变化时,则调用unregisterObserver(Uri uri, IDataAbilityObserver dataObserver)方法取消。
9. 测试支持
为了快速验证结果,我们在MainAbilitySlice定义了query()、insert()、update()、delete()方法,并在onStart()中调用相关方法,其示例代码如下:
@Override
public void onStart(Intent intent) {
super.onStart(intent);
super.setUIContent(ResourceTable.Layout_ability_main);
databaseHelper = DataAbilityHelper.creator(this);
query();
insert(100, "Tom", "male", 20);
insert(101, "Jerry", "female", 21);
insert(102, "Bob", "male", 22);
query(); // 查看插入后的结果
update();
query(); // 查看更新后的结果
delete();
query(); // 查看删除后的结果
}
连接手机或者模拟器,运行该工程,通过HUAWEI DevEco Studio HiLog工具查看结果,显示如下:
10. 完整示例
本示例工程主要基于Data Ability创建数据库服务并对外提供访问数据库的服务端接口(类PersonDataAbility),并在MainAbilitySlice中通过DataAbilityHelper客户端,与提供方的Data进行通信,最后通过日志查看数据库操作结果。完整示例代码如下:
类PersonDataAbility(参考第2小节,通过HUAWEI DevEco Studio添加Empty Data Ability):
public class PersonDataAbility extends Ability {
private static final HiLogLabel LABEL_LOG = new HiLogLabel(3, 0xD001100, "Demo");
private static final String DB_NAME = "persondataability.db";
private static final String DB_TAB_NAME = "person";
private static final String DB_COLUMN_PERSON_ID = "id";
private static final String DB_COLUMN_NAME = "name";
private static final String DB_COLUMN_GENDER = "gender";
private static final String DB_COLUMN_AGE = "age";
private static final int DB_VERSION = 1;
private StoreConfig config = StoreConfig.newDefaultConfig(DB_NAME);
private RdbStore rdbStore;
private RdbOpenCallback rdbOpenCallback = new RdbOpenCallback() {
@Override
public void onCreate(RdbStore store) {
store.executeSql("create table if not exists "
+ DB_TAB_NAME + " ("
+ DB_COLUMN_PERSON_ID + " integer primary key, "
+ DB_COLUMN_NAME + " text not null, "
+ DB_COLUMN_GENDER + " text not null, "
+ DB_COLUMN_AGE + " integer)");
}
@Override
public void onUpgrade(RdbStore store, int oldVersion, int newVersion) {
}
};
@Override
public void onStart(Intent intent) {
super.onStart(intent);
HiLog.info(LABEL_LOG, "PersonDataAbility onStart");
DatabaseHelper databaseHelper = new DatabaseHelper(this);
rdbStore = databaseHelper.getRdbStore(config, DB_VERSION, rdbOpenCallback, null);
}
@Override
public ResultSet query(Uri uri, String[] columns, DataAbilityPredicates predicates) {
RdbPredicates rdbPredicates = DataAbilityUtils.createRdbPredicates(predicates, DB_TAB_NAME);
ResultSet resultSet = rdbStore.query(rdbPredicates, columns);
if (resultSet == null) {
HiLog.info(LABEL_LOG, "resultSet is null");
}
return resultSet;
}
@Override
public int insert(Uri uri, ValuesBucket value) {
HiLog.info(LABEL_LOG, "PersonDataAbility insert");
String path = uri.getLastPath();
if (!"person".equals(path)) {
HiLog.info(LABEL_LOG, "DataAbility insert path is not matched");
return -1;
}
ValuesBucket values = new ValuesBucket();
values.putInteger(DB_COLUMN_PERSON_ID, value.getInteger(DB_COLUMN_PERSON_ID));
values.putString(DB_COLUMN_NAME, value.getString(DB_COLUMN_NAME));
values.putString(DB_COLUMN_GENDER, value.getString(DB_COLUMN_GENDER));
values.putInteger(DB_COLUMN_AGE, value.getInteger(DB_COLUMN_AGE));
int index = (int) rdbStore.insert(DB_TAB_NAME, values);
DataAbilityHelper.creator(this, uri).notifyChange(uri);
return index;
}
@Override
public int delete(Uri uri, DataAbilityPredicates predicates) {
RdbPredicates rdbPredicates = DataAbilityUtils.createRdbPredicates(predicates, DB_TAB_NAME);
int index = rdbStore.delete(rdbPredicates);
HiLog.info(LABEL_LOG, "delete: " + index);
DataAbilityHelper.creator(this, uri).notifyChange(uri);
return index;
}
@Override
public int update(Uri uri, ValuesBucket value, DataAbilityPredicates predicates) {
RdbPredicates rdbPredicates = DataAbilityUtils.createRdbPredicates(predicates, DB_TAB_NAME);
int index = rdbStore.update(value, rdbPredicates);
HiLog.info(LABEL_LOG, "update: " + index);
DataAbilityHelper.creator(this, uri).notifyChange(uri);
return index;
}
@Override
public FileDescriptor openFile(Uri uri, String mode) {
return null;
}
@Override
public String[] getFileTypes(Uri uri, String mimeTypeFilter) {
return new String[0];
}
@Override
public PacMap call(String method, String arg, PacMap extras) {
return null;
}
@Override
public String getType(Uri uri) {
return null;
}
}
说明:
以上代码仅demo演示参考使用。
MainAbilitySlice:
public class MainAbilitySlice extends AbilitySlice {
private static final HiLogLabel LABEL_LOG = new HiLogLabel(3, 0xD001100, "MainAbilitySlice");
private static final String BASE_URI = "dataability:///com.huawei.codelab.PersonDataAbility";
private static final String DATA_PATH = "/person";
private static final String DB_COLUMN_PERSON_ID = "id";
private static final String DB_COLUMN_NAME = "name";
private static final String DB_COLUMN_GENDER = "gender";
private static final String DB_COLUMN_AGE = "age";
private DataAbilityHelper databaseHelper;
@Override
public void onStart(Intent intent) {
super.onStart(intent);
super.setUIContent(ResourceTable.Layout_ability_main);
databaseHelper = DataAbilityHelper.creator(this);
query();
insert(100, "Tom", "male", 20);
insert(101, "Jerry", "female", 21);
insert(102, "Bob", "male", 22);
query(); // 查看插入后的结果
update();
query(); // 查询更新后的结果
delete();
query(); // 查看删除后的结果
}
@Override
public void onActive() {
super.onActive();
}
@Override
public void onForeground(Intent intent) {
super.onForeground(intent);
}
private void query() {
String[] columns = new String[] {DB_COLUMN_PERSON_ID,
DB_COLUMN_NAME, DB_COLUMN_GENDER, DB_COLUMN_AGE};
// 构造查询条件
DataAbilityPredicates predicates = new DataAbilityPredicates();
predicates.between(DB_COLUMN_AGE, 15, 40);
try {
ResultSet resultSet = databaseHelper.query(Uri.parse(BASE_URI + DATA_PATH),
columns, predicates);
if (resultSet == null || resultSet.getRowCount() == 0) {
HiLog.info(LABEL_LOG, "query: resultSet is null or no result found");
return;
}
resultSet.goToFirstRow();
do {
int id = resultSet.getInt(resultSet.getColumnIndexForName(DB_COLUMN_PERSON_ID));
String name = resultSet.getString(resultSet.getColumnIndexForName(DB_COLUMN_NAME));
String gender = resultSet.getString(resultSet.getColumnIndexForName(DB_COLUMN_GENDER));
int age = resultSet.getInt(resultSet.getColumnIndexForName(DB_COLUMN_AGE));
HiLog.info(LABEL_LOG, "query: Id :" + id + " Name :" + name + " Gender :" + gender + " Age :" + age);
} while (resultSet.goToNextRow());
} catch (DataAbilityRemoteException | IllegalStateException exception) {
HiLog.error(LABEL_LOG, "query: dataRemote exception | illegalStateException");
}
}
private void insert(int id, String name, String gender, int age) {
ValuesBucket valuesBucket = new ValuesBucket();
valuesBucket.putInteger(DB_COLUMN_PERSON_ID, id);
valuesBucket.putString(DB_COLUMN_NAME, name);
valuesBucket.putString(DB_COLUMN_GENDER, gender);
valuesBucket.putInteger(DB_COLUMN_AGE, age);
try {
if (databaseHelper.insert(Uri.parse(BASE_URI + DATA_PATH), valuesBucket) != -1) {
HiLog.info(LABEL_LOG, "insert successful");
}
} catch (DataAbilityRemoteException | IllegalStateException exception) {
HiLog.error(LABEL_LOG, "insert: dataRemote exception|illegalStateException");
}
}
private void update() {
DataAbilityPredicates predicates = new DataAbilityPredicates();
predicates.equalTo(DB_COLUMN_PERSON_ID, 102);
ValuesBucket valuesBucket = new ValuesBucket();
valuesBucket.putString(DB_COLUMN_NAME, "ZhangSanPlus");
valuesBucket.putInteger(DB_COLUMN_AGE, 28);
try {
if (databaseHelper.update(Uri.parse(BASE_URI + DATA_PATH), valuesBucket, predicates) != -1) {
HiLog.info(LABEL_LOG, "update successful");
}
} catch (DataAbilityRemoteException | IllegalStateException exception) {
HiLog.error(LABEL_LOG, "update: dataRemote exception | illegalStateException");
}
}
private void delete() {
DataAbilityPredicates predicates = new DataAbilityPredicates()
.equalTo(DB_COLUMN_PERSON_ID, 100);
try {
if (databaseHelper.delete(Uri.parse(BASE_URI + DATA_PATH), predicates) != -1) {
HiLog.info(LABEL_LOG, "delete successful");
}
} catch (DataAbilityRemoteException | IllegalStateException exception) {
HiLog.error(LABEL_LOG, "delete: dataRemote exception | illegalStateException");
}
}
}
说明:
以上代码仅demo演示参考使用。
恭喜您
您已经成功的学习了如何通过Data Ability创建关系型数据库,并提供Data端数据库表格数据的增、删、改、查API接口,然后通过创建DataAbilityHelper实例作为客户端访问数据库的数据。
按照以上例子,我的工程是 com.example.myapplicationzzq2,没有出现期盼的运行显示效果,出现SEVERE java.lang.UnsupportedOperationException。请问原文中“com.huawei.codelab”,需要具体替换吗?
看不到HⅰL0g的显示信息?