徐锦洪 发表于 2024-10-19 10:29:10

【鸿蒙HarmonyOS NEXT】数据存储之关系型数据库RDS

一、情况说明


[*] DevEco Studio 版本:
https://i-blog.csdnimg.cn/direct/e4402c678aac40a897577b48758ff24f.png
[*] API版本:以12为主
https://i-blog.csdnimg.cn/direct/e7e95eb5303b470cb5a620d1e3c73f52.png
二、关系型数据库RDS先容

1. RDS关系型数据库简介:
   关系型数据库(Relational Database,RDB)是一种基于关系模子来管理数据的数据库。关系型数据库基于SQLite组件提供了一套完备的对本地数据库进行管理的机制,对外提供了一系列的增、删、改、查等接口,也可以直接运行用户输入的SQL语句来满足复杂的场景需要。支持通过ResultSet.getSendableRow方法获取Sendable数据,进行跨线程传递。
为包管插入并读取数据乐成,建议一条数据不要高出2M。超出该大小,插入乐成,读取失败。
大数据量场景下查询数据大概会导致耗时长乃至应用卡死,建议如下:

[*]单次查询数据量不高出5000条。


[*]在TaskPool中查询。
[*]拼接SQL语句尽量简洁。
[*]合理地分批次查询。
[*]该模块提供以下关系型数据库相干的常用功能:

[*]RdbPredicates: 数据库中用来代表数据实体的性质、特征或者数据实体之间关系的词项,主要用来定义数据库的操作条件。
[*]RdbStore:提供管理关系数据库(RDB)方法的接口。
[*]ResultSet:提供用户调用关系型数据库查询接口之后返回的结果集合。
2. 基本概念


[*] 谓词:数据库中用来代表数据实体的性质、特征或者数据实体之间关系的词项,主要用来定义数- 据库的操作条件。
[*] 结果集:指用户查询之后的结果集合,可以对数据进行访问。结果集提供了机动的数据访问方式,可以更方便地拿到用户想要的数据。
3. RDS应用场景:
   关系型数据库基于SQLite组件,实用于存储包含复杂关系数据的场景,比如一个班级的学生信息,需要包罗姓名、学号、各科结果等,又或者公司的雇员信息,需要包罗姓名、工号、职位等,由于数据之间有较强的对应关系,复杂程度比键值型数据更高,此时需要利用关系型数据库来长期化生存数据。
4. 运作机制先容:
   关系型数据库对应用提供通用的操作接口,底层利用SQLite作为长期化存储引擎,支持SQLite具有的数据库特性,包罗但不限于事件、索引、视图、触发器、外键、参数化查询和预编译SQL语句。
https://i-blog.csdnimg.cn/direct/590e9049c2744554a44ba1258e1deab6.png
三、示例代码加以说明

沿用【鸿蒙HarmonyOS NEXT】页面之间相互传递参数博文中的代码,进行测试。
利用场景模拟:将用户登录的信息存储到关系型数据库中,并将RDS中的数据库抽取展示出来。
代码改写如下:

[*] 新增一个名为model的目录
https://i-blog.csdnimg.cn/direct/570a540f5bed4060aa15c541d7e6b00e.png
[*] 在model目录下创建名称为UserInfo的Ark TS文件,用于封装用户信息表的数据,代码如下:
/**
* 构造数据库表CLIENT_USER对应的实体类
*/
export default class UserInfo {
public id: number
public account: string
public password: string

constructor(id: number, account: string, password: string) {
    this.id = id;
    this.account = account;
    this.password = password;
}
}

[*] LoginPage完备代码如下:
import { router } from '@kit.ArkUI';

// 引入Context相关
import { common } from '@kit.AbilityKit';

// 引入RDB相关
import { relationalStore } from '@kit.ArkData';
import { BusinessError } from '@kit.BasicServicesKit';


