• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# 在TaskPool线程中操作关系型数据库实现案例
2
3### 介绍
4
5本示例通过通讯录场景实例进行讲解,介绍了在 TaskPool 线程中操作关系型数据库的方法,涵盖了单条插入(新增联系人)、批量插入(通讯录同步)、删除(删除联系人)、修改(更新联系人信息)和查询等基本操作。
6
7### 效果图预览
8
9![](./casesfeature/operaterdbintaskpool/operate_rdb_in_taskpool.gif)
10
11**使用说明**
12
131. 进入页面,出现“点击同步通讯录数据”按钮。点击按钮后,将本地 JSON 数据分批插入数据库中。
14
152. 同步完成后,页面显示通讯录列表。点击列表项进入联系人的详情页面,点击加号按钮进入新增联系人页面。
16
173. 在详情页面,可以对联系人信息进行修改和删除操作。
18
19### 实现思路
20
211. **首先,构建一个关系型数据库并封装数据库操作方法涉及几个关键步骤。**
22
23- 通过getRdbStore方法初始化一个关系型数据库,用户可以根据STORE_CONFIG配置RdbStore的参数,使用Promise异步回调。
24  ```javascript
25  // 初始化数据库
26  public async initRdbStore(context: common.Context): Promise<void> {
27    this.rdbStore = await rdb.getRdbStore(context, STORE_CONFIG);
28    await this.createTable();
29  }
30  ```
31- 使用executeSql接口初始化数据库表结构和相关数据。
32  ```javascript
33  // 创建数据库表
34    private async createTable(): Promise<void> {
35    await this.rdbStore.executeSql(SQL_CREATE_TABLE);
36  }
37  ```
38- 封装数据库操作方法分别为数据插入、数据删除、数据查询和数据查询。
39  ```javascript
40  // 单条数据插入数据库
41  public async insertData(context: common.Context, contact: Contact): Promise<void> {
42    let value1 = contact.name;
43    let value2 = contact.phone;
44    let value3 = contact.email;
45    let value4 = contact.address;
46    let value5 = contact.avatar;
47    let value6 = contact.category;
48
49    const valueBucket: ValuesBucket = {
50      'name': value1,
51      'phone': value2,
52      'email': value3,
53      'address': value4,
54      'avatar': value5,
55      'category': value6
56    }
57    if (this.rdbStore != undefined) {
58      let ret = await this.rdbStore.insert(TABLE_NAME, valueBucket, rdb.ConflictResolution.ON_CONFLICT_REPLACE);
59    }
60  }
61
62  // 批量插入数据库
63  public async batchInsertData(context: common.Context, array: Array<Contact>): Promise<void> {
64    let valueBuckets: ValuesBucket[] = [];
65    for (let index = 0; index < array.length; index++) {
66      let contactItem = array[index] as Contact;
67      let value1 = contactItem.name;
68      let value2 = contactItem.phone;
69      let value3 = contactItem.email;
70      let value4 = contactItem.address;
71      let value5 = contactItem.avatar;
72      let value6 = contactItem.category;
73
74      const valueBucket: ValuesBucket = {
75        'name': value1,
76        'phone': value2,
77        'email': value3,
78        'address': value4,
79        'avatar': value5,
80        'category': value6
81      }
82      valueBuckets.push(valueBucket);
83    }
84    if (this.rdbStore != undefined) {
85      let ret = await this.rdbStore.batchInsert(TABLE_NAME, valueBuckets);
86    }
87  }
88
89  // 删除操作
90  public async deleteData(context: common.Context, contact: Contact): Promise<void> {
91    this.rdbStore = await rdb.getRdbStore(context, STORE_CONFIG);
92
93    predicates.or().equalTo('id', contact.id);
94    this.rdbStore.delete(predicates, (err: BusinessError, row: number) => {
95      if (err) {
96        logger.info(TAG, 'delete failed, err: ' + err);
97        return;
98      }
99      logger.info(TAG, `delete contact success:${row}`);
100      promptAction.showToast({
101        message: $r('app.string.operate_rdb_in_taskpool_delete_prompt_text', contact.name),
102        duration: CommonConstants.PROMPT_DURATION_TIME
103      });
104    });
105
106  }
107
108  // 更新数据库
109  public async updateData(context: common.Context, contact: Contact): Promise<void> {
110    logger.info(TAG, 'update begin');
111    if (!context) {
112      logger.info(TAG, 'context is null or undefined');
113    }
114
115    const predicates = new rdb.RdbPredicates(TABLE_NAME);
116    if (predicates === null || predicates === undefined) {
117      logger.info(TAG, 'predicates is null or undefined');
118    }
119
120    if (!this.rdbStore) {
121      logger.info(TAG, 'update rdbStore is null');
122      await this.initRdbStore(context);
123    }
124    let value1 = contact.name;
125    let value2 = contact.phone;
126    let value3 = contact.email;
127    let value4 = contact.address;
128    let value5 = contact.avatar;
129    let value6 = contact.category;
130
131    const valueBucket: ValuesBucket = {
132      'name': value1,
133      'phone': value2,
134      'email': value3,
135      'address': value4,
136      'avatar': value5,
137      'category': value6
138    }
139
140    predicates.equalTo('id', Contact.id);
141
142    if (this.rdbStore != undefined) {
143      this.rdbStore.update(valueBucket, predicates, rdb.ConflictResolution.ON_CONFLICT_REPLACE,
144        (err: BusinessError, row: number) => {
145          if (err) {
146            logger.info(TAG, "updated failed, err: " + err)
147            return
148          }
149          logger.info(TAG, `update done:${row}`);
150          promptAction.showToast({
151            message: $r('app.string.operate_rdb_in_taskpool_update_prompt_text', contact.name),
152            duration: CommonConstants.PROMPT_DURATION_TIME
153          });
154        })
155    }
156  }
157
158  // 查询数据库
159  public async query(context: common.Context): Promise<Array<Contact>> {
160    if (!context) {
161      logger.info(TAG, 'context is null or undefined');
162      return [];
163    }
164
165    let predicates = new rdb.RdbPredicates(TABLE_NAME);
166    predicates.orderByAsc('category')
167    if (predicates === null || predicates === undefined) {
168      logger.info(TAG, 'predicates is null or undefined');
169      return [];
170    }
171
172    try {
173      this.rdbStore = await rdb.getRdbStore(context, STORE_CONFIG);
174      const resultSet: rdb.ResultSet =
175        await this.rdbStore.query(predicates);
176      logger.info(TAG, 'result is ' + JSON.stringify(resultSet.rowCount));
177      // 处理查询到的结果数组
178      return this.getListFromResultSet(resultSet);
179    } catch (err) {
180      logger.error(TAG, 'query result error:' + JSON.stringify(err));
181      return [];
182    }
183  }
184  ```
185
1862. **创建任务池(taskpool)为数据库操作提供一个多线程的运行环境,将创建好的任务(新增、删除、修改、查询操作)放入taskpool内部任务队列,在子线程中实现数据库增删改查的任务,以此防止阻塞主线程。执行完成后,将结果回调至主线程,从而在主线程中更新数据源和用户界面。这样做不仅提升了应用的响应速度,还确保了用户交互的流畅性。以下代码以查询为例:(注:任务不会立即执行,而是等待分发到工作线程执行。)**[源码参考](casesfeature/operaterdbintaskpool/src/main/ets/view/TaskPool.ets)
187
188  ```javascript
189  // queryItem函数调用 需使用装饰器@Concurrent
190  @Concurrent
191  async function queryItem(context: common.Context): Promise<Array<Contact>> {
192    return await DatabaseConnection.getInstance().query(context);
193  }
194
195  export async function taskPoolExecuteQuery(context: common.Context): Promise<Array<Contact>> {
196    try {
197      let task: taskPool.Task = new taskPool.Task(queryItem, context); // queryItem函数调用 需使用装饰器@Concurrent
198      let result: Contact[] = await taskPool.execute(task) as Contact[];
199      return result;
200    } catch (err) {
201      logger.error(TAG, 'query error:' + JSON.stringify(err));
202      return [];
203    }
204  }
205  ```
206
2073. **在taskpool线程中操作关系型数据库方法的调用,将结果回调至主线程,在回调中来操作数据源。**[源码参考](casesfeature/operaterdbintaskpool/src/main/ets/view/AddressBookEdit.ets)
208
209    ```javascript
210    // 单条数据插入操作
211    taskPoolExecuteInsert(context, this.result).then(() => {
212        DynamicsRouter.popAppRouter();
213        // 数据库插入成功后 操作列表数据源回调
214        this.addCallback(this.result);
215    });
216
217    // 数据删除操作
218    taskPoolExecuteDelete(context, this.contact).then(() => {
219        if (this.sourceData) {
220            // 数据库删除成功后 操作列表数据源
221            DynamicsRouter.popAppRouter();
222            this.deleteCallback(this.sourceData);
223        }
224
225    // 更新数据操作
226    taskPoolExecuteUpdate(context, this.result).then(() => {
227        DynamicsRouter.popAppRouter();
228        // 数据库更新成功后 操作列表数据源回调
229        this.editCallback(this.result);
230    });
231
232    // 数据查询操作
233    queryRDB() {
234        taskPoolExecuteQuery(context).then((contact: Array<Contact>) => {
235            this.dataArray = contact.reduce((accumulator, item) => {
236                // 如果类别不存在,则创建一个新的数组
237                if (!accumulator[item.category]) {
238                    accumulator[item.category] = [];
239                }
240                // 将当前项添加到相应类别的数组中
241                accumulator[item.category].push(item);
242                return accumulator;
243            }, {} as Record<string, Contact[]>);
244
245            // 清空类别数组
246            this.categoryArray = [];
247
248            // 使用 Object.entries() 遍历键值对
249            Object.entries(this.dataArray).forEach(data => {
250                let categoryContact: CategoryContact = { category: data[0], itemsContact: data[1] }
251                this.categoryArray.push(data[0]);
252                this.sourceArray.pushData(categoryContact);
253            });
254        });
255    }
256    ```
257
258### 高性能知识点
259
260本示例使用了[LazyForEach](https://docs.openharmony.cn/pages/v5.0/zh-cn/application-dev/reference/apis-arkui/arkui-ts/ts-rendering-control-lazyforeach.md)
261进行数据懒加载,LazyForEach懒加载可以通过设置cachedCount属性来指定缓存数量,同时搭配[组件复用](https://docs.openharmony.cn/pages/v5.0/zh-cn/application-dev/performance/component-recycle.md)
262能力以达到性能更好的效果。
263
264### 工程结构&模块类型
265
266```
267operaterdbintaskpool                             // har类型
268|---constant
269|   |---CommonConstant.ets                       // 常量
270|   |---RdbConstant.ets                          // Rdb常量
271|---model
272|   |---Contact.ets                              // Contact数据结构
273|   |---DataSource.ets                           // 解析JSON数据
274|   |---DataSource.ets                           // 列表数据模型
275|---view
276|   |---AddressBookDetail.ets                    // 通讯录详情页
277|   |---AddressBookEdit.ets                      // 通讯录编辑和新增页
278|   |---AddressBookList.ets                      // 通讯录列表页
279|   |---DatabaseConnection.ets                   // 数据库相关操作
280|   |---OpetateRDBTaskPool.ets                   // 主页面
281|   |---TaskPool.ets                             // TaskPool线程
282```
283
284### 参考资料
285
286[AlphabetIndexer](https://docs.openharmony.cn/pages/v5.0/zh-cn/application-dev/reference/apis-arkui/arkui-ts/ts-container-alphabet-indexer.md)
287
288[@ohos.i18n (国际化-I18n)(系统接口)](https://docs.openharmony.cn/pages/v5.0/zh-cn/application-dev/reference/apis-localization-kit/js-apis-i18n-sys.md)
289
290[@ohos.taskpool(启动任务池)](https://docs.openharmony.cn/pages/v5.0/zh-cn/application-dev/reference/apis-arkts/js-apis-taskpool.md)
291
292[@ohos.data.relationalStore(关系型数据库)](https://docs.openharmony.cn/pages/v5.0/zh-cn/application-dev/reference/apis-arkdata/js-apis-data-relationalStore.md)
293
294[多线程能力场景化示例实践](https://docs.openharmony.cn/pages/v5.0/zh-cn/application-dev/performance/multi_thread_capability.md#%E5%9C%A8taskpool%E7%BA%BF%E7%A8%8B%E6%93%8D%E4%BD%9C%E5%85%B3%E7%B3%BB%E5%9E%8B%E6%95%B0%E6%8D%AE%E5%BA%93)
295
296### 相关权限
297
298不涉及。
299
300### 依赖
301
302不涉及。
303
304### 约束与限制
305
3061.本示例仅支持标准系统上运行。
307
3082.本示例为Stage模型,支持API12版本SDK,SDK版本号(API Version 12 Release)。
309
3103.本示例需要使用DevEco Studio版本号(DevEco Studio 5.0.0 Release)及以上版本才可编译运行。
311
312### 下载
313
314如需单独下载本工程,执行如下命令:
315
316```shell
317git init
318git config core.sparsecheckout true
319echo code/Performance/OperateRDBInTaskPool/ > .git/info/sparse-checkout
320git remote add origin https://gitee.com/openharmony/applications_app_samples.git
321git pull origin master
322```
323
324