数据模型——饮食记录
本次实验完成饮食记录的数据模型,如下图所示
该饮食记录模型与上次的记录项数据模式定义处理方式相同,我们首先分析其数据结构,我们发现首先有早餐、午餐、晚餐等记录类型数据模型,其包括了id、类型名称、类型图标、类型推荐最小和最大卡路里等。在每个类型中又可以添加记录项,记录项也有id、类型id、卡路里总数、和上节课定义的记录项相同的数据项和食品数量或运动时长等组成的记录项数据模型。
记录类型数据模型
export default class RecordType{/*** 类型id*/id: number/*** 类型名称*/name: ResourceStr/*** 类型图标*/icon: ResourceStr/*** 类型推荐最小卡路里*/min: number/*** 类型推荐最大卡路里*/max: numberconstructor(id: number, name: ResourceStr, icon: ResourceStr, min: number = 0, max: number = 0) {this.id = idthis.name = namethis.icon = iconthis.min = minthis.max = max}
}
可以看出min和max设置了默认可选值,因为当类型为运动时,没有类型推荐最小卡路里、类型推荐最大卡路里
import RecordType from '../viewmodel/RecordType'enum RecordTypeEnum {/*** 早餐*/BREAKFAST,/*** 午餐*/LUNCH,/*** 晚餐*/DINNER,/*** 加餐*/EXTRA_MEAL,/*** 运动*/WORKOUT
}/*** 记录类型常量*/
const RecordTypes: RecordType[] = [new RecordType(0, $r("app.string.breakfast"), $r("app.media.ic_breakfast"), 423, 592),new RecordType(1, $r("app.string.lunch"), $r("app.media.ic_lunch"), 592, 761),new RecordType(2, $r("app.string.dinner"), $r("app.media.ic_dinner"), 423, 592),new RecordType(3, $r("app.string.extra_meal"), $r("app.media.ic_extra_m"), 0, 169),new RecordType(4, $r("app.string.workout"), $r("app.media.ic_workout")),
]export {RecordTypes, RecordTypeEnum}
这里设置RecordTypeEnum枚举的作用与上次相同,有助于每一项数据项能方便的找到其所在的类型。
记录项数据模型
import RecordItem from './RecordItem'/*** 饮食记录的页面数据模型*/
export default class RecordVO {/*** 记录id*/id: number/*** 饮食记录类型*/typeId: number/*** 卡路里总数*/calorie: number/*** 记录中的食物或运动信息*/recordItem: RecordItem/*** 食物数量或运动时长,如果是运动信息则无*/amount: number = 0
}
在其中recordItem的类型是上次我们定义的RecordItem数据模型,方便我们直接使用上次定义的数据模型中的数据。
如图所示,RecordModel我们采用了关系型数据库来保存记录项的数据,通过关系型数据库来进行数据操作,这样做的原因是因为,这部分数据是可以根据日期进行查询的,还可以执行删除操作,所以不能像其他数据一样保存在内存中,必须保存在关系型数据库中才能进行
持久化保存。
为了操作数据库方便,我们定义了数据库通用工具类
import common from '@ohos.app.ability.common';
import relationalStore from '@ohos.data.relationalStore';
import { ColumnInfo, ColumnType } from '../bean/ColumnInfo';
import Logger from './Logger';const DB_FILENAME: string = 'HeiMaHealthy.db'class DbUtil {rdbStore: relationalStore.RdbStoreinitDB(context: common.UIAbilityContext): Promise<void> {let config: relationalStore.StoreConfig = {name: DB_FILENAME,securityLevel: relationalStore.SecurityLevel.S1}return new Promise<void>((resolve, reject) => {relationalStore.getRdbStore(context, config).then(rdbStore => {this.rdbStore = rdbStoreLogger.debug('rdbStore 初始化完成!')resolve()}).catch(reason => {Logger.debug('rdbStore 初始化异常', JSON.stringify(reason))reject(reason)})})}createTable(createSQL: string): Promise<void> {return new Promise((resolve, reject) => {this.rdbStore.executeSql(createSQL).then(() => {Logger.debug('创建表成功', createSQL)resolve()}).catch(err => {Logger.error('创建表失败,' + err.message, JSON.stringify(err))reject(err)})})}
//添加通用新增方法insert(tableName:string,obj:any,columns:ColumnInfo[]){return new Promise((resolve,reject)=>{//1.构建新增的数据let value=this.buildValueBucket(obj,columns)//2.新增this.rdbStore.insert(tableName,value,(err,id)=>{if (err) {Logger.debug('新增失败!',JSON.stringify(err))reject(err)}else {Logger.debug('新增成功!新增id:',id.toString())resolve(id)}})})}//通用删除delete(predicates:relationalStore.RdbPredicates){return new Promise((resolve,reject)=>{//1.删除this.rdbStore.delete(predicates,(err,rows)=>{if (err) {Logger.debug('删除失败!',JSON.stringify(err))reject(err)}else {Logger.debug('删除成功!删除行数:',rows.toString())resolve(rows)}})})}//批量查询queryForList<T>(predicates:relationalStore.RdbPredicates,columns:ColumnInfo[]):Promise<T[]>{return new Promise((resolve,reject)=>{//1.删除this.rdbStore.query(predicates,columns.map(info=>info.columnName),(err,result)=>{if (err) {Logger.debug('查询失败!',JSON.stringify(err))reject(err)}else {Logger.debug('查询成功!查询行数:',result.rowCount.toString())resolve(this.parseResultSet(result,columns))}})})}//解析resultSETparseResultSet<T>(result:relationalStore.ResultSet,columns:ColumnInfo[]):T[]{//1.声明最终返回结果let arr=[]//2.判断是否有结果if (result.rowCount<=0) {return arr;}//3.处理结果while (!result.isAtLastRow) {//3.1 去下一行result.goToNextRow()//3.2 解析该行数据,转为结果对象格式let obj={}columns.forEach(info=>{let val=nullswitch (info.type){case ColumnType.LONG:val=result.getLong(result.getColumnIndex(info.columnName))break;case ColumnType.DOUBLE:val=result.getDouble(result.getColumnIndex(info.columnName))break;case ColumnType.STRING:val=result.getString(result.getColumnIndex(info.columnName))break;case ColumnType.BLOB:val=result.getBlob(result.getColumnIndex(info.columnName))break;}obj[info.name]=val;})//3.3 将对象填充到数组arr.push(obj)}return arr;}buildValueBucket(obj:any,columns:ColumnInfo[]):relationalStore.ValuesBucket{//1.构建数据let value={}columns.forEach(info=>{let val=obj[info.name]if (typeof val !=='undefined') {value[info.columnName]=val}})return value
}}let dbUtil: DbUtil = new DbUtil();export default dbUtil as DbUtil
可以看出,我们只需要提供DBUtil中所需方法的参数,即可进行增删改查操作,适用于所有数据库。但是想要实现该功能,需要在EntryAbility中初始化RDB工具和数据库中表的建立:
async onCreate(want, launchParam) {//1.加载用户首选项PreferenceUtil.loadPreference(this.context)//2.初始化日期AppStorage.SetOrCreate(CommonConstants.RECORD_DATE,DateUtil.beginTimeOfDay(new Date()))//3.初始化RDB工具await DbUtil.initDB(this.context)//4.创建record表的创建DbUtil.createTable(RecordModel.getCreateTableSql())hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onCreate');}
在完成这样后我们便可以在RecordModel中调用DBUtil所提供的方法,进行record数据表的增删改查操作,从而完成各种业务逻辑。
观察下面的代码,const CREATE_TABLE_SQL提供给了EntryAbility进行数据库中record表的建立,而且只有列名与数据库列名的映射正确,才能使自己定义的数据模型能正确使用数据库。
/*** 数据库建表语句*/
import relationalStore from '@ohos.data.relationalStore'
import { ColumnInfo, ColumnType } from '../common/bean/ColumnInfo'
import RecordPO from '../common/bean/RecordPO'
import DbUtil from '../common/utils/DbUtil'
const CREATE_TABLE_SQL: string = `CREATE TABLE IF NOT EXISTS record (id INTEGER PRIMARY KEY AUTOINCREMENT,type_id INTEGER NOT NULL,item_id INTEGER NOT NULL,amount DOUBLE NOT NULL,create_time INTEGER NOT NULL)`
//列名与数据库列名的映射
const COLUMNS: ColumnInfo[] = [{name: 'id', columnName: 'id', type: ColumnType.LONG},{name: 'typeId', columnName: 'type_id', type: ColumnType.LONG},{name: 'itemId', columnName: 'item_id', type: ColumnType.LONG},{name: 'amount', columnName: 'amount', type: ColumnType.DOUBLE},{name: 'createTime', columnName: 'create_time', type: ColumnType.LONG}
]const TABLE_NAME = 'record'
const ID_COLUMN = 'id'
const DATE_COLUMN = 'create_time'class RecordModel {getCreateTableSql(): string {return CREATE_TABLE_SQL}
//插入insert(record:RecordPO){return DbUtil.insert(TABLE_NAME,record,COLUMNS)}
//删除deleteById(id:number){//1.删除条件let predicates=new relationalStore.RdbPredicates(TABLE_NAME)predicates.equalTo(ID_COLUMN,id)//2.删除return DbUtil.delete(predicates)}//查找(按日期)listByDate(date:number){//1.查询条件let predicates=new relationalStore.RdbPredicates(TABLE_NAME)predicates.equalTo(DATE_COLUMN,date)//2.查询DbUtil.queryForList(predicates,COLUMNS)}
}let recordModel = new RecordModel()export default recordModel as RecordModel
下次课我们将完成利用此数据模型,实现保存不同日期的饮食记录的数据保存功能。