1# Batch Database Operations 2 3## Using TaskPool for Frequent Database Operations 4 5When dealing with scenarios that require frequent database operations, the time-consuming nature of reading from and writing to the database can lead to UI thread blockages. To mitigate this, you are advised to offload these operations to background threads. 6 7By leveraging the TaskPool capabilities provided by ArkTS, database operations can be efficiently moved to background threads. The implementation involves the following steps: 8 91. Create multiple tasks to support various database operations such as creation, insertion, querying, and clearing. 10 112. The UI main thread calls these tasks to perform database operations such as adding, deleting, modifying, and querying data. 12 13```ts 14// Index.ets 15import { relationalStore, ValuesBucket } from '@kit.ArkData'; 16import { taskpool } from '@kit.ArkTS'; 17 18@Concurrent 19async function create(context: Context) { 20 const CONFIG: relationalStore.StoreConfig = { 21 name: "Store.db", 22 securityLevel: relationalStore.SecurityLevel.S1, 23 }; 24 25 // The default database file path is context.databaseDir + rdb + StoreConfig.name. 26 let store: relationalStore.RdbStore = await relationalStore.getRdbStore(context, CONFIG); 27 console.info(`Create Store.db successfully!`); 28 29 // Create a table. 30 const CREATE_TABLE_SQL = "CREATE TABLE IF NOT EXISTS test (" + 31 "id INTEGER PRIMARY KEY AUTOINCREMENT, " + 32 "name TEXT NOT NULL, " + 33 "age INTEGER, " + 34 "salary REAL, " + 35 "blobType BLOB)"; 36 await store.executeSql(CREATE_TABLE_SQL); 37 console.info(`Create table test successfully!`); 38} 39 40@Concurrent 41async function insert(context: Context, valueBucketArray: Array<relationalStore.ValuesBucket>) { 42 const CONFIG: relationalStore.StoreConfig = { 43 name: "Store.db", 44 securityLevel: relationalStore.SecurityLevel.S1, 45 }; 46 47 // The default database file path is context.databaseDir + rdb + StoreConfig.name. 48 let store: relationalStore.RdbStore = await relationalStore.getRdbStore(context, CONFIG); 49 console.info(`Create Store.db successfully!`); 50 51 // Insert data. 52 await store.batchInsert("test", valueBucketArray as Object as Array<relationalStore.ValuesBucket>); 53} 54 55@Concurrent 56async function query(context: Context): Promise<Array<relationalStore.ValuesBucket>> { 57 const CONFIG: relationalStore.StoreConfig = { 58 name: "Store.db", 59 securityLevel: relationalStore.SecurityLevel.S1, 60 }; 61 62 // The default database file path is context.databaseDir + rdb + StoreConfig.name. 63 let store: relationalStore.RdbStore = await relationalStore.getRdbStore(context, CONFIG); 64 console.info(`Create Store.db successfully!`); 65 66 // Obtain the result set. 67 let predicates: relationalStore.RdbPredicates = new relationalStore.RdbPredicates("test"); 68 let resultSet = await store.query(predicates); // Query all data. 69 console.info(`Query data successfully! row count:${resultSet.rowCount}`); 70 let index = 0; 71 let result = new Array<relationalStore.ValuesBucket>(resultSet.rowCount) 72 resultSet.goToFirstRow() 73 do { 74 result[index++] = resultSet.getRow() 75 } while (resultSet.goToNextRow()); 76 resultSet.close(); 77 return result 78} 79 80@Concurrent 81async function clear(context: Context) { 82 const CONFIG: relationalStore.StoreConfig = { 83 name: "Store.db", 84 securityLevel: relationalStore.SecurityLevel.S1, 85 }; 86 87 // The default database file path is context.databaseDir + rdb + StoreConfig.name. 88 await relationalStore.deleteRdbStore(context, CONFIG); 89 console.info(`Delete Store.db successfully!`); 90} 91 92@Entry 93@Component 94struct Index { 95 @State message: string = 'Hello World'; 96 97 build() { 98 RelativeContainer() { 99 Text(this.message) 100 .id('HelloWorld') 101 .fontSize(50) 102 .fontWeight(FontWeight.Bold) 103 .alignRules({ 104 center: { anchor: '__container__', align: VerticalAlign.Center }, 105 middle: { anchor: '__container__', align: HorizontalAlign.Center } 106 }) 107 .onClick(async () => { 108 let context : Context = this.getUIContext().getHostContext() as Context; 109 110 // Prepare data. 111 const count = 5 112 let valueBucketArray = new Array<relationalStore.ValuesBucket>(count); 113 for (let i = 0; i < count; i++) { 114 let v : relationalStore.ValuesBucket = { 115 id: i, 116 name: "zhangsan" + i, 117 age: 20, 118 salary: 5000 + 50 * i 119 }; 120 valueBucketArray[i] = v; 121 } 122 await taskpool.execute(create, context) 123 await taskpool.execute(insert, context, valueBucketArray) 124 let index = 0 125 let ret = await taskpool.execute(query, context) as Array<relationalStore.ValuesBucket> 126 for (let v of ret) { 127 console.info(`Row[${index}].id = ${v.id}`) 128 console.info(`Row[${index}].name = ${v.name}`) 129 console.info(`Row[${index}].age = ${v.age}`) 130 console.info(`Row[${index}].salary = ${v.salary}`) 131 index++ 132 } 133 await taskpool.execute(clear, context) 134 }) 135 } 136 .height('100%') 137 .width('100%') 138 } 139} 140``` 141<!-- @[taskpool_frequently_operate_database](https://gitee.com/openharmony/applications_app_samples/blob/master/code/DocsSample/ArkTS/ArkTsConcurrent/ApplicationMultithreadingDevelopment/PracticalCases/entry/src/main/ets/managers/UsingSendable.ets) --> 142 143## Using Sendable for Large-Scale Database Operations 144 145When handling large volumes of database data, cross-thread data transfer may still block the UI main thread due to its time-consuming nature. To address this, you are advised to use Sendable to encapsulate database data, thereby reducing cross-thread overhead. 146 1471. Define the data format in the database. Sendable can be used to minimize cross-thread latency. 148 149 ```ts 150 // SharedValuesBucket.ets 151 export interface IValueBucket { 152 id: number 153 name: string 154 age: number 155 salary: number 156 } 157 158 @Sendable 159 export class SharedValuesBucket implements IValueBucket { 160 id: number = 0 161 name: string = "" 162 age: number = 0 163 salary: number = 0 164 165 constructor(v: IValueBucket) { 166 this.id = v.id; 167 this.name = v.name; 168 this.age = v.age; 169 this.salary = v.salary 170 } 171 } 172 ``` 173 <!-- @[define_data_format](https://gitee.com/openharmony/applications_app_samples/blob/master/code/DocsSample/ArkTS/ArkTsConcurrent/ApplicationMultithreadingDevelopment/PracticalCases/entry/src/main/ets/managers/SharedValuesBucket.ets) --> 174 1752. Initiate from the UI main thread and perform Create, Read, Update, Delete (CRUD) operations in the background thread. 176 177 ```ts 178 // Index.ets 179 import { relationalStore, ValuesBucket } from '@kit.ArkData'; 180 import { collections, taskpool } from '@kit.ArkTS'; 181 import { IValueBucket, SharedValuesBucket } from './SharedValuesBucket'; 182 183 @Concurrent 184 async function create(context: Context) { 185 const CONFIG: relationalStore.StoreConfig = { 186 name: "Store.db", 187 securityLevel: relationalStore.SecurityLevel.S1, 188 }; 189 190 // The default database file path is context.databaseDir + rdb + StoreConfig.name. 191 let store: relationalStore.RdbStore = await relationalStore.getRdbStore(context, CONFIG); 192 console.info(`Create Store.db successfully!`); 193 194 // Create a table. 195 const CREATE_TABLE_SQL = "CREATE TABLE IF NOT EXISTS test (" + 196 "id INTEGER PRIMARY KEY AUTOINCREMENT, " + 197 "name TEXT NOT NULL, " + 198 "age INTEGER, " + 199 "salary REAL, " + 200 "blobType BLOB)"; 201 await store.executeSql(CREATE_TABLE_SQL); 202 console.info(`Create table test successfully!`); 203 } 204 205 @Concurrent 206 async function insert(context: Context, valueBucketArray: collections.Array<SharedValuesBucket | undefined>) { 207 const CONFIG: relationalStore.StoreConfig = { 208 name: "Store.db", 209 securityLevel: relationalStore.SecurityLevel.S1, 210 }; 211 212 // The default database file path is context.databaseDir + rdb + StoreConfig.name. 213 let store: relationalStore.RdbStore = await relationalStore.getRdbStore(context, CONFIG); 214 console.info(`Create Store.db successfully!`); 215 216 // Insert data. 217 await store.batchInsert("test", valueBucketArray as Object as Array<ValuesBucket>); 218 } 219 220 @Concurrent 221 async function query(context: Context): Promise<collections.Array<SharedValuesBucket | undefined>> { 222 const CONFIG: relationalStore.StoreConfig = { 223 name: "Store.db", 224 securityLevel: relationalStore.SecurityLevel.S1, 225 }; 226 227 // The default database file path is context.databaseDir + rdb + StoreConfig.name. 228 let store: relationalStore.RdbStore = await relationalStore.getRdbStore(context, CONFIG); 229 console.info(`Create Store.db successfully!`); 230 231 // Obtain the result set. 232 let predicates: relationalStore.RdbPredicates = new relationalStore.RdbPredicates("test"); 233 let resultSet = await store.query(predicates); // Query all data. 234 console.info(`Query data successfully! row count:${resultSet.rowCount}`); 235 let index = 0; 236 let result = collections.Array.create<SharedValuesBucket | undefined>(resultSet.rowCount, undefined) 237 resultSet.goToFirstRow() 238 do { 239 let v: IValueBucket = { 240 id: resultSet.getLong(resultSet.getColumnIndex("id")), 241 name: resultSet.getString(resultSet.getColumnIndex("name")), 242 age: resultSet.getLong(resultSet.getColumnIndex("age")), 243 salary: resultSet.getLong(resultSet.getColumnIndex("salary")) 244 }; 245 result[index++] = new SharedValuesBucket(v) 246 } while (resultSet.goToNextRow()); 247 resultSet.close(); 248 return result 249 } 250 251 @Concurrent 252 async function clear(context: Context) { 253 const CONFIG: relationalStore.StoreConfig = { 254 name: "Store.db", 255 securityLevel: relationalStore.SecurityLevel.S1, 256 }; 257 258 // The default database file path is context.databaseDir + rdb + StoreConfig.name. 259 await relationalStore.deleteRdbStore(context, CONFIG); 260 console.info(`Delete Store.db successfully!`); 261 } 262 263 @Entry 264 @Component 265 struct Index { 266 @State message: string = 'Hello World'; 267 268 build() { 269 RelativeContainer() { 270 Text(this.message) 271 .id('HelloWorld') 272 .fontSize(50) 273 .fontWeight(FontWeight.Bold) 274 .alignRules({ 275 center: { anchor: '__container__', align: VerticalAlign.Center }, 276 middle: { anchor: '__container__', align: HorizontalAlign.Center } 277 }) 278 .onClick(async () => { 279 let context : Context = this.getUIContext().getHostContext() as Context; 280 281 // Prepare data. 282 const count = 5 283 let valueBucketArray = collections.Array.create<SharedValuesBucket | undefined>(count, undefined); 284 for (let i = 0; i < count; i++) { 285 let v: IValueBucket = { 286 id: i, 287 name: "zhangsan" + i, 288 age: 20, 289 salary: 5000 + 50 * i 290 }; 291 valueBucketArray[i] = new SharedValuesBucket(v); 292 } 293 await taskpool.execute(create, context) 294 await taskpool.execute(insert, context, valueBucketArray) 295 let index = 0 296 let ret: collections.Array<SharedValuesBucket> = 297 await taskpool.execute(query, context) as collections.Array<SharedValuesBucket> 298 for (let v of ret.values()) { 299 console.info(`Row[${index}].id = ${v.id}`) 300 console.info(`Row[${index}].name = ${v.name}`) 301 console.info(`Row[${index}].age = ${v.age}`) 302 console.info(`Row[${index}].salary = ${v.salary}`) 303 index++ 304 } 305 await taskpool.execute(clear, context) 306 }) 307 } 308 .height('100%') 309 .width('100%') 310 } 311 } 312 ``` 313 <!-- @[operate_child_thread_data](https://gitee.com/openharmony/applications_app_samples/blob/master/code/DocsSample/ArkTS/ArkTsConcurrent/ApplicationMultithreadingDevelopment/PracticalCases/entry/src/main/ets/managers/UsingTaskPool.ets) --> 314 315## Using Sendable for Large-Scale Database Operations with Complex Class Instances 316 317The properties of regular class instances can contain Sendable class instances. 318 319For complex regular class instances, you can first wrap the relevant database data fields in Sendable class instances and then let the regular class instances hold these Sendable class instances. This can help lower the cost of cross-thread operations. 320 3211. Define the data format in the database. Sendable can be used to minimize cross-thread latency. 322 323 ```ts 324 // SharedValuesBucket.ets 325 export interface IValueBucket { 326 id: number; 327 name: string; 328 age: number; 329 salary: number; 330 } 331 332 @Sendable 333 export class SharedValuesBucket implements IValueBucket { 334 id: number = 0; 335 name: string = ""; 336 age: number = 0; 337 salary: number = 0; 338 339 constructor(v: IValueBucket) { 340 this.id = v.id; 341 this.name = v.name; 342 this.age = v.age; 343 this.salary = v.salary; 344 } 345 } 346 ``` 347 3482. Define a regular class instance to hold the Sendable class instance. 349 350 ```ts 351 // Material.ets 352 import { SharedValuesBucket } from './SharedValuesBucket'; 353 import { collections } from '@kit.ArkTS'; 354 355 export class Material { 356 seq: number = 0; 357 materialName: string = ""; 358 //... Other properties are omitted. 359 buckets: collections.Array<SharedValuesBucket | undefined>; 360 361 constructor(seq: number, materialName: string, buckets: collections.Array<SharedValuesBucket | undefined>) { 362 this.seq = seq; 363 this.materialName = materialName; 364 this.buckets = buckets; 365 } 366 367 getBuckets() : collections.Array<SharedValuesBucket | undefined>{ 368 return this.buckets; 369 } 370 371 setBuckets(buckets: collections.Array<SharedValuesBucket | undefined>) { 372 this.buckets = buckets; 373 } 374 } 375 ``` 376 3773. Initiate from the UI main thread and perform Create, Read, Update, Delete (CRUD) operations in the background thread. 378 379 ```ts 380 // Index.ets 381 import { relationalStore, ValuesBucket } from '@kit.ArkData'; 382 import { collections, taskpool } from '@kit.ArkTS'; 383 import { IValueBucket, SharedValuesBucket } from './SharedValuesBucket'; 384 import { Material } from './Material'; 385 386 @Concurrent 387 async function create(context: Context) { 388 const CONFIG: relationalStore.StoreConfig = { 389 name: "Store.db", 390 securityLevel: relationalStore.SecurityLevel.S1, 391 }; 392 393 // The default database file path is context.databaseDir + rdb + StoreConfig.name. 394 let store: relationalStore.RdbStore = await relationalStore.getRdbStore(context, CONFIG); 395 console.info(`Create Store.db successfully!`); 396 397 // Create a table. 398 const CREATE_TABLE_SQL = "CREATE TABLE IF NOT EXISTS test (" + 399 "id INTEGER PRIMARY KEY AUTOINCREMENT, " + 400 "name TEXT NOT NULL, " + 401 "age INTEGER, " + 402 "salary REAL, " + 403 "blobType BLOB)"; 404 await store.executeSql(CREATE_TABLE_SQL); 405 console.info(`Create table test successfully!`); 406 } 407 408 @Concurrent 409 async function insert(context: Context, valueBucketArray: collections.Array<SharedValuesBucket | undefined>) { 410 const CONFIG: relationalStore.StoreConfig = { 411 name: "Store.db", 412 securityLevel: relationalStore.SecurityLevel.S1, 413 }; 414 415 // The default database file path is context.databaseDir + rdb + StoreConfig.name. 416 let store: relationalStore.RdbStore = await relationalStore.getRdbStore(context, CONFIG); 417 console.info(`Create Store.db successfully!`); 418 419 // Insert data. 420 await store.batchInsert("test", valueBucketArray as Object as Array<ValuesBucket>); 421 } 422 423 @Concurrent 424 async function query(context: Context): Promise<collections.Array<SharedValuesBucket | undefined>> { 425 const CONFIG: relationalStore.StoreConfig = { 426 name: "Store.db", 427 securityLevel: relationalStore.SecurityLevel.S1, 428 }; 429 430 // The default database file path is context.databaseDir + rdb + StoreConfig.name. 431 let store: relationalStore.RdbStore = await relationalStore.getRdbStore(context, CONFIG); 432 console.info(`Create Store.db successfully!`); 433 434 // Obtain the result set. 435 let predicates: relationalStore.RdbPredicates = new relationalStore.RdbPredicates("test"); 436 let resultSet = await store.query(predicates); // Query all data. 437 console.info(`Query data successfully! row count:${resultSet.rowCount}`); 438 let index = 0; 439 let result = collections.Array.create<SharedValuesBucket | undefined>(resultSet.rowCount, undefined) 440 resultSet.goToFirstRow() 441 do { 442 let v: IValueBucket = { 443 id: resultSet.getLong(resultSet.getColumnIndex("id")), 444 name: resultSet.getString(resultSet.getColumnIndex("name")), 445 age: resultSet.getLong(resultSet.getColumnIndex("age")), 446 salary: resultSet.getLong(resultSet.getColumnIndex("salary")) 447 }; 448 result[index++] = new SharedValuesBucket(v) 449 } while (resultSet.goToNextRow()); 450 resultSet.close(); 451 return result 452 } 453 454 @Concurrent 455 async function clear(context: Context) { 456 const CONFIG: relationalStore.StoreConfig = { 457 name: "Store.db", 458 securityLevel: relationalStore.SecurityLevel.S1, 459 }; 460 461 // The default database file path is context.databaseDir + rdb + StoreConfig.name. 462 await relationalStore.deleteRdbStore(context, CONFIG); 463 console.info(`Delete Store.db successfully!`); 464 } 465 466 function initMaterial() : Material { 467 // Prepare data. 468 const count = 5 469 let valueBucketArray = collections.Array.create<SharedValuesBucket | undefined>(count, undefined); 470 for (let i = 0; i < count; i++) { 471 let v: IValueBucket = { 472 id: i, 473 name: "zhangsan" + i, 474 age: 20, 475 salary: 5000 + 50 * i 476 }; 477 valueBucketArray[i] = new SharedValuesBucket(v); 478 } 479 let material = new Material(1, "test", valueBucketArray); 480 return material; 481 } 482 483 @Entry 484 @Component 485 struct Index { 486 @State message: string = 'Hello World'; 487 488 build() { 489 RelativeContainer() { 490 Text(this.message) 491 .id('HelloWorld') 492 .fontSize(50) 493 .fontWeight(FontWeight.Bold) 494 .alignRules({ 495 center: { anchor: '__container__', align: VerticalAlign.Center }, 496 middle: { anchor: '__container__', align: HorizontalAlign.Center } 497 }) 498 .onClick(async () => { 499 let context : Context = this.getUIContext().getHostContext() as Context; 500 let material = initMaterial(); 501 await taskpool.execute(create, context); 502 await taskpool.execute(insert, context, material.getBuckets()); 503 let index = 0; 504 let ret: collections.Array<SharedValuesBucket> = 505 await taskpool.execute(query, context) as collections.Array<SharedValuesBucket>; 506 material.setBuckets(ret); 507 for (let v of ret.values()) { 508 console.info(`Row[${index}].id = ${v.id}`); 509 console.info(`Row[${index}].name = ${v.name}`); 510 console.info(`Row[${index}].age = ${v.age}`); 511 console.info(`Row[${index}].salary = ${v.salary}`); 512 index++; 513 } 514 await taskpool.execute(clear, context); 515 }) 516 } 517 .height('100%') 518 .width('100%') 519 } 520 } 521 ``` 522