• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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