HarmonyOS-应用数据持久化(Preferences+kv+RelationalStore)

508 阅读4分钟

用户首选项(Preferences)

  • 通常用于保存应用的配置信息。数据通过文本的形式保存在设备中,应用使用过程中会将文本中的数据全量加载到内存中,所以访问速度快、效率高,但不适合需要存储大量数据的场景。
  • Preferences会随着存放的数据量越多而导致应用占用的内存越大,因此,Preferences不适合存放过多的数据,建议存储的数据不超过一万条
  • 适用的场景一般为应用保存用户的个性化设置(字体大小,是否开启夜间模式)等。

简介

本示例使用@ohos.data.preferences 接口,展示了使用首选项持久化存储数据的功能。

持久化主题

效果预览

default主题grey主题simplicity主题

使用说明

1.点击顶部titleBar的右侧切换按钮,弹出主题菜单,选择任意主题则切换相应的主题界面;

2.退出应用再重新进入,显示上一次退出前的主题界面。

开发步骤

1. 准备常量数据
  1. 三种主题名称组成的数组
  2. 三种主题数据:图标icon和对应name
  3. 存储主题数据的时候 要指定首选项对象即文件(数据库)名称

一个项目可以有使用一个首选项对象存数据,也可以使用多个首选项文件分开存不同的数据。建议分门别类设计。

  1. 存储主题的时候,需要设计一个key值

一个首选项对象(文件)可以存很多个键值对数据。根据key区分。

export interface ImageAndName {
  image: string;
  name: string;
}
import { ImageAndName } from "../model/ImageAndName"

export default class DxConstants{

  // 三种主题的名称
  static readonly THEME_NAMES:string[] = ['default','grey','Simple']

  // 三种主题数据
  static readonly themeDataArr: ImageAndName[][] = [
    [
      { image: "/image/model1/dialer.png", name: "dianhua" },
      { image: "/image/model1/shopping.png", name: "shangcheng" },
      { image: "/image/model1/notes.png", name: "beiwanglu" },
      { image: "/image/model1/settings.png", name: "shezhi" },
      { image: "/image/model1/camera.png", name: "xiangji" },
      { image: "/image/model1/gallery.png", name: "xiangce" },
      { image: "/image/model1/music.png", name: "yinyue" },
      { image: "/image/model1/video.png", name: "shipin" },
    ],
    [
      { image: "/image/model2/simplicityCall.png", name: "dianhua" },
      { image: "/image/model2/simplicityShop.png", name: "shangcheng" },
      { image: "/image/model2/simplicityNotes.png", name: "beiwanglu" },
      { image: "/image/model2/simplicitySetting.png", name: "shezhi" },
      { image: "/image/model2/simplicityCamera.png", name: "xiangji" },
      { image: "/image/model2/simplicityPhotos.png", name: "xiangce" },
      { image: "/image/model2/simplicityMusic.png", name: "yinyue" },
      { image: "/image/model2/simplicityVideo.png", name: "shipin" },
    ],
    [
      { image: "/image/model3/pwcall.png", name: "dianhua" },
      { image: "/image/model3/pwshop.png", name: "shangcheng" },
      { image: "/image/model3/pwnotes.png", name: "beiwanglu" },
      { image: "/image/model3/pwsetting.png", name: "shezhi" },
      { image: "/image/model3/pwcamera.png", name: "xiangji" },
      { image: "/image/model3/pwphotos.png", name: "xiangce" },
      { image: "/image/model3/pwmusic.png", name: "yinyue" },
      { image: "/image/model3/pwvideo.png", name: "shipin" },
    ]
  ]

  // 存储主题的首选项的 文件(数据库)名称
  static readonly PREFERENCES_THEME:string = "theme.db"

  // 存储主题的时候 key
  static readonly THEME_KEY:string = "themeModel"
}
2. 准备首选项工具类
  1. 设计成map数据结构,方便存多种首选项文件对象。后期存主题时,使用一个首选项对象,存字体大小时,可以使用另一个。如有其他需要使用不同的首选项对象时,灵活扩展。
  2. 首选项对象创建需要上下文对象和该文件数据库的name。设计好loadPreference()方法,创建好首选项对象后加入到map中。
  3. 设计保存数据和获取数据的方法。 既然设计逻辑为该项目有多个首选项对象,那么在 保存/获取 数据的时候就需要确定从 哪个首选项对象 身上 存储/获取 哪个key
