• 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 = 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