@Preview
@Entry
@Component
struct LoginPage {
@State message: string = '登录页';
@State btnMsg: string = '登录';
@State account: string = ''; // 账号状态变量
@State password: string = ''; // 密码状态变量

// 获取Context
private context = getContext(this) as common.UIAbilityContext;
// 定义RdbStore
private rdbStore: relationalStore.RdbStore | undefined = undefined;

aboutToAppear(): void {

    // 构造StoreConfig对象
    const STORE_CONFIG: relationalStore.StoreConfig = {
      name: 'RdbTest.db', // 数据库文件名
      securityLevel: relationalStore.SecurityLevel.S1, // 数据库安全级别
      encrypt: false, // 可选参数,指定数据库是否加密,默认不加密
      customDir: 'customDir/subCustomDir', // 可选参数,数据库自定义路径。数据库将在如下的目录结构中被创建:context.databaseDir + '/rdb/' + customDir,其中context.databaseDir是应用沙箱对应的路径,'/rdb/'表示创建的是关系型数据库,customDir表示自定义的路径。当此参数不填时,默认在本应用沙箱目录下创建RdbStore实例。
      isReadOnly: false // 可选参数,指定数据库是否以只读方式打开。该参数默认为false,表示数据库可读可写。该参数为true时,只允许从数据库读取数据,不允许对数据库进行写操作,否则会返回错误码801。
    };

    // 判断数据库版本,如果不匹配则需进行升降级操作
    // 默认数据库版本为0,表结构:CLIENT_USER (ID INTEGER PRIMARY KEY AUTOINCREMENT, ACCOUNT TEXT NOT NULL, PASSWORD TEXT NOT NULL)'
    const SQL_CREATE_TABLE =
      'CREATE TABLE IF NOT EXISTS CLIENT_USER (ID INTEGER PRIMARY KEY AUTOINCREMENT, ACCOUNT TEXT NOT NULL, PASSWORD TEXT NOT NULL)'; // 建表Sql语句

    // 获得一个相关的RdbStore,操作关系型数据库,用户可以根据自己的需求配置RdbStore的参数,然后通过RdbStore调用相关接口可以执行相关的数据操作,使用callback异步回调。
    relationalStore.getRdbStore(this.context, STORE_CONFIG, (err, store) => {
      if (err) {
      console.error(`At LoginPage Failed to get RdbStore. Code:${err.code}, message:${err.message}`);
      return;
      }
      console.info('At LoginPage Succeeded in getting RdbStore.');
      // 当数据库创建时,数据库默认版本为0
      if (store.version === 0) {
      store.executeSql(SQL_CREATE_TABLE); // 创建数据表
      // 设置数据库的版本,入参为大于0的整数
      store.version = 1;
      }
      // 赋值给当前的rdbStore对象,确保获取到RdbStore实例后,再进行数据库的增、删、改、查等操作
      this.rdbStore = store
    });
}

build() {
    Column() {
      Text(this.message)
      .id('HelloWorld')
      .fontSize(20)
      .fontWeight(FontWeight.Bold)
      .width('100%')
      .height(50)
      .textAlign(TextAlign.Center)
      .backgroundColor(0xF1F3F5)

      Image($r('app.media.startIcon'))
      .width(150)
      .height(150)
      .margin({ top: 40, bottom: 40 })

      TextInput({ placeholder: '请输入手机号' })
      .maxLength(11)// 最大长度
      .type(InputType.Number)// 输入类型为数字
      .inputStyle()// 应用自定义样式
      .onChange((value: string) => {
          this.account = value; // 更新账号状态
      })
      Line().lineStyle() // 应用自定义Line样式

      // 密码输入框
      TextInput({ placeholder: '请输入密码' })
      .maxLength(12)// 最大长度
      .type(InputType.Password)// 输入类型为密码
      .inputStyle()// 应用自定义样式
      .onChange((value: string) => {
          // TODO: 生产环境需要使用正则表达式对手机号进行验证
          this.password = value; // 更新密码状态
      })
      Line().lineStyle() // 应用自定义Line样式


      Button(this.btnMsg)
      .width('80%')
      .margin({ top: 100 })
      .height(50)
      .onClick(() => {
          if (this.account === undefined || this.account === '') {
            console.info('请输入账号')
            return
          }

          if (this.password === undefined || this.password === '') {
            console.info('请输入密码')
            return
          }

          // 使用ArkData RDB数据库 以建表方式存储用户信息数据
          if (this.rdbStore !== undefined) {
            // 构造insert函数所需ValuesBucket对象
            const userInfo: relationalStore.ValuesBucket = {
            ACCOUNT: this.account,
            PASSWORD: this.password
            };
            // 插入数据到指定表中
            this.rdbStore.insert('CLIENT_USER', userInfo, (err: BusinessError, rowId: number) => {
            // 如果成功插入,则返回该行所在表中的行号,否则返回-1
            if (rowId === -1) {
                console.error(`At LoginPage,插入数据失败,错误代码:${err.code},错误提示:${err.message}`)
                return
            }
            console.info(`At LoginPage,插入数据成功,rowId为 ${rowId}`)
            })
          }

          // 跳转到首页
          router.pushUrl({
            url: 'pages/HomePage',
            params: {
            account: this.account,
            password: this.password
            }
          })
      })
    }
    .height('100%')
    .width('100%')
    .padding(0)
}
}