// 首选项工具栏,提供首选项增上改查功能
import { preferences } from '@kit.ArkData'
import { Context } from '@ohos.arkui.UIContext'

class PreferencesUtil {
  // 准备一个map, 用来存多个首选项对象
  preMap: Map<string, preferences.Preferences> = new Map()

  // 加载 首选项对象
  loadPreference(context: Context, name: string) {
    try {
      // 该接口 第二个参数 在next修改为配置项 而非字符串
      let pre: preferences.Preferences = preferences.getPreferencesSync(context, { name })
      // 存到首选项 map中
      this.preMap.set(name, pre)
      console.log('PreferencesUtil', `加载Preferences【${name}】成功`)
    } catch (e) {
      console.log('PreferencesUtil', `加载Preferences【${name}】失败`, JSON.stringify(e))
    }
  }

  // 保存数据
  putPreferenceVal(preferenceName: string, key: string, value: preferences.ValueType) {
    if (!this.preMap.has(preferenceName)) {
      console.log('PreferencesUtil', `加载Preferences【${preferenceName}】尚未初始化`)
      return
    }
    try {
      // 从数组中获取该首选项对象
      let pref = this.preMap.get(preferenceName)
      // 写入数据
      pref?.putSync(key, value)
      // 刷盘
      pref?.flush()
      console.log('PreferencesUtil', `保存【${preferenceName}】-【${key}】-【${value}】成功`)
    } catch (e) {
      console.log('PreferencesUtil', `保存【${preferenceName}】-【${key}】-【${value}】失败`, JSON.stringify(e))
    }
  }

  // 获取数据
  getPreferenceVal(preferenceName: string, key: string, defaultVal: preferences.ValueType){
    if (!this.preMap.has(preferenceName)) {
      console.log('PreferencesUtil', `加载Preferences【${preferenceName}】尚未初始化`)
      return
    }
    try {
      let pref = this.preMap.get(preferenceName)
      let val = pref?.getSync(key, defaultVal)
      console.log('PreferencesUtil', `读取【${preferenceName}】-【${key}】-【${val}】成功`)
      return val
    }catch (e){
      console.log('PreferencesUtil', `读取【${preferenceName}】-【${key}】失败`,JSON.stringify(e))
      // try和catch都要有返回
      return defaultVal
    }
  }
}

export default new PreferencesUtil()
3. 入口类中创建首选项对象
onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
  // 初始化首选项
  let context = this.context
  // 存储主题的 首选项文件放到 map中
  PreferencesUtil.loadPreference(context,DxConstants.PREFERENCES_THEME)
}
4. 在UI中使用
  • 切换主题:在首页预先设置好几套主体数据,使用preferences.getPreferences获取使用Preferences对象,调用Preferences.get() 读取缓存中的参数,得到当前应该展示哪一套主体。每次点击切换按钮都会调用Preferences.put()来重新修改参数,然后使用 Preferences.flush()保存并刷新文件内容。 源码参考:Index.ets
import DxConstants from "../common/DxConstants";
import PreferencesUtil from "../common/PreferencesUtil";
import { ImageAndName } from "../model/ImageAndName";


