关系型数据库基于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组件。
鸿蒙才起步,后续希望官方也能跟进推出这种好用的组件。