Harmonyos next:久化数据之关系型数据库(sql)

250 阅读4分钟

关系型数据库基于SQLite组件,适用于存储包含复杂关系数据的场景,比如一个班级的学生信息,需要包括姓名、学号、各科成绩等,又或者公司的雇员信息,需要包括姓名、工号、职位等,由于数据之间有较强的对应关系,复杂程度比键值型数据更高,此时需要使用关系型数据库来持久化保存数据。

一、场景介绍

需要面向对象的数据,比如一个喝水管理的类,有当前饮水量,目标量,单次量,水资源类型,喝水时间等字段属性。当需要保存喝水记录时,每次记录是一个喝水管理对象,这时候就需要使用关系型数据库。

二、和K-V键值对区别

k-v是比较简单的对应关系,比如上诉的喝水管理,如果时间只对应喝水量这么简单直接的关系的就可以用键值对。但是如果时间还需要对应其他的如单次量、类型等使用k-v就没法对应或者特别麻烦。

但是也不是说关系型数据库总是最优,在一些简单场景k-v更优选,因为k-v使用起来更简单。比如单纯记录总喝水量,用之前提到的preference的k-v就更简单,反之用关系型数据库就大材小用了。

三、如何使用(以喝水记录为例)

3.1建数据库

  createDb(context: Context) {
    const STORE_CONFIG: relationalStore.StoreConfig = {
      name: this.DB_Name, // 数据库文件名
      securityLevel: relationalStore.SecurityLevel.S3, // 数据库安全级别
      encrypt: false, // 可选参数,指定数据库是否加密,默认不加密
      customDir: 'customDir/subCustomDir', // 可选参数,数据库自定义路径。数据库将在如下的目录结构中被创建:context.databaseDir + '/rdb/' + customDir,其中context.databaseDir是应用沙箱对应的路径,'/rdb/'表示创建的是关系型数据库,customDir表示自定义的路径。当此参数不填时,默认在本应用沙箱目录下创建RdbStore实例。
      isReadOnly: false // 可选参数,指定数据库是否以只读方式打开。该参数默认为false,表示数据库可读可写。该参数为true时,只允许从数据库读取数据,不允许对数据库进行写操作,否则会返回错误码801。
    };
​
    // 判断数据库版本,如果不匹配则需进行升降级操作
    // 假设当前数据库版本为3,表结构:RECORD (DRINKIMAGE, TIME, COUNT, NAME, IDENTITY)
    const SQL_CREATE_TABLE =
      'CREATE TABLE IF NOT EXISTS WATER_RECORD (ID INTEGER PRIMARY KEY AUTOINCREMENT, DRINKIMAGE TEXT NOT NULL,TIME TEXT NOT NULL, COUNT INTEGER,  NAME TEXT NOT NULL)';
​
    relationalStore.getRdbStore(context, STORE_CONFIG, (err, store) => {
      if (err) {
        logger.logE(`Failed to get RdbStore. Code:${err.code}, message:${err.message}`);
        return;
      }
      logger.logI('Succeeded in getting RdbStore.');
      this.store = store
      // 当数据库创建时,数据库默认版本为0
      if (store.version === 0) {
        store.executeSql(SQL_CREATE_TABLE); // 创建数据表
        // 设置数据库的版本,入参为大于0的整数
        // store.version = 3;
      }
    });
  }

3.2建表

    const SQL_CREATE_TABLE =
      'CREATE TABLE IF NOT EXISTS WATER_RECORD (ID INTEGER PRIMARY KEY AUTOINCREMENT, DRINKIMAGE TEXT NOT NULL,TIME TEXT NOT NULL, COUNT INTEGER,  NAME TEXT NOT NULL)';
​
    relationalStore.getRdbStore(context, STORE_CONFIG, (err, store) => {
      if (err) {
        logger.logE(`Failed to get RdbStore. Code:${err.code}, message:${err.message}`);
        return;
      }
      logger.logI('Succeeded in getting RdbStore.');
      this.store = store
      // 当数据库创建时,数据库默认版本为0
      if (store.version === 0) {
        store.executeSql(SQL_CREATE_TABLE); // 创建数据表
        // 设置数据库的版本,入参为大于0的整数
        // store.version = 3;
      }
    });