@Entry
  @Component
  struct Index {
    @StorageProp("info") message: string = ""
    // 当前页面展示的数据
    @State theme: ImageAndName[] = DxConstants.themeDataArr[0]
    @State themeName: string = DxConstants.THEME_KEY[0]

    // 页面加载先查看上一次的主题是什么
    aboutToAppear(): void {
      this.themeName = PreferencesUtil.getPreferenceVal(
        DxConstants.PREFERENCES_THEME,
        DxConstants.THEME_KEY,
        DxConstants.THEME_NAMES[0]
      ) as string
      // 根据 modelName 获取索引值
      let index = DxConstants.THEME_NAMES.indexOf(this.themeName)
      // 初始化当前模型数组
      this.theme = DxConstants.themeDataArr[index]
    }

    // 修改当前显示模式
    changeTheme(index: number) {
      // 主题名称
      this.themeName = DxConstants.THEME_NAMES[index]
      // 主题数组
      this.theme = DxConstants.themeDataArr[index]
      // 持久化存储
      PreferencesUtil.putPreferenceVal(
        DxConstants.PREFERENCES_THEME,
        DxConstants.THEME_KEY,
        DxConstants.THEME_NAMES[index]
      )
    }

    build() {
      Column({ space: 30 }) {
        Text(this.message).fontColor("#fff50909")
        Divider().color(Color.Red)
        Row() {
          Text("主题:"+this.themeName)
            .fontSize(20)
            .layoutWeight(5)
            .padding({ left: 10 })
            .fontColor(Color.White)
            .fontWeight(FontWeight.Bold)
          Image($r('app.media.change'))
            .key('changeBtn')
            .id('changeBtn')
            .height(30)
            .layoutWeight(1)
            .objectFit(ImageFit.ScaleDown)
            .bindMenu([
              {
                value: DxConstants.THEME_NAMES[0],
                action: () => {
                  // change  and save model0 :default
                  this.changeTheme(0)
                }
              },
              {
                value: DxConstants.THEME_NAMES[1],
                action: () => {
                  // change  and save model1 :simplicity
                  this.changeTheme(1)

                }
              },
              {
                value: DxConstants.THEME_NAMES[2],
                action: () => {
                  // change  and save model2 :pomelo
                  this.changeTheme(2)
                }
              }
            ])
        }
        .width('100%')
          .height(50)
          .backgroundColor('#0D9FFB')

        Grid() {
          ForEach(this.theme, (item: ImageAndName) => {
            GridItem() {
              Column() {
                Image(item.image)
                  .width(70)
                  .height(70)
                  .padding(10)
                  .objectFit(ImageFit.Fill)
                Text(item.name).fontSize(15)
              }
              .width(90)
                .height(90)
            }
          }, (item: ImageAndName) => JSON.stringify(item))
        }
        .rowsGap(10)
          .width('100%')
          .columnsGap(10)
          .layoutWeight(1)
          .padding({ top: 20 })
          .backgroundColor('#e5e5e5')
          .columnsTemplate('1fr 1fr 1fr 1fr')
      }
      .width('100%')
    .height('100%')
    .backgroundColor($r('app.color.theme_color'))
  }
}

持久化字号大小(缩放倍数)。

在前文持久化主题数据掌握后,顺便持久化一下字号大小。 项目源码


设计思路

  1. 入口类初始化一个新的首选项数据库文件,例如取名:fontSize.db
  2. 准备状态变量绑定到滑块Slider身上, 滑块通过onChange事件修改当前状态变量数据,双向更新。
  3. 在滑块onChange事件中持久化存储
  4. aboutToAppear函数中获取持久化的字号大小放大倍数

编码过程

  1. src/main/ets/common/DxConstants.ets文件中追加常量数据
//  存储字号大小的 数据库 名称
static readonly PREFERENCES_FontSize:string = "fontSizeScale.db"
// 存储字号大小 倍数 的时候 key
static readonly FONTSIZE_KEY:string = "fontSizeScale"
// 字号大小 倍数 默认值
static readonly fontSizeScaleDefault : number= 1
  1. 在入口类 src/main/ets/preferencesability/PreferencesAbility.ets 中初始化首选项数据库文件
onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
  console.log("dxin => PreferencesAbility is Created")
  // 获取主模块传递过来的参数
  let abilityWant = want
  let info = abilityWant?.parameters?.info || '首选项'
  AppStorage.setOrCreate("info", info)

  // 初始化首选项
  let context = this.context
  // 存储主题的 首选项文件放到 map中
  PreferencesUtil.loadPreference(context, DxConstants.PREFERENCES_THEME)
  // 存储字号大小的 首选项文件  放到 map 中
  PreferencesUtil.loadPreference(context, DxConstants.PREFERENCES_FontSize)
}
  1. 在UI中使用
    1. 定义状态变量
@State currentSliderVal: number = DxConstants.fontSizeScaleDefault
    1. 页面加载前初始化状态变量
aboutToAppear(): void {
  // 去查询一下 上次存储的 字号大小数据 数据
  this.currentSliderVal = PreferencesUtil.getPreferenceVal(
    DxConstants.PREFERENCES_FontSize,
    DxConstants.FONTSIZE_KEY,
    DxConstants.fontSizeScaleDefault
  ) as number
}
    1. 在页面中添加滑块组件
