問題描述
在開發鴻蒙原生應用時,如何優雅地實現 RelationalStore 數據庫的初始化、表結構創建、索引優化以及數據庫版本升級?很多開發者在實際項目中會遇到以下問題:
- 數據庫初始化流程不清晰
- 數據庫版本升級時如何保證數據安全
- 如何優化數據庫查詢性能
- 如何處理數據庫升級失敗的回滾
本文基於真實的人情管理系統項目,分享一套完整的數據庫管理解決方案。
技術要點
- RelationalStore 數據庫管理
- 數據庫版本升級機制
- 事務處理與回滾
- 索引優化策略
- 單例模式應用
完整實現代碼
/**
* 數據庫管理器
* 負責數據庫的創建、升級和數據操作
*/
import { relationalStore } from '@kit.ArkData';
import { BusinessError } from '@kit.BasicServicesKit';
import { common } from '@kit.AbilityKit';
export class DatabaseManager {
private static instance: DatabaseManager;
private store: relationalStore.RdbStore | null = null;
private readonly DB_NAME = 'lelv_human_relations.db';
private readonly DB_VERSION = 2; // 當前數據庫版本
private readonly CURRENT_VERSION_KEY = 'database_version';
private constructor() {}
/**
* 獲取單例實例
*/
public static getInstance(): DatabaseManager {
if (!DatabaseManager.instance) {
DatabaseManager.instance = new DatabaseManager();
}
return DatabaseManager.instance;
}
/**
* 初始化數據庫
*/
public async initDatabase(context: common.UIAbilityContext): Promise<void> {
try {
console.info('開始初始化數據庫...');
const config: relationalStore.StoreConfig = {
name: this.DB_NAME,
securityLevel: relationalStore.SecurityLevel.S1
};
// 創建數據庫連接
this.store = await relationalStore.getRdbStore(context, config);
console.info('數據庫連接創建成功');
// 創建數據表
await this.createTables();
// 檢查數據庫版本並執行升級
await this.checkAndUpgradeDatabase();
console.info('數據庫初始化成功');
} catch (error) {
console.error('數據庫初始化失敗:', JSON.stringify(error));
throw new Error(`數據庫初始化失敗: ${error.message}`);
}
}
/**
* 創建數據表
*/
private async createTables(): Promise<void> {
if (!this.store) {
throw new Error('數據庫未初始化');
}
// 創建人物表
const createPersonTable = `
CREATE TABLE IF NOT EXISTS lelv_persons (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
relationship_type TEXT NOT NULL,
relationship_tags TEXT,
phone TEXT,
avatar TEXT,
contact_id TEXT,
create_time INTEGER NOT NULL,
update_time INTEGER NOT NULL
)
`;
// 創建人情記錄表
const createRecordTable = `
CREATE TABLE IF NOT EXISTS lelv_human_records (
id TEXT PRIMARY KEY,
type TEXT NOT NULL,
event_type TEXT NOT NULL,
custom_event_type TEXT,
amount REAL NOT NULL,
event_time INTEGER NOT NULL,
person_id TEXT NOT NULL,
location TEXT,
remark TEXT,
photos TEXT,
custom_fields TEXT,
create_time INTEGER NOT NULL,
update_time INTEGER NOT NULL,
FOREIGN KEY (person_id) REFERENCES lelv_persons (id)
)
`;
// 創建應用設置表
const createSettingsTable = `
CREATE TABLE IF NOT EXISTS lelv_app_settings (
key TEXT PRIMARY KEY,
value TEXT NOT NULL,
update_time INTEGER NOT NULL
)
`;
await this.store.executeSql(createPersonTable);
await this.store.executeSql(createRecordTable);
await this.store.executeSql(createSettingsTable);
// 創建索引以提升查詢性能
await this.createIndexes();
}
/**
* 創建索引
*/
private async createIndexes(): Promise<void> {
if (!this.store) return;
const indexes = [
'CREATE INDEX IF NOT EXISTS idx_records_person_id ON lelv_human_records (person_id)',
'CREATE INDEX IF NOT EXISTS idx_records_event_time ON lelv_human_records (event_time)',
'CREATE INDEX IF NOT EXISTS idx_records_type ON lelv_human_records (type)',
'CREATE INDEX IF NOT EXISTS idx_persons_name ON lelv_persons (name)',
'CREATE INDEX IF NOT EXISTS idx_persons_phone ON lelv_persons (phone)'
];
for (const index of indexes) {
await this.store.executeSql(index);
}
}
/**
* 檢查並升級數據庫
*/
private async checkAndUpgradeDatabase(): Promise<void> {
if (!this.store) return;
try {
// 獲取當前數據庫版本
const currentVersion = await this.getCurrentVersion();
console.info(`當前數據庫版本: ${currentVersion}, 目標版本: ${this.DB_VERSION}`);
if (currentVersion < this.DB_VERSION) {
console.info('開始數據庫升級...');
await this.upgradeDatabase(currentVersion, this.DB_VERSION);
await this.setCurrentVersion(this.DB_VERSION);
console.info('數據庫升級完成');
}
} catch (error) {
console.error('數據庫升級失敗:', JSON.stringify(error));
throw new Error('數據庫升級失敗');
}
}
/**
* 獲取當前數據庫版本
*/
private async getCurrentVersion(): Promise<number> {
if (!this.store) return 0;
try {
const resultSet = await this.store.querySql(
'SELECT value FROM lelv_app_settings WHERE key = ?',
[this.CURRENT_VERSION_KEY]
);
if (resultSet.goToFirstRow()) {
const versionIndex = resultSet.getColumnIndex('value');
if (versionIndex >= 0) {
const version = resultSet.getLong(versionIndex);
resultSet.close();
return version;
}
}
resultSet.close();
return 0; // 默認版本0
} catch (error) {
console.warn('獲取數據庫版本失敗,使用默認版本0');
return 0;
}
}
/**
* 設置當前數據庫版本
*/
private async setCurrentVersion(version: number): Promise<void> {
if (!this.store) return;
const now = Date.now();
await this.store.executeSql(
'INSERT OR REPLACE INTO lelv_app_settings (key, value, update_time) VALUES (?, ?, ?)',
[this.CURRENT_VERSION_KEY, version.toString(), now]
);
}
/**
* 升級數據庫
*/
private async upgradeDatabase(fromVersion: number, toVersion: number): Promise<void> {
if (!this.store) return;
try {
// 開啓事務
await this.store.beginTransaction();
// 從版本1升級到版本2
if (fromVersion < 2 && toVersion >= 2) {
await this.upgradeToVersion2();
}
// 可以繼續添加更多版本的升級邏輯
// if (fromVersion < 3 && toVersion >= 3) {
// await this.upgradeToVersion3();
// }
// 提交事務
await this.store.commit();
} catch (error) {
// 回滾事務
await this.store.rollback(void 0);
throw new Error(`數據庫升級失敗: ${error.message}`);
}
}
/**
* 升級到版本2
*/
private async upgradeToVersion2(): Promise<void> {
if (!this.store) return;
console.info('執行數據庫升級到版本2...');
// 添加新的索引以提升查詢性能
const newIndexes = [
'CREATE INDEX IF NOT EXISTS idx_records_amount ON lelv_human_records (amount)',
'CREATE INDEX IF NOT EXISTS idx_persons_relationship_type ON lelv_persons (relationship_type)'
];
for (const indexSql of newIndexes) {
await this.store.executeSql(indexSql);
}
// 添加新的字段
try {
await this.store.executeSql(`
ALTER TABLE lelv_human_records ADD COLUMN is_deleted INTEGER DEFAULT 0
`);
} catch (error) {
// 字段可能已存在,忽略錯誤
console.warn('添加is_deleted字段失敗,可能已存在');
}
console.info('數據庫升級到版本2完成');
}
/**
* 優化數據庫性能
*/
public async optimizeDatabase(): Promise<void> {
if (!this.store) return;
try {
console.info('開始數據庫性能優化...');
// 執行VACUUM命令回收空間
await this.store.executeSql('VACUUM');
// 執行ANALYZE命令更新統計信息
await this.store.executeSql('ANALYZE');
console.info('數據庫性能優化完成');
} catch (error) {
console.error('數據庫性能優化失敗:', JSON.stringify(error));
throw new Error('數據庫性能優化失敗');
}
}
/**
* 獲取數據庫實例
*/
public getStore(): relationalStore.RdbStore | null {
return this.store;
}
}
核心原理解析
1. 單例模式設計
使用單例模式確保整個應用只有一個數據庫管理器實例,避免多次初始化造成的資源浪費和數據衝突。
2. 數據庫版本管理
- 通過
DB_VERSION常量管理當前數據庫版本 - 在
lelv_app_settings表中記錄實際數據庫版本 - 初始化時自動檢查版本並執行升級
3. 事務處理
數據庫升級使用事務保證原子性:
await this.store.beginTransaction(); // 開啓事務
// ... 執行升級操作
await this.store.commit(); // 提交事務
// 或
await this.store.rollback(void 0); // 回滾事務
4. 索引優化策略
根據查詢場景創建合適的索引:
- 主鍵索引: id 字段
- 外鍵索引: person_id 字段
- 查詢索引: event_time, type, amount 等常用查詢字段
最佳實踐建議
- 數據庫初始化時機: 在應用啓動時的 UIAbility 中初始化
- 版本號管理: 每次數據庫結構變更都需要增加版本號
- 升級腳本測試: 充分測試各個版本之間的升級路徑
- 錯誤處理: 捕獲異常並提供友好的錯誤提示
- 性能監控: 定期執行 VACUUM 和 ANALYZE 優化數據庫
避坑指南
- ❌ 不要在主線程執行耗時的數據庫操作
- 使用 async/await 確保異步執行
- ❌ 不要忘記關閉 ResultSet
- 每次查詢後都要調用
resultSet.close()
- 每次查詢後都要調用
- ❌ 不要在事務中執行長時間操作
- 事務要儘快提交或回滾
- ❌ ALTER TABLE 時注意兼容性
- 使用
IF NOT EXISTS和DEFAULT值
- 使用
效果展示
數據庫初始化日誌輸出:
開始初始化數據庫...
數據庫連接創建成功
數據表創建完成
當前數據庫版本: 1, 目標版本: 2
開始數據庫升級...
執行數據庫升級到版本2...
數據庫升級到版本2完成
數據庫升級完成
數據庫初始化成功
總結
本方案提供了一套完整的鴻蒙 RelationalStore 數據庫管理解決方案,包括:
- ✅ 規範的數據庫初始化流程
- ✅ 安全的版本升級機制
- ✅ 完善的事務回滾保護
- ✅ 合理的索引優化策略
相關資源
- 鴻蒙學習資源