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 = getContext(this); 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 142## Using Sendable for Large-Scale Database Operations 143 144When 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. 145 1461. Define the data format in the database. Sendable can be used to minimize cross-thread latency. 147 148 ```ts 149 // SharedValuesBucket.ets 150 export interface IValueBucket { 151 id: number 152 name: string 153 age: number 154 salary: number 155 } 156 157 @Sendable 158 export class SharedValuesBucket implements IValueBucket { 159 id: number = 0 160 name: string = "" 161 age: number = 0 162 salary: number = 0 163 164 constructor(v: IValueBucket) { 165 this.id = v.id; 166 this.name = v.name; 167 this.age = v.age; 168 this.salary = v.salary 169 } 170 } 171 ``` 172 1732. Initiate from the UI main thread and perform Create, Read, Update, Delete (CRUD) operations in the background thread. 174 175 ```ts 176 // Index.ets 177 import { relationalStore, ValuesBucket } from '@kit.ArkData'; 178 import { collections, taskpool } from '@kit.ArkTS'; 179 import { IValueBucket, SharedValuesBucket } from './SharedValuesBucket'; 180 181 @Concurrent 182 async function create(context: Context) { 183 const CONFIG: relationalStore.StoreConfig = { 184 name: "Store.db", 185 securityLevel: relationalStore.SecurityLevel.S1, 186 }; 187 188 // The default database file path is context.databaseDir + rdb + StoreConfig.name. 189 let store: relationalStore.RdbStore = await relationalStore.getRdbStore(context, CONFIG); 190 console.info(`Create Store.db successfully!`); 191 192 // Create a table. 193 const CREATE_TABLE_SQL = "CREATE TABLE IF NOT EXISTS test (" + 194 "id INTEGER PRIMARY KEY AUTOINCREMENT, " + 195 "name TEXT NOT NULL, " + 196 "age INTEGER, " + 197 "salary REAL, " + 198 "blobType BLOB)"; 199 await store.executeSql(CREATE_TABLE_SQL); 200 console.info(`Create table test successfully!`); 201 } 202 203 @Concurrent 204 async function insert(context: Context, valueBucketArray: collections.Array<SharedValuesBucket | undefined>) { 205 const CONFIG: relationalStore.StoreConfig = { 206 name: "Store.db", 207 securityLevel: relationalStore.SecurityLevel.S1, 208 }; 209 210 // The default database file path is context.databaseDir + rdb + StoreConfig.name. 211 let store: relationalStore.RdbStore = await relationalStore.getRdbStore(context, CONFIG); 212 console.info(`Create Store.db successfully!`); 213 214 // Insert data. 215 await store.batchInsert("test", valueBucketArray as Object as Array<ValuesBucket>); 216 } 217 218 @Concurrent 219 async function query(context: Context): Promise<collections.Array<SharedValuesBucket | undefined>> { 220 const CONFIG: relationalStore.StoreConfig = { 221 name: "Store.db", 222 securityLevel: relationalStore.SecurityLevel.S1, 223 }; 224 225 // The default database file path is context.databaseDir + rdb + StoreConfig.name. 226 let store: relationalStore.RdbStore = await relationalStore.getRdbStore(context, CONFIG); 227 console.info(`Create Store.db successfully!`); 228 229 // Obtain the result set. 230 let predicates: relationalStore.RdbPredicates = new relationalStore.RdbPredicates("test"); 231 let resultSet = await store.query(predicates); // Query all data. 232 console.info(`Query data successfully! row count:${resultSet.rowCount}`); 233 let index = 0; 234 let result = collections.Array.create<SharedValuesBucket | undefined>(resultSet.rowCount, undefined) 235 resultSet.goToFirstRow() 236 do { 237 let v: IValueBucket = { 238 id: resultSet.getLong(resultSet.getColumnIndex("id")), 239 name: resultSet.getString(resultSet.getColumnIndex("name")), 240 age: resultSet.getLong(resultSet.getColumnIndex("age")), 241 salary: resultSet.getLong(resultSet.getColumnIndex("salary")) 242 }; 243 result[index++] = new SharedValuesBucket(v) 244 } while (resultSet.goToNextRow()); 245 resultSet.close(); 246 return result 247 } 248 249 @Concurrent 250 async function clear(context: Context) { 251 const CONFIG: relationalStore.StoreConfig = { 252 name: "Store.db", 253 securityLevel: relationalStore.SecurityLevel.S1, 254 }; 255 256 // The default database file path is context.databaseDir + rdb + StoreConfig.name. 257 await relationalStore.deleteRdbStore(context, CONFIG); 258 console.info(`Delete Store.db successfully!`); 259 } 260 261 @Entry 262 @Component 263 struct Index { 264 @State message: string = 'Hello World'; 265 266 build() { 267 RelativeContainer() { 268 Text(this.message) 269 .id('HelloWorld') 270 .fontSize(50) 271 .fontWeight(FontWeight.Bold) 272 .alignRules({ 273 center: { anchor: '__container__', align: VerticalAlign.Center }, 274 middle: { anchor: '__container__', align: HorizontalAlign.Center } 275 }) 276 .onClick(async () => { 277 let context = getContext(this); 278 279 // Prepare data. 280 const count = 5 281 let valueBucketArray = collections.Array.create<SharedValuesBucket | undefined>(count, undefined); 282 for (let i = 0; i < count; i++) { 283 let v: IValueBucket = { 284 id: i, 285 name: "zhangsan" + i, 286 age: 20, 287 salary: 5000 + 50 * i 288 }; 289 valueBucketArray[i] = new SharedValuesBucket(v); 290 } 291 await taskpool.execute(create, context) 292 await taskpool.execute(insert, context, valueBucketArray) 293 let index = 0 294 let ret: collections.Array<SharedValuesBucket> = 295 await taskpool.execute(query, context) as collections.Array<SharedValuesBucket> 296 for (let v of ret.values()) { 297 console.info(`Row[${index}].id = ${v.id}`) 298 console.info(`Row[${index}].name = ${v.name}`) 299 console.info(`Row[${index}].age = ${v.age}`) 300 console.info(`Row[${index}].salary = ${v.salary}`) 301 index++ 302 } 303 await taskpool.execute(clear, context) 304 }) 305 } 306 .height('100%') 307 .width('100%') 308 } 309 } 310 ``` 311 312## Using Sendable for Large-Scale Database Operations with Complex Class Instances 313 314The properties of regular class instances can contain Sendable class instances. 315 316For 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. 317 3181. Define the data format in the database. Sendable can be used to minimize cross-thread latency. 319 320 ```ts 321 // SharedValuesBucket.ets 322 export interface IValueBucket { 323 id: number; 324 name: string; 325 age: number; 326 salary: number; 327 } 328 329 @Sendable 330 export class SharedValuesBucket implements IValueBucket { 331 id: number = 0; 332 name: string = ""; 333 age: number = 0; 334 salary: number = 0; 335 336 constructor(v: IValueBucket) { 337 this.id = v.id; 338 this.name = v.name; 339 this.age = v.age; 340 this.salary = v.salary; 341 } 342 } 343 ``` 344 3452. Define a regular class instance to hold the Sendable class instance. 346 347 ```ts 348 // Material.ets 349 import { SharedValuesBucket } from './SharedValuesBucket'; 350 import { collections } from '@kit.ArkTS'; 351 352 export class Material { 353 seq: number = 0; 354 materialName: string = ""; 355 //... Other properties are omitted. 356 buckets: collections.Array<SharedValuesBucket | undefined>; 357 358 constructor(seq: number, materialName: string, buckets: collections.Array<SharedValuesBucket | undefined>) { 359 this.seq = seq; 360 this.materialName = materialName; 361 this.buckets = buckets; 362 } 363 364 getBuckets() : collections.Array<SharedValuesBucket | undefined>{ 365 return this.buckets; 366 } 367 368 setBuckets(buckets: collections.Array<SharedValuesBucket | undefined>) { 369 this.buckets = buckets; 370 } 371 } 372 ``` 373 3743. Initiate from the UI main thread and perform Create, Read, Update, Delete (CRUD) operations in the background thread. 375 376 ```ts 377 // Index.ets 378 import { relationalStore, ValuesBucket } from '@kit.ArkData'; 379 import { collections, taskpool } from '@kit.ArkTS'; 380 import { IValueBucket, SharedValuesBucket } from './SharedValuesBucket'; 381 import { Material } from './Material'; 382 383 @Concurrent 384 async function create(context: Context) { 385 const CONFIG: relationalStore.StoreConfig = { 386 name: "Store.db", 387 securityLevel: relationalStore.SecurityLevel.S1, 388 }; 389 390 // The default database file path is context.databaseDir + rdb + StoreConfig.name. 391 let store: relationalStore.RdbStore = await relationalStore.getRdbStore(context, CONFIG); 392 console.info(`Create Store.db successfully!`); 393 394 // Create a table. 395 const CREATE_TABLE_SQL = "CREATE TABLE IF NOT EXISTS test (" + 396 "id INTEGER PRIMARY KEY AUTOINCREMENT, " + 397 "name TEXT NOT NULL, " + 398 "age INTEGER, " + 399 "salary REAL, " + 400 "blobType BLOB)"; 401 await store.executeSql(CREATE_TABLE_SQL); 402 console.info(`Create table test successfully!`); 403 } 404 405 @Concurrent 406 async function insert(context: Context, valueBucketArray: collections.Array<SharedValuesBucket | undefined>) { 407 const CONFIG: relationalStore.StoreConfig = { 408 name: "Store.db", 409 securityLevel: relationalStore.SecurityLevel.S1, 410 }; 411 412 // The default database file path is context.databaseDir + rdb + StoreConfig.name. 413 let store: relationalStore.RdbStore = await relationalStore.getRdbStore(context, CONFIG); 414 console.info(`Create Store.db successfully!`); 415 416 // Insert data. 417 await store.batchInsert("test", valueBucketArray as Object as Array<ValuesBucket>); 418 } 419 420 @Concurrent 421 async function query(context: Context): Promise<collections.Array<SharedValuesBucket | undefined>> { 422 const CONFIG: relationalStore.StoreConfig = { 423 name: "Store.db", 424 securityLevel: relationalStore.SecurityLevel.S1, 425 }; 426 427 // The default database file path is context.databaseDir + rdb + StoreConfig.name. 428 let store: relationalStore.RdbStore = await relationalStore.getRdbStore(context, CONFIG); 429 console.info(`Create Store.db successfully!`); 430 431 // Obtain the result set. 432 let predicates: relationalStore.RdbPredicates = new relationalStore.RdbPredicates("test"); 433 let resultSet = await store.query(predicates); // Query all data. 434 console.info(`Query data successfully! row count:${resultSet.rowCount}`); 435 let index = 0; 436 let result = collections.Array.create<SharedValuesBucket | undefined>(resultSet.rowCount, undefined) 437 resultSet.goToFirstRow() 438 do { 439 let v: IValueBucket = { 440 id: resultSet.getLong(resultSet.getColumnIndex("id")), 441 name: resultSet.getString(resultSet.getColumnIndex("name")), 442 age: resultSet.getLong(resultSet.getColumnIndex("age")), 443 salary: resultSet.getLong(resultSet.getColumnIndex("salary")) 444 }; 445 result[index++] = new SharedValuesBucket(v) 446 } while (resultSet.goToNextRow()); 447 resultSet.close(); 448 return result 449 } 450 451 @Concurrent 452 async function clear(context: Context) { 453 const CONFIG: relationalStore.StoreConfig = { 454 name: "Store.db", 455 securityLevel: relationalStore.SecurityLevel.S1, 456 }; 457 458 // The default database file path is context.databaseDir + rdb + StoreConfig.name. 459 await relationalStore.deleteRdbStore(context, CONFIG); 460 console.info(`Delete Store.db successfully!`); 461 } 462 463 function initMaterial() : Material { 464 // Prepare data. 465 const count = 5 466 let valueBucketArray = collections.Array.create<SharedValuesBucket | undefined>(count, undefined); 467 for (let i = 0; i < count; i++) { 468 let v: IValueBucket = { 469 id: i, 470 name: "zhangsan" + i, 471 age: 20, 472 salary: 5000 + 50 * i 473 }; 474 valueBucketArray[i] = new SharedValuesBucket(v); 475 } 476 let material = new Material(1, "test", valueBucketArray); 477 return material; 478 } 479 480 @Entry 481 @Component 482 struct Index { 483 @State message: string = 'Hello World'; 484 485 build() { 486 RelativeContainer() { 487 Text(this.message) 488 .id('HelloWorld') 489 .fontSize(50) 490 .fontWeight(FontWeight.Bold) 491 .alignRules({ 492 center: { anchor: '__container__', align: VerticalAlign.Center }, 493 middle: { anchor: '__container__', align: HorizontalAlign.Center } 494 }) 495 .onClick(async () => { 496 let context = getContext(this); 497 let material = initMaterial(); 498 await taskpool.execute(create, context); 499 await taskpool.execute(insert, context, material.getBuckets()); 500 let index = 0; 501 let ret: collections.Array<SharedValuesBucket> = 502 await taskpool.execute(query, context) as collections.Array<SharedValuesBucket>; 503 material.setBuckets(ret); 504 for (let v of ret.values()) { 505 console.info(`Row[${index}].id = ${v.id}`); 506 console.info(`Row[${index}].name = ${v.name}`); 507 console.info(`Row[${index}].age = ${v.age}`); 508 console.info(`Row[${index}].salary = ${v.salary}`); 509 index++; 510 } 511 await taskpool.execute(clear, context); 512 }) 513 } 514 .height('100%') 515 .width('100%') 516 } 517 } 518 ``` 519