Column() {
  Row() {
    Text(0.8 + "").fontSize(20)
    Text(this.currentSliderVal.toFixed(1)).fontSize(30).fontColor("#fff50065")
    Text(1.2 + "").fontSize(20)
  }
  .width("80%")
    .height(40)
    .justifyContent(FlexAlign.SpaceBetween)

  Slider({
    min: 0.8,
    max: 1.2,
    step: 0.1,
    value: this.currentSliderVal
  })
    .width("80%")
    .height(40)
    .onChange((val) => {
      this.currentSliderVal = val
      // 把当前改变的数据,存储起来 持久化
      PreferencesUtil.putPreferenceVal(
        DxConstants.PREFERENCES_FontSize,
        DxConstants.FONTSIZE_KEY,
        this.currentSliderVal
      )
      console.log("dxin => 存储好了字号大小:", this.currentSliderVal)
    })
}
    1. 页面中用到文字的地方,进行字号大小的设置:字号*倍数
Text(item.name).fontSize(16 * this.currentSliderVal)

键值型数据库(KV-Store)

HarmonyOS应用数据持久化-KV数据库存储用户数据

  • 设备协同数据库,针对每条记录,Key的长度≤896 Byte,Value的长度<4 MB。
  • 单版本数据库,针对每条记录,Key的长度≤1 KB,Value的长度<4 MB。
  • 每个应用程序最多支持同时打开16个键值型分布式数据库。
  • 键值型数据库事件回调方法中不允许进行阻塞操作,例如修改UI组件。
  • 存储的数据类型不能是对象,只能是 Uint8Array | string | number | boolean

简介

本示例使用@ohos.data.distributedKVStore (分布式键值数据库)接口,持久化存储用户数据。杀死进程后重新进入app,展示退出前用户数据信息。

开发思路

  1. 参考官方KV数据库文档进行学习,发现学习路径与首选项持久化数据库雷同。都需要创建一个数据库文件,在该文件中根据标记存储数据(键值对)
  2. 设计一个KV数据库操作工具类,封装初始化kv数据库的实例对象(kvStore),使用这个对象进行增上改查。
  3. 数据库的名字(kvStoreId),要存储用户时的键名(userInfoKey),抽取到通用自定义常量文件中,方便复用和扩展。
  4. 在入口类中初始化项目,注意,注意,注意

经测试,要在onWindowStageCreate中初始化。在onCreate中有问题。经分析,应该是生命周期问题,但是参考官方文档后,又无法与文档相悖。无法总结其原理,只好记录于此。记下来,初始化位置选 onWindowStageCreate

  1. 在UI中使用较为简单,业务处调用工具类存储数据,aboutToAppear调用工具类获取数据。

代码编写

src/main/ets/common/DxConstants.ets

export default class DxConstants{
  // kv数据库的名字
  static readonly kvStoreId : string= "Dxin"

  //kv数据库中存储 用户数据的键名
  static readonly userInfoKey : string= "userinfo"

}

src/main/ets/common/DxKvStoreUtil.ets

import { Context } from '@kit.AbilityKit';
import { distributedKVStore } from '@kit.ArkData';
import { BusinessError } from '@kit.BasicServicesKit';

class DxKvStoreUtil {
  /*
   * KV数据库可以设计成 map 用不同的kv存储不同的数据 注意每个应用同时打开KV数据库 数量最多 16 个
   *  此处 为demo案例,未设计为 map。如果设计为map的用法,参考如下网址的首选项工具类
   *  https://juejin.cn/post/7451824088924553231
   * */
  kvStore: distributedKVStore.SingleKVStore | undefined = undefined;

