鸿蒙应用开发实战【27】--- DatabaseService 数据库初始化
本文是「号码助手全栈开发系列」第 27 篇,持续更新中...
前言
上一篇我们了解了 RDB 的整体架构和核心 API,现在进入实战------创建 DatabaseService。这是数据库访问的入口,负责初始化 RdbStore、维护单例、执行建表 SQL 和管理数据库版本。
本篇涵盖:getRdbStore 初始化流程与 StoreConfig、单例模式 getInstance / getStore、CREATE TABLE IF NOT EXISTS 建表、3 张核心表(card / app_binding / sms_candidate)的字段设计、版本管理 onUpgrade 回调、完整 DatabaseService 类代码。

一、DatabaseService 类设计
DatabaseService 的核心职责是:
- 创建/打开数据库 --- 调用
relationalStore.getRdbStore()初始化 RdbStore - 单例管理 --- 全局唯一实例,避免重复打开数据库连接
- 建表与升级 --- 在
onUpgrade回调中执行 CREATE TABLE 和 ALTER TABLE - 提供 RdbStore --- 供 DAO 层获取操作对象
完整类结构
typescript
import { relationalStore } from '@kit.ArkData';
import { UIAbilityContext } from '@kit.AbilityKit';
export class DatabaseService {
// 单例实例
private static instance: DatabaseService;
// RdbStore 实例
private store: relationalStore.RdbStore | null = null;
// 私有构造,外部不可 new
private constructor() {}
// 获取单例
static getInstance(): DatabaseService {
if (!DatabaseService.instance) {
DatabaseService.instance = new DatabaseService();
}
return DatabaseService.instance;
}
}
二、数据库初始化流程
2.1 StoreConfig 配置
初始化 RdbStore 需要准备 StoreConfig:
typescript
private getStoreConfig(): relationalStore.StoreConfig {
return {
name: 'haomazhushou.db',
securityLevel: relationalStore.SecurityLevel.S2,
encrypt: true,
};
}
字段说明:
- name --- 数据库文件名,最终路径为
{app沙盒}/app_database/haomazhushou.db - securityLevel --- S2 级加密保护个人信息
- encrypt --- 开启加密(S2 默认已加密,显式声明更清晰)
2.2 getStore 方法
typescript
async getStore(context: UIAbilityContext): Promise<relationalStore.RdbStore> {
if (this.store) {
return this.store;
}
const config = this.getStoreConfig();
this.store = await relationalStore.getRdbStore(context, config);
return this.store;
}
这段代码实现了 延迟初始化------只有首次调用时才创建 RdbStore,后续调用直接返回缓存的实例。
2.3 完整的初始化方法
加入 onUpgrade 回调,在建表/升级时自动执行 SQL:
typescript
async getStore(context: UIAbilityContext): Promise<relationalStore.RdbStore> {
if (this.store) {
return this.store;
}
const config: relationalStore.StoreConfig = {
name: 'haomazhushou.db',
securityLevel: relationalStore.SecurityLevel.S2,
encrypt: true,
};
this.store = await relationalStore.getRdbStore(context, config, async (): Promise<void> => {
await this.createTables();
});
return this.store;
}
private async createTables(): Promise<void> {
if (!this.store) return;
// 建表操作在 getRdbStore 的 onUpgrade 回调中自动执行
await this.store.executeSql(CREATE_TABLE_CARD);
await this.store.executeSql(CREATE_TABLE_APP_BINDING);
await this.store.executeSql(CREATE_TABLE_SMS_CANDIDATE);
console.info('Database tables created successfully');
}
📌 注意 :
getRdbStore的第三个参数是onUpgrade回调,它在数据库版本升高时触发。首次安装时版本从 0→1,也会触发该回调。
三、三张核心表设计
3.1 card --- 卡号表
存储用户的手机号卡信息:
typescript
const CREATE_TABLE_CARD: string = `
CREATE TABLE IF NOT EXISTS card (
id INTEGER PRIMARY KEY AUTOINCREMENT,
label TEXT NOT NULL,
phone TEXT NOT NULL,
operator TEXT,
color TEXT DEFAULT '#4F7CFF',
sort_order INTEGER DEFAULT 0,
created_at TEXT DEFAULT (datetime('now', 'localtime')),
updated_at TEXT DEFAULT (datetime('now', 'localtime'))
);
`;
| 字段 | 类型 | 说明 |
|---|---|---|
| id | INTEGER PK AUTOINCREMENT | 主键,自增 |
| label | TEXT NOT NULL | 卡号标签,如"主卡"、"副卡"、"流量卡" |
| phone | TEXT NOT NULL | 手机号码 |
| operator | TEXT | 运营商,如"中国移动"、"中国联通"、"中国电信" |
| color | TEXT DEFAULT '#4F7CFF' | 卡片主题色,十六进制 |
| sort_order | INTEGER DEFAULT 0 | 排序权重,越小越靠前 |
| created_at | TEXT | 创建时间,默认当前时间 |
| updated_at | TEXT | 更新时间,默认当前时间 |
3.2 app_binding --- 应用绑定表
记录每个应用绑定了哪张卡:
typescript
const CREATE_TABLE_APP_BINDING: string = `
CREATE TABLE IF NOT EXISTS app_binding (
id INTEGER PRIMARY KEY AUTOINCREMENT,
app_name TEXT NOT NULL,
category TEXT,
card_id INTEGER REFERENCES card(id),
status TEXT DEFAULT '使用中',
remark TEXT,
icon_key TEXT,
created_at TEXT DEFAULT (datetime('now', 'localtime')),
updated_at TEXT DEFAULT (datetime('now', 'localtime'))
);
`;
| 字段 | 类型 | 说明 |
|---|---|---|
| id | INTEGER PK AUTOINCREMENT | 主键,自增 |
| app_name | TEXT NOT NULL | 应用名称,如"微信"、"支付宝" |
| category | TEXT | 应用分类,如"社交"、"支付"、"金融" |
| card_id | INTEGER REFERENCES card(id) | 外键,关联 card 表 |
| status | TEXT DEFAULT '使用中' | 绑定状态 |
| remark | TEXT | 用户的备注信息 |
| icon_key | TEXT | 应用图标键值 |
| created_at | TEXT | 创建时间 |
| updated_at | TEXT | 更新时间 |
3.3 sms_candidate --- 短信候选表
存储从短信中提取的待处理绑定记录:
typescript
const CREATE_TABLE_SMS_CANDIDATE: string = `
CREATE TABLE IF NOT EXISTS sms_candidate (
id INTEGER PRIMARY KEY AUTOINCREMENT,
sms_sender TEXT,
sms_body TEXT,
app_name TEXT,
card_label TEXT,
source TEXT DEFAULT 'sms',
imported INTEGER DEFAULT 0,
created_at TEXT DEFAULT (datetime('now', 'localtime'))
);
`;
| 字段 | 类型 | 说明 |
|---|---|---|
| id | INTEGER PK AUTOINCREMENT | 主键,自增 |
| sms_sender | TEXT | 短信发送者号码/名称 |
| sms_body | TEXT | 短信原文 |
| app_name | TEXT | 从短信中解析出的应用名 |
| card_label | TEXT | 从短信中解析出的卡号标签 |
| source | TEXT DEFAULT 'sms' | 来源:'sms' / 'paste' / 'manual' |
| imported | INTEGER DEFAULT 0 | 导入标记:0=未导入,1=已导入 |
| created_at | TEXT | 创建时间 |
四、版本管理与升级
4.1 版本号策略
typescript
private static readonly DB_VERSION: number = 1;
- 初始版本号设为 1,对应首次建表
- 每次数据库结构变更,版本号 +1
- 已有数据不会被删除,只在 onUpgrade 中执行 ALTER TABLE
4.2 onUpgrade 升级回调
typescript
this.store = await relationalStore.getRdbStore(context, {
name: 'haomazhushou.db',
securityLevel: relationalStore.SecurityLevel.S2,
encrypt: true,
}, async (oldVersion: number, newVersion: number): Promise<void> => {
console.info(`Database upgrade from ${oldVersion} to ${newVersion}`);
if (oldVersion < 1) {
// 版本 0→1:初始建表
await this.createTables();
}
// 后续版本升级示例:
// if (oldVersion < 2) {
// await this.store?.executeSql('ALTER TABLE card ADD COLUMN ...');
// }
// if (oldVersion < 3) {
// await this.store?.executeSql('CREATE TABLE IF NOT EXISTS ...');
// }
});
⚠️ 关键点 :每个升级步骤必须判断
oldVersion < N,这样用户无论从哪个旧版本升级都能逐步执行到最新。比如用户从 v1 直接跳到 v3,会依次执行 v1→v2 和 v2→v3 的升级。
五、完整 DatabaseService 代码
整合以上所有功能,得到完整的 DatabaseService 类:
typescript
import { relationalStore } from '@kit.ArkData';
import { UIAbilityContext } from '@kit.AbilityKit';
const CREATE_TABLE_CARD: string = `
CREATE TABLE IF NOT EXISTS card (
id INTEGER PRIMARY KEY AUTOINCREMENT,
label TEXT NOT NULL,
phone TEXT NOT NULL,
operator TEXT,
color TEXT DEFAULT '#4F7CFF',
sort_order INTEGER DEFAULT 0,
created_at TEXT DEFAULT (datetime('now', 'localtime')),
updated_at TEXT DEFAULT (datetime('now', 'localtime'))
);
`;
const CREATE_TABLE_APP_BINDING: string = `
CREATE TABLE IF NOT EXISTS app_binding (
id INTEGER PRIMARY KEY AUTOINCREMENT,
app_name TEXT NOT NULL,
category TEXT,
card_id INTEGER REFERENCES card(id),
status TEXT DEFAULT '使用中',
remark TEXT,
icon_key TEXT,
created_at TEXT DEFAULT (datetime('now', 'localtime')),
updated_at TEXT DEFAULT (datetime('now', 'localtime'))
);
`;
const CREATE_TABLE_SMS_CANDIDATE: string = `
CREATE TABLE IF NOT EXISTS sms_candidate (
id INTEGER PRIMARY KEY AUTOINCREMENT,
sms_sender TEXT,
sms_body TEXT,
app_name TEXT,
card_label TEXT,
source TEXT DEFAULT 'sms',
imported INTEGER DEFAULT 0,
created_at TEXT DEFAULT (datetime('now', 'localtime'))
);
`;
export class DatabaseService {
private static instance: DatabaseService;
private store: relationalStore.RdbStore | null = null;
private static readonly DB_VERSION: number = 1;
private constructor() {}
static getInstance(): DatabaseService {
if (!DatabaseService.instance) {
DatabaseService.instance = new DatabaseService();
}
return DatabaseService.instance;
}
async getStore(context: UIAbilityContext): Promise<relationalStore.RdbStore> {
if (this.store) {
return this.store;
}
this.store = await relationalStore.getRdbStore(context, {
name: 'haomazhushou.db',
securityLevel: relationalStore.SecurityLevel.S2,
encrypt: true,
}, async (oldVersion: number, newVersion: number): Promise<void> => {
console.info(`Upgrade from ${oldVersion} to ${newVersion}`);
if (oldVersion < 1) {
await this.createTables();
}
});
return this.store;
}
private async createTables(): Promise<void> {
if (!this.store) return;
await this.store.executeSql(CREATE_TABLE_CARD);
await this.store.executeSql(CREATE_TABLE_APP_BINDING);
await this.store.executeSql(CREATE_TABLE_SMS_CANDIDATE);
console.info('All tables created');
}
// 页面销毁时不需要主动关闭 RdbStore,系统会管理生命周期
}
六、使用示例
在 UIAbility 的 onCreate 中初始化:
typescript
import { UIAbility } from '@kit.AbilityKit';
export default class EntryAbility extends UIAbility {
async onCreate(): Promise<void> {
try {
const dbService = DatabaseService.getInstance();
await dbService.getStore(this.context);
console.info('Database initialized');
} catch (error) {
console.error('Database init failed:', error);
}
}
}
在页面中获取数据库进行 DAO 操作:
typescript
@Entry
@Component
struct HomePage {
async aboutToAppear(): Promise<void> {
const store = await DatabaseService.getInstance().getStore(getContext(this));
// 将 store 传递给 DAO 进行操作
}
}
小结
本篇完成了数据库初始化的核心工作:
| 步骤 | 关键代码 |
|---|---|
| 单例模式 | static getInstance() + 私有构造 |
| 延迟初始化 | getStore() 首次调用时创建 |
| StoreConfig | name + securityLevel + encrypt |
| 建表 | CREATE TABLE IF NOT EXISTS 三张表 |
| 版本升级 | onUpgrade 回调,逐版本迁移 |
| 完整类 | DatabaseService 封装全部逻辑 |
下一篇将深入「CardEntity 数据模型与 CardColor 枚举设计」,定义卡号实体的 TypeScript 接口和 7 色枚举。
如果这篇文章对你有帮助,欢迎点赞👍、收藏⭐、关注🔔,你的支持是我持续创作的动力!
相关资源:
- openHarmony 跨平台社区:https://openharmonycrossplatform.csdn.net
- HarmonyOS 官方文档:https://developer.huawei.com/consumer/cn/doc/