1# 在TaskPool线程中操作关系型数据库实现案例 2 3### 介绍 4 5本示例通过通讯录场景实例进行讲解,介绍了在 TaskPool 线程中操作关系型数据库的方法,涵盖了单条插入(新增联系人)、批量插入(通讯录同步)、删除(删除联系人)、修改(更新联系人信息)和查询等基本操作。 6 7### 效果图预览 8 9 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