  // 1. 初始化 kvstore
  async kvStoreInit(kvStoreName: string, context: Context) {
    let kvManger: distributedKVStore.KVManager | undefined = undefined

    const kvMangerConfig: distributedKVStore.KVManagerConfig = {
      context,
      bundleName: getContext(context).applicationInfo.name
    }
    kvManger = distributedKVStore.createKVManager(kvMangerConfig)
    const options: distributedKVStore.Options = {
      createIfMissing: true,
      encrypt: false,
      backup: false,
      autoSync: false,
      // kvStoreType不填时,默认创建多设备协同数据库
      kvStoreType: distributedKVStore.KVStoreType.SINGLE_VERSION,
      // 多设备协同数据库:kvStoreType: distributedKVStore.KVStoreType.DEVICE_COLLABORATION,
      securityLevel: distributedKVStore.SecurityLevel.S1
    };
    // --------------------**** 测试代码 start-------------------
    // 获取应用所有创建过的 kvStore 需要根据 BundleName

    // 此处测试代码对本示例功能无影响,纯粹想测一下 getAllKVStoreId(appid) 这个API
    // let appid = getContext(context).applicationInfo.name
    // let stroeIdArr: string [] = await kvManger.getAllKVStoreId(appid)
    // if (stroeIdArr.includes(kvStoreName)) {
    //   // 已经存在
    //   console.log("dxin => ", appid, `里已经存在${kvStoreName} kv实例`)
    // } else {
    //   console.log("dxin => ", appid, `里还没有@@存在${kvStoreName} kv实例`)
    // }
    // --------------------**** 测试代码 end-------------------

    let store =  await kvManger.getKVStore<distributedKVStore.SingleKVStore>(kvStoreName, options)
    if (store !== undefined) {
      this.kvStore = store
      console.info(`Dxin => 获取名字叫${kvStoreName}的 KVStore 成功.`);
    } else {
      console.info(`Dxin => 获取名字叫${kvStoreName}的 KVStore 失败@@.`);
    }
  }

  //   2.put 方法,注意put的数据有类型要求
  async putData(keyName: string, valueData: Uint8Array | string | number | boolean) {
    try {
      if (this.kvStore != undefined) {
        this.kvStore.put(keyName, valueData)
          .then(() => {
            console.info(`Dxin => 向${keyName} 去 put 时成功了`);
          })
          .catch((err: BusinessError) => {
            console.error(`Dxin => 向 ${keyName} 去 put 时失败了. Code:${err.code},message:${err.message}`);
          })
      }
    } catch (e) {
      let error = e as BusinessError;
      console.error(`Dxin => 向${keyName} 去 put 时发生了未知错误. Code:${error.code},message:${error.message}`);
    }
  }

  //3. 使用 kvStore 获取具体的数据
  async getData(keyName: string) {
    if (this.kvStore === undefined) {
      console.error(`Dxin => 从${keyName} 去 get 时发现,这个kv数据库都不存在`);
      return
    }
    let data = await this.kvStore.get(keyName)
    console.log(`Dxin => 从${keyName}成功获取数据. Data:${data}`)
    return data
  }

  //4. 删除 指定键名对应的数据
  deleteData(keyName: string) {
    if (this.kvStore === undefined) {
      console.error(`Dxin => 从${keyName} 去 删除数据 时发现,kv数据库都不存在`);
      return
    }
    this.kvStore.delete(keyName)
      .then(() => {
        console.info('Dxin => 成功地从${keyName}中删除数据');
      })
      .catch((err: BusinessError) => {
        console.error(`Dxin => 失败地从${keyName}中删除数据. Code:${err.code},message:${err.message}`);

      })
  }
}

export default new DxKvStoreUtil()

src/main/ets/kvstoreability/KvStoreAbility.ets

async onWindowStageCreate(windowStage: window.WindowStage) {
  // 此处调用方法一定记得 await 等待初始化kv数据库成功再去显示UI 如果异步显示UI 会导致KV 还未初始化完呢
  // 经过测试,该方法放在 onWindowStageCreate 可行
  // 经过测试,该方法放在 onCreate 不可行 执行时机不同,生命周期只是先后顺序,并非onCreate结束后再onWindowStageCreate
  await DxKvStoreUtil.kvStoreInit(DxConstants.kvStoreId, this.context)
  console.log("Dxin => 入口类初始化了KV数据库:", DxConstants.kvStoreId)
  windowStage.loadContent('pages/Index', (err) => {
    if (err.code) {
      return;
    }
  });
}

src/main/ets/pages/Index.ets

import DxConstants from '../common/DxConstants';
import DxKvStoreUtil from '../common/DxKvStoreUtil';

// 用户模型
class Userinfo {
  username: string
  password: string

  constructor(username: string, password: string) {
    this.username = username
    this.password = password
  }
}