3.3提供增删查改接口

//插入数据  
insert(waterRecord: WaterRecord) {
    let value1 = waterRecord.drinkImage;
    let value2 = waterRecord.time;
    let value3 = waterRecord.count;
    let value4 = waterRecord.name;
    const valueBucket: relationalStore.ValuesBucket = {
      'DRINKIMAGE': value1,
      'TIME': value2,
      'COUNT': value3,
      'NAME': value4,
    };
​
    if (this.store !== undefined) {
      (this.store as relationalStore.RdbStore).insert(this.TABLE_NAME, valueBucket,
        (err: BusinessError, rowId: number) => {
          if (err) {
            logger.error(`Failed to insert data. Code:${err.code}, message:${err.message}`);
            return;
          }
          logger.info(`Succeeded in inserting data. rowId:${rowId}`);
        })
    }
  }
  // 删除数据
  delete(waterRecord: WaterRecord) {
    let predicates1 = new relationalStore.RdbPredicates(this.TABLE_NAME);
    predicates1.equalTo('TIME', waterRecord.time);
    if (this.store !== undefined) {
      (this.store as relationalStore.RdbStore).delete(predicates1, (err: BusinessError, rows: number) => {
        if (err) {
          logger.error(`Failed to delete data. Code:${err.code}, message:${err.message}`);
          return;
        }
        logger.info(`Delete rows: ${rows}`);
      })
    }
  }
//查询数据
async queryAll(): Promise<Array<WaterRecord>> {
    let array = new Array<WaterRecord>()
    let predicates2 = new relationalStore.RdbPredicates(this.TABLE_NAME);
    if (this.store !== undefined) {
      const resultSet =
        await (this.store as relationalStore.RdbStore).query(predicates2, ['ID', 'DRINKIMAGE', 'TIME', 'COUNT', 'NAME'])
      logger.info(`ResultSet column names: ${resultSet.columnNames}, column count: ${resultSet.columnCount}`);
      // resultSet是一个数据集合的游标,默认指向第-1个记录,有效的数据从0开始。
      while (resultSet.goToNextRow()) {
        const id = resultSet.getLong(resultSet.getColumnIndex('ID'));
        const image = resultSet.getString(resultSet.getColumnIndex('DRINKIMAGE'));
        const time = resultSet.getString(resultSet.getColumnIndex('TIME'));
        const count = resultSet.getDouble(resultSet.getColumnIndex('COUNT'));
        const name = resultSet.getString(resultSet.getColumnIndex('NAME'));
        logger.info(`id=${id}, DRINKIMAGE=${image}, TIME=${time}, COUNT=${count}, NAME=${name}`);
        const waterRecord = new WaterRecord(image, name, count, time)
        array.push(waterRecord)
      }
      // 释放数据集的内存
      resultSet.close();
      return array
    }
    return array
  }

四、和安卓的room数据库对比

什么是Room数据库?

是Google官方推出的持久性库,用于在SQLite数据库上提供一个抽象层,并在运行时进行编译检查。

为什么要使用Room数据库?

与直接使用SQLite相比,Room提供了更强大的功能和更简洁的代码,包括编译时SQL语句验证、方便的对象关系映射(ORM)和响应式查询支持。

下面以建表和查询为例子

建表

@Entity(tableName = "student")
data class Student(
    val name: String,
    val age:String,
) {
    @PrimaryKey(autoGenerate = true)
    var id:Long = 0L
}

查询

@Query("select * from student where id = :id ")
fun findById(id:Long): Student

可以看到比直接用sql方便很多。

其实安卓也是最开始从手写sql到民间封装的一些三方库到现在官方正式推出room组件。

鸿蒙才起步,后续希望官方也能跟进推出这种好用的组件。