HarmonyOS应用开发实战:猫猫大作战-relationalStore 建库建表、增删改查 SQL、事务批量插入、与 Preference 键值取
2026/7/28 0:21:49 网站建设 项目流程

前言

前面我们用 HTTP 拉排行榜、上报得分——但应用关闭后本地数据全没了,下次开要重新拉。战绩历史、玩家昵称、皮肤解锁这种结构化持久数据要存本地数据库。HarmonyOS 提供@ohos.data.relationalStore(SQLite 封装)做关系型存储——建表、插入、查询、更新、删除,SQL 语法齐全。

本篇以「猫猫大作战」战绩历史表(建表 + 插入 + 查询 Top 50 + 删除)为锚点,把relationalStore 建库建表增删改查 SQL事务批量插入与 Preference 键值取舍四大要点讲透。

提示:本系列不讲 ArkTS 基础语法与环境搭建,假设你已跟完第 1–63 篇。本篇是阶段四第四篇。

一、场景拆解:战绩历史持久化

「猫猫大作战」战绩历史(第 47、48 篇 LazyForEach 用过GameRecord[])当前是内存数组——应用关就没了。玩家想本地持久化战绩

  • 每局结束插入一条记录(得分/时间/连击/日期)。
  • 战绩页查询最近 50 条(按日期降序)。
  • 删除单条战绩(误录清除)。
  • 批量插入历史导入(从服务器拉 100 条一次写入)。
// 预演:HistoryDb 建表 + CRUD import { relationalStore } from '@kit.ArkData'; export class HistoryDb { private store: relationalStore.RdbStore | null = null; private readonly dbName: string = 'catgame.db'; private readonly tableName: string = 'history'; async init(context: Context): Promise<void> { const config: relationalStore.StoreConfig = { name: this.dbName, securityLevel: relationalStore.SecurityLevel.S1 }; this.store = await relationalStore.getRdbStore(context, config); await this.createTable(); } private async createTable(): Promise<void> { const sql = `CREATE TABLE IF NOT EXISTS ${this.tableName} ( id INTEGER PRIMARY KEY AUTOINCREMENT, score INTEGER NOT NULL, duration INTEGER NOT NULL, max_combo INTEGER NOT NULL, merge_count INTEGER NOT NULL, highest_level INTEGER NOT NULL, date TEXT NOT NULL )`; await this.store!.executeSql(sql); } // 插入 async insert(record: Omit<GameRecord, 'id'>): Promise<number> { const values: relationalStore.ValuesBucket = { 'score': record.score, 'duration': record.duration, 'max_combo': record.maxCombo, 'merge_count': record.mergeCount, 'highest_level': record.highestLevel, 'date': record.date }; const rowId = await this.store!.insert(this.tableName, values); return rowId; } // 查询最近 50 条 async queryRecent(limit: number = 50): Promise<GameRecord[]> { const sql = `SELECT * FROM ${this.tableName} ORDER BY date DESC LIMIT ?`; const result = await this.store!.querySql(sql, [limit]); const records: GameRecord[] = []; while (result.goToNextRow()) { records.push(this.mapRow(result)); } return records; } // 删除单条 async delete(id: number): Promise<void> { const predicates = new relationalStore.RdbPredicates(this.tableName); predicates.equalTo('id', id); await this.store!.delete(predicates); } private mapRow(result: relationalStore.ResultSet): GameRecord { return { id: result.getLong(result.getColumnIndex('id')), score: result.getLong(result.getColumnIndex('score')), duration: result.getLong(result.getColumnIndex('duration')), maxCombo: result.getLong(result.getColumnIndex('max_combo')), mergeCount: result.getLong(result.getColumnIndex('merge_count')), highestLevel: result.getLong(result.getColumnIndex('highest_level')), date: result.getString(result.getColumnIndex('date')) }; } }