@Entry
  @Component
  struct Index {
    @State userinfo: Userinfo = new Userinfo("dxin", "123")

    async aboutToAppear(): Promise<void> {
      // KV数据库获取
      let userinfoStr: string = await DxKvStoreUtil.getData(DxConstants.userInfoKey) as string
      if (userinfoStr === undefined) {
        this.userinfo = new Userinfo("dd","666")
      }else {
        let currentUserinfo = JSON.parse(userinfoStr) as Userinfo
        this.userinfo = currentUserinfo
      }
    }

    build() {
      Column({ space: 30 }) {
        Text(`欢迎${this.userinfo.username},你的密码是${this.userinfo.password}`)
        TextInput({ placeholder: "用户名", text:$$this.userinfo.username })
          .onFocus(() => {
            this.userinfo.username = ""
          })
        TextInput({ placeholder: "密码", text: $$this.userinfo.password })
          .onFocus(() => {
            this.userinfo.password = ""
          })

        Button("KV数据库应用")
          .onClick(() => {
            //   KV数据库存储 注意存储的数据类型不能是对象,只能是 Uint8Array | string | number | boolean
            let userinfoStr = JSON.stringify(this.userinfo)
            DxKvStoreUtil.putData(DxConstants.userInfoKey, userinfoStr)
          })
      }
      .width('100%')
        .height('100%')

    }
  }

关系型数据库(RelationalStore)

以书籍信息为例完成增删改查。使用关系型数据库操作数据。掌握该案例后,可举一反三设计开发记账本项目、便单项目、等各种常见app类似功能。

本案例源码参考

开发思路

  1. 封装工具类,设计初始化rdb实例方法(涉及到ability的上下文对象),供增上改查方法中使用该实例对象
  2. 入口类中初始化rdb实例对象
  3. UI处设计业务逻辑进行增删改查

代码编写

常量类

    export default class DxConstants {
      //rdbstore 数据库的名称 根据业务自定义,此处为学习demo,随便取
      static readonly rdbStoreName: string = 'DxRdbStore'
    
      //   ------------------dxBook 表-----------
      static readonly bookTableName: string = 'dxBook'
      static readonly SQL_CREATE_TABLE_Book =
        `CREATE TABLE IF NOT EXISTS dxBook (
          ID INTEGER PRIMARY KEY AUTOINCREMENT,
          NAME TEXT NOT NULL,
          price INTEGER
        )`
    }

书籍数据库工具类

import { relationalStore, ValuesBucket } from "@kit.ArkData"
import Book from "../model/Book";
import DxConstants from "./DxConstants";

class BookRdbUtil {
  // Book表 增删改查 都要用到的 rdb对象 可以在入口类调用方法初始化(因为需要上下文对象)
  bookRdbStore: relationalStore.RdbStore | undefined = undefined

  // 初始化方法
  async initRdbStore(context: Context) {
    //  设置当前数据库配置信息
    const STORE_CONFIG: relationalStore.StoreConfig = {
      name: DxConstants.bookTableName, // 数据库文件名
      securityLevel: relationalStore.SecurityLevel.S3, // 数据库安全级别
    }
    let rdbStore = await relationalStore.getRdbStore(context, STORE_CONFIG)
    rdbStore.executeSql(DxConstants.SQL_CREATE_TABLE_Book)
    this.bookRdbStore = rdbStore
    // console.log(`dxin => bookRdbStore初始化OK ${this.bookRdbStore}`)
  }

  //   增
  add(book: Book) {
    /*
     *
     * */
    if (this.bookRdbStore != undefined) {
      /*
       * param1: 往哪个表中插入数据
       * param2: 插入什么数据
       * return :  插入后影响的行数
       * */
      return this.bookRdbStore.insertSync(DxConstants.bookTableName, { name: book.name, price: book.price })
    } else {
      console.log(`dxin => 插入数据失败, ${DxConstants.bookTableName}对应的rdbstore是undefined`)
      return -1
    }
  }

  //   删
  delBookById(id: number) {
    if (this.bookRdbStore != undefined) {
      let predicates = new relationalStore.RdbPredicates(DxConstants.bookTableName) // 获取谓词对象
      predicates.equalTo('id', id) // 匹配规则
      return this.bookRdbStore.deleteSync(predicates)
    } else {
      console.log(`dxin => ${DxConstants.bookTableName}表删除数据时出错,数据库 undefined ,或者谓词 undefined`)
      return -1
    }
  }

  //   改