// TextInput组件的自定义样式扩展
@Extend(TextInput)
function inputStyle() {
.placeholderColor(Color.Gray) // 占位符颜色
.height(50) // 输入框高度
.fontSize(15) // 字体大小
.backgroundColor(0xF1F3F5) // 背景颜色
.width('90%') // 宽度为父组件的100%
.padding({ left: 12 }) // 左侧填充
.margin({ top: 15 }) // 上方边距
}

// Line组件的自定义样式扩展
@Extend(Line)
function lineStyle() {
.width('100%') // 宽度为父组件的100%
.height(1) // 高度
.backgroundColor(0xF1F3F5) // 背景颜色
}



[*] HomePage完备代码如下:
import { router } from '@kit.ArkUI';
import { relationalStore } from '@kit.ArkData';
import { common } from '@kit.AbilityKit'
import { BusinessError } from '@kit.BasicServicesKit';
import UserInfo from '../model/UserInfo';


@Preview
@Entry
@Component
struct HomePage {
@State message: string = '首页';
// 定义List组件所需展示的数组,初始化为null
@State listUserInfo: Array<UserInfo> = []
// 获取Context
private context = getContext(this) as common.UIAbilityContext;
// 定义RdbStore
private rdbStore: relationalStore.RdbStore | undefined = undefined;

aboutToAppear(): void {
    // 使用ArkData RDB数据库 获取用户数据
    // 构造StoreConfig对象
    const STORE_CONFIG: relationalStore.StoreConfig = {
      name: 'RdbTest.db', // 数据库文件名
      securityLevel: relationalStore.SecurityLevel.S1, // 数据库安全级别
      encrypt: false, // 可选参数,指定数据库是否加密,默认不加密
      customDir: 'customDir/subCustomDir', // 可选参数,数据库自定义路径。数据库将在如下的目录结构中被创建:context.databaseDir + '/rdb/' + customDir,其中context.databaseDir是应用沙箱对应的路径,'/rdb/'表示创建的是关系型数据库,customDir表示自定义的路径。当此参数不填时,默认在本应用沙箱目录下创建RdbStore实例。
      isReadOnly: false // 可选参数,指定数据库是否以只读方式打开。该参数默认为false,表示数据库可读可写。该参数为true时,只允许从数据库读取数据,不允许对数据库进行写操作,否则会返回错误码801。
    };

    // 获得一个相关的RdbStore,操作关系型数据库,用户可以根据自己的需求配置RdbStore的参数,然后通过RdbStore调用相关接口可以执行相关的数据操作,使用callback异步回调。
    relationalStore.getRdbStore(this.context, STORE_CONFIG, (err, store) => {
      if (err) {
      console.error(`At HomePage Failed to get RdbStore. Code:${err.code}, message:${err.message}`);
      return;
      }
      console.info('At HomePage Succeeded in getting RdbStore.');
      // 当数据库创建时,数据库已更新版本为1
      // 赋值给当前的rdbStore对象,确保获取到RdbStore实例后,再进行数据库的增、删、改、查等操作
      if (store.version === 1) {
      this.rdbStore = store
      // 测试查询出来看看
      //let clientUserPredicate = new relationalStore.RdbPredicates('CLIENT_USER') // CLIENT_USER 为表名称
      this.rdbStore.querySql('SELECT ID,ACCOUNT,PASSWORD FROM CLIENT_USER ORDER BY ID DESC;',
          (err: BusinessError, resultSet) => {
            if (err) {
            console.error(`LoginPage 读取数据库表失败,错误代码:${err.code},错误信息为:${err.message}`)
            return
            }
            console.info(`读取的结果为(对象形式): ${resultSet}`)
            // resultSet是一个数据集合的游标,默认指向第-1个记录,有效的数据从0开始。
            while (resultSet.goToNextRow()) {
            const id = resultSet.getLong(resultSet.getColumnIndex('ID'));
            const account = resultSet.getLong(resultSet.getColumnIndex('ACCOUNT'));
            const passwd = resultSet.getString(resultSet.getColumnIndex('PASSWORD'));
            console.info(`id=${id}, account=${account}, passwd=${passwd}`);
            let userInfo = new UserInfo(id, account.toString(), passwd)
            this.listUserInfo.push(userInfo)
            }
            // 释放数据集的内存
            resultSet.close();
          })
      }
    });
}

build() {
    Column() {
      Text(this.message)
      .fontSize(30)
      .width('100%')
      .height(50)
      .textAlign(TextAlign.Center)
      .backgroundColor(0xF1F3F5)

      Blank().height(10)

      List({ initialIndex: 0, space: 5 }) {
      ForEach(this.listUserInfo, (user: UserInfo, index: number) => {
          ListItem() {
            Row() {
            Column() {
                Image($r('app.media.goods_icon_svg')).width(80).height(60)
            }.width('10%')

            Column() {
                Text(`ID: ${user.id}`).width('100%').padding({left: 10})
                Text(`账号: ${user.account}`).width('100%').padding({left: 10})
                Text(`密码:${user.password}`).width('100%').padding({left: 10})
            }.width('90%')
            }
            .alignItems(VerticalAlign.Center)
            .width('100%')
          }
          .height(80)
          .width('100%')
          .backgroundColor(0xF1F3F5)

      }, (item: UserInfo, index: number) => index.toString())
      }
      .height('80%')
      .width('100%')

      Button('返回上一页')
      .width('80%')
      .margin({ top: 10 })
      .height(50)
      .onClick(() => {
          // 返回登录页面
          router.showAlertBeforeBackPage({ message: '确认返回上一页吗?' })
          router.back({
            url: 'pages/LoginPage',
            params: {
            msg: 'homepage'
            }
          })
      })
    }
    .height('100%')
    .width('100%')
}
}

测试步骤如下:

[*] 打开模拟器,并将代码部署到模拟器上,当模拟器正常运行代码后,输入用户名和密码:
https://i-blog.csdnimg.cn/direct/327b278601604bf4a98b0b82d54a921b.png
[*] 点击手机模拟器上应用的登录按钮,跳转到了首页,然后再从首页返回登录页,查看控制台日志,内容如截图赤色框或者首页文字所示:
https://i-blog.csdnimg.cn/direct/59b79dd6d40c4970b9deceef033ef321.png
四、小结

通过上述的说明和示例演示,相信大家已经很清楚关系型数据库RDS读写数据的用法了。细心的读者朋友大概会问,怎样删除数据呢?或者有何约束和限定呢?感兴趣的读者朋友可以对照鸿蒙官网上的示例尝试下,欢迎大家的留言,我们在留言区进行讨论。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页: [1]
查看完整版本: 【鸿蒙HarmonyOS NEXT】数据存储之关系型数据库RDS