核心问题

  1. relationalStore怎么建库建表?
  2. 增删改查四种 SQL 怎么用?参数化查询怎么防注入?
  3. 事务怎么批量插入?
  4. SQLite 和 Preference 键值存储怎么选?

二、relationalStore 建库建表

2.1 申请数据存储权限

修改entry/src/main/module.json5

{ "module": { "requestPermissions": [ { "name": "ohos.permission.data.relationalStore", "reason": "$string:reason_database", "usedScene": { "abilities": ["EntryAbility"], "when": "always" } } ] } }

提示:某些 HarmonyOS 版本 relationalStore 不需要单独权限——应用沙盒内数据库自动授权。以实际 SDK 为准,权限报错时补申请。

2.2 创建 RdbStore

import { relationalStore } from '@kit.ArkData'; const config: relationalStore.StoreConfig = { name: 'catgame.db', // 数据库文件名 securityLevel: relationalStore.SecurityLevel.S1 // 安全等级 }; const store: relationalStore.RdbStore = await relationalStore.getRdbStore(context, config);

config 字段

字段含义推荐值
name数据库文件名(.db)<app>.db
securityLevel安全等级(S1–S4)S1(最低,普通数据)

安全等级

等级含义适合
S1开放(低)公开数据(排行榜缓存)
S2普通用户数据(战绩、昵称)
S3个人敏感身份信息
S4严格机密密码、财务

关键经验「战绩/昵称」用 S2,「公开缓存」用 S1——不要全部 S4,否则备份/迁移受限。

2.3 建表 SQL

const sql = `CREATE TABLE IF NOT EXISTS history ( id INTEGER PRIMARY KEY AUTOINCREMENT, score INTEGER NOT NULL, duration INTEGER NOT NULL, max_combo INTEGER NOT NULL, merge_count INTEGER NOT NULL, highest_level INTEGER NOT NULL, date TEXT NOT NULL )`; await store.executeSql(sql);

SQL 拆解

片段含义
CREATE TABLE IF NOT EXISTS建表,存在则跳过(幂等)
history表名
id INTEGER PRIMARY KEY AUTOINCREMENT主键自增
score INTEGER NOT NULL整数字段,非空
date TEXT NOT NULL文本字段,非空

关键经验建表用IF NOT EXISTS——重启应用多次建表幂等,不报错。

2.4 SQLite 字段类型

SQLite 是动态类型,但建表仍推荐声明:

声明实际存储适合
INTEGER整数得分、时长、连击
TEXT文本日期、昵称、UUID
REAL浮点评分、坐标
BLOB二进制头像、序列化数据

三、增删改查 SQL

3.1 增:insert

// 方式 A:API insert(推荐,防注入) const values: relationalStore.ValuesBucket = { 'score': 1500, 'duration': 180, 'max_combo': 5, 'merge_count': 28, 'highest_level': 3, 'date': '2026-07-24T10:30:00Z' }; const rowId = await store.insert('history', values); // 方式 B:原始 SQL(要手动参数化防注入) const sql = `INSERT INTO history (score, duration, max_combo, merge_count, highest_level, date) VALUES (?, ?, ?, ?, ?, ?)`; await store.executeSql(sql, [1500, 180, 5, 28, 3, '2026-07-24T10:30:00Z']);

关键经验插入用store.insert(tableName, values)API——自动参数化防注入,不用拼 SQL。

3.2 查:querySql

// 查询最近 50 条(参数化) const sql = `SELECT * FROM history ORDER BY date DESC LIMIT ?`; const result = await store.querySql(sql, [50]); const records: GameRecord[] = []; while (result.goToNextRow()) { records.push({ id: result.getLong(result.getColumnIndex('id')), score: result.getLong(result.getColumnIndex('score')), /* ... */ }); } return records;

ResultSet 遍历

API作用
result.goToNextRow()移到下一行,返回 false 表示结束
result.getColumnIndex('name')取字段索引
result.getLong(idx)/getString(idx)取字段值

关键经验查询用querySql+ 参数化——LIMIT ?[50],不拼字符串防注入。

3.3 改:update

// 方式 A:RdbPredicates(推荐) const predicates = new relationalStore.RdbPredicates('history'); predicates.equalTo('id', 5); const values: relationalStore.ValuesBucket = { 'score': 2000 }; const changedRows = await store.update(predicates, values); // 方式 B:原始 SQL const sql = `UPDATE history SET score = ? WHERE id = ?`; await store.executeSql(sql, [2000, 5]);

3.4 删:delete

// 方式 A:RdbPredicates(推荐) const predicates = new relationalStore.RdbPredicates('history'); predicates.equalTo('id', 5); const deletedRows = await store.delete(predicates); // 方式 B:原始 SQL const sql = `DELETE FROM history WHERE id = ?`; await store.executeSql(sql, [5]);

3.5 RdbPredicates 条件构造

const predicates = new relationalStore.RdbPredicates('history'); // 等于 predicates.equalTo('score', 1500); // 大于/小于 predicates.greaterThan('score', 1000); predicates.lessThan('duration', 300); // 范围 predicates.between('score', 1000, 2000); // 模糊匹配 predicates.like('date', '2026-07-%'); // 排序 + 分页 predicates.orderByAsc('date'); // 升序 predicates.orderByDesc('score'); // 降序 predicates.limitAs(50); // 取 50 条 predicates.offsetAs(10); // 跳过 10 条

关键经验RdbPredicates 链式构造条件——比手写 SQL 安全(自动参数化),可读性强。

四、事务批量插入

4.1 为什么要事务

场景:从服务器拉 100 条历史导入本地——单条 insert 100 次,中途某条失败要回滚已插的,否则数据不一致。

事务保证:要么全成功,要么全回滚。

4.2 事务 API

await store.beginTransaction(); // 开启事务 try { for (const record of records) { await store.insert('history', { 'score': record.score, 'duration': record.duration, /* ... */ }); } await store.commit(); // 提交(全成功才持久化) } catch (error) { await store.rollBack(); // 回滚(失败恢复到事务前) throw error; }

4.3 性能对比

方式100 条插入耗时原因
单条 insert × 100~2s每条都触发磁盘 IO + 索引更新
事务批量~50ms一次磁盘 IO,索引一次更新

关键经验批量插入必用事务——性能提升 40 倍,且保证原子性。

五、实战:HistoryDb 完整实现

5.1 创建 HistoryDb

新建entry/src/main/ets/services/HistoryDb.ets

import { relationalStore } from '@kit.ArkData'; import { GameRecord, CatLevel } from '../components/GameTypes'; export class HistoryDb { private store: relationalStore.RdbStore | null = null; private readonly dbName: string = 'catgame.db'; private readonly tableName: string = 'history'; async init(context: Context): Promise<void> { const config: relationalStore.StoreConfig = { name: this.dbName, securityLevel: relationalStore.SecurityLevel.S2 }; this.store = await relationalStore.getRdbStore(context, config); await this.createTable(); } private async createTable(): Promise<void> { const sql = `CREATE TABLE IF NOT EXISTS ${this.tableName} ( id INTEGER PRIMARY KEY AUTOINCREMENT, score INTEGER NOT NULL, duration INTEGER NOT NULL, max_combo INTEGER NOT NULL, merge_count INTEGER NOT NULL, highest_level INTEGER NOT NULL, date TEXT NOT NULL )`; await this.store!.executeSql(sql); } // 插入单条 async insert(record: Omit<GameRecord, 'id'>): Promise<number> { const values: relationalStore.ValuesBucket = { 'score': record.score, 'duration': record.duration, 'max_combo': record.maxCombo, 'merge_count': record.mergeCount, 'highest_level': record.highestLevel, 'date': record.date }; return await this.store!.insert(this.tableName, values); } // 事务批量插入(服务器导入用) async batchInsert(records: Omit<GameRecord, 'id'>[]): Promise<void> { await this.store!.beginTransaction(); try { for (const record of records) { const values: relationalStore.ValuesBucket = { 'score': record.score, 'duration': record.duration, 'max_combo': record.maxCombo, 'merge_count': record.mergeCount, 'highest_level': record.highestLevel, 'date': record.date }; await this.store!.insert(this.tableName, values); } await this.store!.commit(); } catch (error) { await this.store!.rollBack(); throw error; } } // 查询最近 N 条(战绩页用) async queryRecent(limit: number = 50, offset: number = 0): Promise<GameRecord[]> { const predicates = new relationalStore.RdbPredicates(this.tableName); predicates.orderByDesc('date'); predicates.limitAs(limit); predicates.offsetAs(offset); const result = await this.store!.query(predicates); return this.mapResultSet(result); } // 查询最高分(统计) async queryHighestScore(): Promise<number> { const sql = `SELECT MAX(score) as max_score FROM ${this.tableName}`; const result = await this.store!.querySql(sql); if (result.goToNextRow()) { return result.getLong(result.getColumnIndex('max_score')) || 0; } return 0; } // 查询总场数 async queryTotalCount(): Promise<number> { const sql = `SELECT COUNT(*) as total FROM ${this.tableName}`; const result = await this.store!.querySql(sql); if (result.goToNextRow()) { return result.getLong(result.getColumnIndex('total')); } return 0; } // 按得分范围查询 async queryByScoreRange(min: number, max: number): Promise<GameRecord[]> { const predicates = new relationalStore.RdbPredicates(this.tableName); predicates.between('score', min, max); predicates.orderByDesc('score'); const result = await this.store!.query(predicates); return this.mapResultSet(result); } // 更新单条 async update(id: number, updates: Partial<GameRecord>): Promise<number> { const predicates = new relationalStore.RdbPredicates(this.tableName); predicates.equalTo('id', id); const values: relationalStore.ValuesBucket = {}; if (updates.score !== undefined) values['score'] = updates.score; if (updates.duration !== undefined) values['duration'] = updates.duration; if (updates.maxCombo !== undefined) values['max_combo'] = updates.maxCombo; if (updates.mergeCount !== undefined) values['merge_count'] = updates.mergeCount; if (updates.highestLevel !== undefined) values['highest_level'] = updates.highestLevel; return await this.store!.update(predicates, values); } // 删除单条 async delete(id: number): Promise<number> { const predicates = new relationalStore.RdbPredicates(this.tableName); predicates.equalTo('id', id); return await this.store!.delete(predicates); } // 删除所有(清空表) async deleteAll(): Promise<number> { const predicates = new relationalStore.RdbPredicates(this.tableName); return await this.store!.delete(predicates); } // 删除 30 天前的旧记录(清理) async deleteOlderThan(days: number): Promise<number> { const cutoffDate = new Date(Date.now() - days * 24 * 60 * 60 * 1000).toISOString(); const sql = `DELETE FROM ${this.tableName} WHERE date < ?`; return await this.store!.executeSql(sql, [cutoffDate]); } private mapResultSet(result: relationalStore.ResultSet): GameRecord[] { const records: GameRecord[] = []; while (result.goToNextRow()) { records.push(this.mapRow(result)); } return records; } private mapRow(result: relationalStore.ResultSet): GameRecord { return { id: result.getLong(result.getColumnIndex('id')), score: result.getLong(result.getColumnIndex('score')), duration: result.getLong(result.getColumnIndex('duration')), maxCombo: result.getLong(result.getColumnIndex('max_combo')), mergeCount: result.getLong(result.getColumnIndex('merge_count')), highestLevel: result.getLong(result.getColumnIndex('highest_level')) as CatLevel, date: result.getString(result.getColumnIndex('date')) }; } // 销毁(关数据库连接) async close(): Promise<void> { if (this.store) { this.store = null; // 简化:实际可能有 release/close API } } }

5.2 Index 结束游戏时插入战绩

// 来源:entry/src/main/ets/pages/Index.ets endGame(SQLite 改造后) import { HistoryDb } from '../services/HistoryDb'; import { common } from '@kit.AbilityKit'; @Entry @Component struct Index { /* ... 其他 state */ private historyDb: HistoryDb = new HistoryDb(); private context: common.UIAbilityContext = getContext(this) as common.UIAbilityContext; aboutToAppear() { this.historyDb.init(this.context); // 初始化数据库 } endGame() { this.gameState = GameState.GAME_OVER; this.maxCombo = this.gameEngine.getMaxCombo(); this.mergeCount = this.gameEngine.getMergeCount(); this.highestLevel = this.gameEngine.getHighestLevel(); if (this.score > this.highScore) { this.highScore = this.score; } this.clearTimers(); // 异步插入战绩到 SQLite(不阻塞 UI) this.saveHistory(); } async saveHistory() { try { await this.historyDb.insert({ score: this.score, duration: this.gameTime, maxCombo: this.maxCombo, mergeCount: this.mergeCount, highestLevel: this.highestLevel, date: new Date().toISOString() }); console.info('战绩保存成功'); } catch (error) { console.error(`战绩保存失败: ${(error as Error).message}`); } } aboutToDisappear() { this.clearTimers(); this.historyDb.close(); } /* ... 其他方法 */ }

5.3 改造 LeaderboardPage 显示本地战绩

// 来源:entry/src/main/ets/pages/LeaderboardPage.ets(SQLite 改造后) import { GameRecord, CatLevel } from '../components/GameTypes'; import { HttpService } from '../services/HttpService'; import { HistoryDb } from '../services/HistoryDb'; import { common } from '@kit.AbilityKit'; @Component export struct LeaderboardPage { @State history: GameRecord[] = []; @State loading: boolean = false; @State errorMsg: string = ''; private httpService: HttpService = new HttpService(); private historyDb: HistoryDb = new HistoryDb(); private context: common.UIAbilityContext = getContext(this) as common.UIAbilityContext; aboutToAppear() { this.historyDb.init(this.context).then(() => { this.loadHistory(); }); } aboutToDisappear() { this.httpService.destroy(); this.historyDb.close(); } async loadHistory() { this.loading = true; this.errorMsg = ''; try { // 先查本地 SQLite(快) const local = await this.historyDb.queryRecent(50); if (local.length > 0) { this.history = local; } // 异步拉服务器同步(第 67 篇会专讲同步策略) this.syncFromServer(); } catch (error) { this.errorMsg = (error as Error).message; } finally { this.loading = false; } } async syncFromServer() { try { const remote = await this.httpService.fetchHistory(50); // 事务批量插入服务器数据到本地 await this.historyDb.batchInsert(remote.map(r => ({ score: r.score, duration: r.duration, maxCombo: r.maxCombo, mergeCount: r.mergeCount, highestLevel: r.highestLevel, date: r.date }))); // 重新查本地 this.history = await this.historyDb.queryRecent(50); } catch (error) { console.error(`服务器同步失败: ${(error as Error).message}`); } } build() { Column() { Row() { Text('📚 战绩历史').fontSize(20).fontWeight(FontWeight.Bold).fontColor('#2C3E50') } .width('100%').height(56).padding({ left: 16 }) .justifyContent(FlexAlign.Start).alignItems(VerticalAlign.Center) if (this.loading) { Column() { LoadingProgress().width(48).height(48).color('#3498DB') Text('加载中...').fontSize(16).fontColor('#95A5A6').margin({ top: 12 }) } .width('100%').layoutWeight(1) .justifyContent(FlexAlign.Center).alignItems(HorizontalAlign.Center) } else if (this.history.length === 0) { Column() { Text('📭').fontSize(48).margin({ bottom: 12 }) Text('暂无战绩').fontSize(16).fontColor('#95A5A6') } .width('100%').layoutWeight(1) .justifyContent(FlexAlign.Center).alignItems(HorizontalAlign.Center) } else { List() { ForEach(this.history, (record: GameRecord) => { ListItem() { Row() { Column() { Text('得分').fontSize(10).fontColor('#95A5A6') Text(record.score.toString()) .fontSize(18).fontWeight(FontWeight.Bold).fontColor('#2ECC71') } .alignItems(HorizontalAlign.Start).layoutWeight(2) Column() { Text('用时').fontSize(10).fontColor('#95A5A6') Text(this.formatTime(record.duration)) .fontSize(16).fontColor('#2C3E50') } .alignItems(HorizontalAlign.Center).layoutWeight(2) Column() { Text('连击').fontSize(10).fontColor('#95A5A6') Text(`x${record.maxCombo}`) .fontSize(16).fontWeight(FontWeight.Bold).fontColor('#E74C3C') } .alignItems(HorizontalAlign.End).layoutWeight(2) } .width('100%').height(64) .padding({ left: 16, right: 16, top: 8, bottom: 8 }) .onClick(() => { // 长按删除(第 51 篇 onTouch 思路) this.showDeleteConfirm(record.id); }) } }, (record: GameRecord) => `history_${record.id}`) } .width('100%').layoutWeight(1) .divider({ strokeWidth: 1, color: '#ECF0F1', startMargin: 16, endMargin: 16 }) } } .width('100%').height('100%') .backgroundColor('#FFFFFF') } formatTime(seconds: number): string { const min = Math.floor(seconds / 60); const sec = seconds % 60; return `${min.toString().padStart(2, '0')}:${sec.toString().padStart(2, '0')}`; } showDeleteConfirm(id: number) { // 简化:用 AlertDialog 或自定义确认弹窗 console.info(`确认删除战绩 id=${id}`); } }

六、SQLite vs Preference 取舍

6.1 对比

维度SQLite(relationalStore)Preference(kvStore)
数据类型表(行×列)键值(key→value)
适合结构化、列表、关系少量配置、标志位
查询SQL(条件/排序/聚合)按 key 取
体积中(建表开销)
性能中(SQL 解析)高(直接取)
事务✅ 支持❌ 无

6.2 「猫猫大作战」取舍

数据推荐原因
战绩历史(50+ 条)SQLite列表结构,要排序查询
玩家昵称Preference单字符串
最高分Preference单数字
音效开关Preference单布尔
皮肤解锁列表SQLite多条目,要查询
鉴权 tokenPreference单字符串
最近一次登录时间Preference单数字

关键经验「列表/关系/要查询」用 SQLite,「单值/标志/配置」用 Preference——本系列第 65 篇会专讲 Preference。

七、踩坑提示

7.1 忘初始化 store 就用

// ❌ 错误:没 init 就 insert,store 是 null async insert(record) { await this.store!.insert(/* ... */); // store 是 null,抛错 } // ✅ 正确:aboutToAppear 先 init aboutToAppear() { this.historyDb.init(this.context); } // init 完成后才 insert

7.2 SQL 注入(拼接字符串)

// ❌ 错误:拼接字符串,注入风险 const sql = `SELECT * FROM history WHERE date = '${userInput}'`; // userInput = ' OR 1=1 --,拉出全部数据 // ✅ 正确:参数化查询 const sql = `SELECT * FROM history WHERE date = ?`; await store.querySql(sql, [userInput]); // 或用 RdbPredicates const predicates = new RdbPredicates('history'); predicates.equalTo('date', userInput);

7.3 ResultSet 忘遍历

// ❌ 错误:query 返回 ResultSet,没遍历就当数组用 const result = await store.query(predicates); console.info(result.length); // undefined,ResultSet 没 length // ✅ 正确:goToNextRow 遍历 const records = []; while (result.goToNextRow()) { records.push(/* ... */); }

7.4 事务忘回滚

// ❌ 错误:事务失败没 rollBack,部分数据已插入 await store.beginTransaction(); for (const record of records) { await store.insert(/* ... */); // 第 50 条失败抛错 } // 忘了 catch 里 rollBack,前 49 条已插入但数据不完整 await store.commit(); // ✅ 正确:try-catch + rollBack await store.beginTransaction(); try { for (const record of records) { await store.insert(/* ... */); } await store.commit(); } catch (error) { await store.rollBack(); // 失败回滚 throw error; }

7.5 忘 close 泄漏

// ❌ 错误:组件销毁没 close,连接泄漏 aboutToDisappear() { /* 忘了 this.historyDb.close(); */ } // ✅ 正确:销毁时关连接 aboutToDisappear() { this.historyDb.close(); }

7.6 主键自增理解错

// ❌ 错误:插入时手动设 id,与 AUTOINCREMENT 冲突 await store.insert('history', { 'id': 100, 'score': 1500, /* ... */ }); // ✅ 正确:不设 id,让数据库自增 await store.insert('history', { 'score': 1500, /* ... */ }); // 数据库自动分配 id = 1, 2, 3, ...

八、调试技巧

  1. console.info打执行的 SQL:追查询/插入是否正确。
  2. DevEco Database Inspector:查看数据库文件、表结构、数据,类似 Android Database Inspector。
  3. 查询返回空排查:检查表名是否对;检查 WHERE 条件是否过严;检查表是否真有数据。
  4. 插入失败排查:检查必填字段(NOT NULL)是否都传;检查类型是否匹配(INTEGER 传字符串会报错)。

九、性能与最佳实践

  1. 结构化/列表/要查询用 SQLite,单值/配置用 Preference——按数据类型选。
  2. 建表用 IF NOT EXISTS——重启幂等,不报错。
  3. 查询用参数化(querySql + ?)或 RdbPredicates——防 SQL 注入。
  4. 批量插入用事务——性能提升 40 倍,且保证原子性。
  5. 事务失败必 rollBack——否则数据不一致。
  6. aboutToAppear init,aboutToDisappear close——避免连接泄漏。
  7. 不手动设主键——让 AUTOINCREMENT 自增。
  8. 安全等级选对——普通数据 S2,公开缓存 S1,敏感 S3/S4。

十、阶段四进度(61–65)

本篇是阶段四「网络与数据」第 4 篇:

主题核心要点
61HTTP 请求http 模块、GET/POST、错误处理
62REST/GraphQL接口设计取舍
63数据序列化JSON stringify/parse、Protobuf
64(本篇)SQLite 持久化relationalStore 建表、CRUD、事务
65Preference 键值轻量配置存储

总结

本篇我们从 SQLite 持久化切入,掌握了relationalStore 建库建表(StoreConfig + CREATE TABLE)增删改查(insert/querySql/update/delete + RdbPredicates 防注入)事务批量插入(beginTransaction + commit + rollBack)、**与 Preference 取舍(列表用 SQLite,单值用 Preference)**四大要点,并给出了 HistoryDb 完整实现 + 战绩页改造代码。核心要点:建表 IF NOT EXISTS 幂等;查询参数化防注入;批量用事务提升 40 倍;aboutToAppear init aboutToDisappear close;列表用 SQLite 单值用 Preference

下一篇我们将拆解 Preference——键值存储轻量配置。

如果这篇文章对你有帮助,欢迎点赞👍、收藏⭐、关注🔔,你的支持是我持续创作的动力!


相关资源:

  • 「猫猫大作战」项目源码:本仓库entry/src/main/ets/services/HistoryDb.etsentry/src/main/ets/pages/Index.etsentry/src/main/ets/pages/LeaderboardPage.ets
  • @ohos.data.relationalStore 官方指南
  • RdbPredicates API 官方文档
  • HarmonyOS 数据存储最佳实践
  • 开源鸿蒙跨平台社区
  • HarmonyOS 开发者官方文档首页
  • 系列索引:本仓库articles/INDEX.md

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询