  /*
   * 修改方法 根据ID 更新为新的 Book对象数据
   *  predicatesName:创建表表的谓词 需要确定该字符串
   *  field: 更新哪个字段
   *  fieldVal:匹配该字段的哪一个值
   *  newVal:当前行的新数据
   *
   * 返回值: 修改语句影响的行数 number
   * */
  update(book:Book) {
    // 修改数据
    if ( this.bookRdbStore != undefined) {
      let predicates =  new relationalStore.RdbPredicates(DxConstants.bookTableName)
      predicates.equalTo('id', book.id);
      // 定义一个 ValuesBucket 数据
      let newBook:ValuesBucket={
        id:book.id || 0,
        name:book.name,
        price:book.price
      }
      return this.bookRdbStore.updateSync(newBook, predicates)
    } else {
      console.log(`dxin => 修改数据时出错,数据库 undefined `)
      return -1
    }
  }

  //   查所有
  queryAllBook() {
    let predicates = new relationalStore.RdbPredicates(DxConstants.bookTableName) // 获取谓词对象
    let bookArr = this.getBookArr(predicates)
    return bookArr
  }

  //   条件查询  根据书名查询
  queryBookByName(name: string) {
    let predicates = new relationalStore.RdbPredicates(DxConstants.bookTableName) // 获取谓词对象
    predicates.equalTo('name', name)
    let bookArr = this.getBookArr(predicates)
    return bookArr
  }
  //   条件查询  根据价格查询
  queryBookByPrice(price: number) {
    let predicates = new relationalStore.RdbPredicates(DxConstants.bookTableName) // 获取谓词对象
    predicates.equalTo('price', price)
    let bookArr = this.getBookArr(predicates)
    return bookArr
  }

  // book遍历字段封装数组功能
  getBookArr(predicates: relationalStore.RdbPredicates): Book[] {
    if (this.bookRdbStore != null) {
      let result = this.bookRdbStore.querySync(predicates)
      let bookArr: Book[] = []
      while (!result.isAtLastRow) {
        result.goToNextRow()
        let id = result.getLong(result.getColumnIndex('id'))
        let name = result.getString(result.getColumnIndex('name'))
        let price = result.getLong(result.getColumnIndex('price'))
        bookArr.unshift(new Book(name, price, id))
      }
      result.close() // 释放数据集内存
      return bookArr
    } else {
      console.log(`dxin => ${DxConstants.bookTableName}表查询数据时出错,数据库 undefined ,或者谓词 undefined`)
      return [new Book("查询错误", 0, 0)]
    }
  }
}

export default new BookRdbUtil()

入口类

  onWindowStageCreate(windowStage: window.WindowStage): void {
    // 初始化BookRdbUtil 中的 bookRdbStore
    BookRdbUtil.initRdbStore(this.context)

    windowStage.loadContent('pages/Index', (err) => {
      if (err.code) {
        return;
      }
    });
  }

书籍增删改查页面

import Book from '../model/Book';
import BookRdbUtil from '../common/BookRdbUtil';
import { promptAction } from '@kit.ArkUI';


@Entry
@Component
struct BookCurd {
  @State title: string = '使用工具类操作book表数据';
  // book数组数据
  @State bookArr: Book[] = []
  // 待存入 书籍对象
  @State insertBookData: Book = new Book('帝心', 19)
  // 待查询的书名
  @State queryName: string = '红楼梦'
  // 待查询的价格
  @State queryPrice: number = 99

  //  待修改的数据
  @State editBookObj: Book  = new Book('待修改',0)

  aboutToAppear(): void {
    this.showBookArrData()
  }

  // 数据回显到页面上
  showBookArrData() {
    this.bookArr = BookRdbUtil.queryAllBook()
  }

  //  弹窗id
  customDialogComponentId: number = 0

  // 弹窗布局
  @Builder
  editBook() {
    Column() {
      TextInput({ text: $$this.editBookObj.name })
        .fontSize(22).width("70%")
        .backgroundColor('#c6805b5b')
        .borderRadius(5)
      TextInput({ text: $$this.editBookObj.price})
        .fontSize(22).width("70%")
        .backgroundColor('#a4915c5c')
        .borderRadius(5)
      Button('修改')
        .width('70%')
        .onClick(() => {
          // 咋拿到 上方输入框新数据 去操作数据库
          BookRdbUtil.update(this.editBookObj)    // 更新
         this.showBookArrData()              //  查询回显
          promptAction.closeCustomDialog(this.customDialogComponentId)
        })
    }
    .height('30%')
    .borderRadius(5)
    .justifyContent(FlexAlign.SpaceAround)
  }

  build() {
    Column({ space: 10 }) {
      Text(this.title).fontSize(30)
      Divider().color(Color.Red)
      // 功能区
      Row() {
        // 插入
        Column() {
          TextInput({ placeholder: "请输入书名", text: $$this.insertBookData.name }).width("100%")
            .onFocus(() => {
              this.insertBookData.name = ''
            })
          TextInput({ placeholder: "价格", text: $$this.insertBookData.price }).width("100%")
            .onFocus(() => {
              this.insertBookData.price = 0
            })
          Button('插入')
            .width("50%")
            .type(ButtonType.Normal)
            .borderRadius(10)
            .onClick(() => {
              let rows = BookRdbUtil.add(this.insertBookData)
              if (rows != -1) {
                console.log(`dxin => insert result ${`插入数据OK了`}`)
                // 插入成功后 数据回显
                this.showBookArrData()
              } else {
                console.log(`dxin => insert result ${`插入数据出错了`}`)
              }
            })
        }
        .width("40%")
        .height('100%')
        .justifyContent(FlexAlign.SpaceEvenly)
        .alignItems(HorizontalAlign.Center)
        .backgroundColor('#88acd0ef')

        // 查询
        Column() {
          // 根据名字查询
          Row() {
            TextInput({ placeholder: "待查书名", text: $$this.queryName }).width("50%")
              .onFocus(() => {
                this.queryName = ''
              })
              .onBlur(() => {
                this.queryName = ''
              })
            Button('查by书名')
              .width("40%")
              .type(ButtonType.Normal)
              .borderRadius(10)
              .onClick(() => {
                let bookArr = BookRdbUtil.queryBookByName(this.queryName)

                this.bookArr = bookArr
              })
          }
          .width("100%")
          .justifyContent(FlexAlign.SpaceAround)

          // 根据价格查询
          Row() {
            TextInput({ placeholder: "待查价格", text: $$this.queryPrice })
              .width("50%")
              .onFocus(() => {
                this.queryPrice = 0
              })
              .onBlur(() => {
                this.queryPrice = 0
              })
            Button('查by价格')
              .width("40%")
              .type(ButtonType.Normal)
              .borderRadius(10)
              .onClick(() => {
                let bookArr = BookRdbUtil.queryBookByPrice(this.queryPrice)
                this.bookArr = bookArr
              })
          }
          .width("100%")
          .justifyContent(FlexAlign.SpaceAround)

        }
        .width("60%")
        .height('100%')
        .justifyContent(FlexAlign.SpaceAround)
        .alignItems(HorizontalAlign.Center)
        .backgroundColor('#3c96ef6c')

      }
      .width("100%")
      .height(150)
      .justifyContent(FlexAlign.SpaceEvenly)

      Divider().color(Color.Red)
      if (this.bookArr.length == 0) {
        Text('无可展示数据')
          .fontSize(30)
          .width(30)
      }
      List({ space: 5 }) {
        ForEach(this.bookArr, (item: Book) => {
          ListItem() {
            Row() {
              Button('修改')
                .width(60)
                .height(30)
                .fontSize(12)
                .fontColor('#ff11e2c3')
                .backgroundColor('#94ffffff')
                .onClick(() => {
                  //todo 弹窗修改数据
                  this.editBookObj = item
                  // 先准备一个弹窗对象
                  promptAction.openCustomDialog({
                    builder: () => {
                      this.editBook()
                    }
                  }).then((id) => {
                    this.customDialogComponentId = id
                  })
                })
              Text(`id:${item.id}`)
              Text(`name:${item.name}`)
              Text(`price:${item.price}`)
              Button('del')
                .width(60)
                .height(30)
                .fontSize(12)
                .fontColor('#ffff0000')
                .backgroundColor('#94ffffff')
                .onClick(() => {
                  let rows = BookRdbUtil.delBookById(Number(item.id))
                  if (rows != 0) {
                    this.showBookArrData()
                  } else {
                    console.log(`dxin => insert del ${`删除数据出错了`}`)
                  }
                })
            }
            .width("100%")
            .justifyContent(FlexAlign.SpaceEvenly)
          }.width("100%")
        })
      }
      .width("100%")
      .layoutWeight(1)
      .divider({ strokeWidth: 1, color: '#fff39b9b' })
    }
    .width('100%')
    .height('100%')
  }
}