HarmonyOS应用开发之数据持久化方案:轻量级偏好数据与关系型数据库

数据持久化是移动应用开发的核心需求,HarmonyOS提供了多种数据存储方案来满足不同场景的需求。本文将深入探讨轻量级偏好数据存储和关系型数据库两种最常用的持久化方案,帮助您根据业务需求选择合适的数据存储策略。

一、数据持久化概述

1.1 持久化方案对比

HarmonyOS提供了多层次的数据持久化解决方案:

存储方案 适用场景 数据量 查询复杂度 性能特点
轻量级偏好数据 配置信息、用户偏好 小(KB级) 简单键值查询 读写速度快
关系型数据库 结构化数据、复杂查询 中大型(MB-GB级) 复杂SQL查询 事务支持
分布式数据对象 跨设备数据同步 中小型 对象操作 实时同步
文件存储 大文件、媒体资源 大型(GB级) 文件操作 灵活但复杂

1.2 选择依据

选择轻量级偏好数据当

  • 存储简单的配置信息
  • 数据量小且结构简单
  • 需要快速读写访问
  • 不需要复杂查询

选择关系型数据库当

  • 数据结构复杂且有关联性
  • 需要执行复杂查询和聚合操作
  • 要求数据完整性和事务支持
  • 数据量较大且需要高效管理

二、轻量级偏好数据存储

2.1 基本概念与特性

轻量级偏好数据(Preferences)是一种基于键值对的轻量级存储方案,适用于保存应用的配置信息、用户设置等小规模数据。

核心特性

  • 线程安全的键值对存储
  • 支持基本数据类型(string、number、boolean等)
  • 数据自动持久化到本地文件
  • 简单的API接口

2.2 基本用法详解

import preferences from '@ohos.data.preferences'

@Component
struct PreferencesBasicExample {
  // Preferences实例
  private prefs: preferences.Preferences | null = null
  
  @State appSettings: AppSettings = {
    theme: 'light',
    fontSize: 16,
    notifications: true,
    language: 'zh-CN'
  }

  build() {
    Column({ space: 20 }) {
      Text('偏好设置管理')
        .fontSize(20)
        .fontWeight(FontWeight.Bold)
      
      this.buildSettingsForm()
      this.buildActionButtons()
    }
    .width('100%')
    .padding(20)
    .onAppear(() => {
      this.initPreferences()
    })
  }

  @Builder
  buildSettingsForm() {
    Column({ space: 15 }) {
      // 主题设置
      Row({ space: 10 }) {
        Text('主题模式:')
          .fontSize(16)
          .width(80)
        Button(this.appSettings.theme === 'light' ? '浅色' : '深色')
          .onClick(() => {
            this.appSettings.theme = this.appSettings.theme === 'light' ? 'dark' : 'light'
          })
      }
      
      // 字体大小
      Row({ space: 10 }) {
        Text('字体大小:')
          .fontSize(16)
          .width(80)
        Text(this.appSettings.fontSize.toString())
          .fontSize(16)
        Slider({
          value: this.appSettings.fontSize,
          min: 12,
          max: 24,
          step: 2
        })
        .onChange((value: number) => {
          this.appSettings.fontSize = value
        })
        .layoutWeight(1)
      }
      
      // 通知开关
      Row({ space: 10 }) {
        Text('消息通知:')
          .fontSize(16)
          .width(80)
        Toggle({ type: ToggleType.Checkbox, isOn: this.appSettings.notifications })
          .onChange((isOn: boolean) => {
            this.appSettings.notifications = isOn
          })
      }
    }
  }

  @Builder
  buildActionButtons() {
    Row({ space: 15 }) {
      Button('保存设置')
        .onClick(() => {
          this.saveSettings()
        })
      
      Button('加载设置')
        .onClick(() => {
          this.loadSettings()
        })
      
      Button('重置设置')
        .onClick(() => {
          this.resetSettings()
        })
    }
  }

  // 初始化Preferences
  async initPreferences() {
    try {
      this.prefs = await preferences.getPreferences(this.context, 'app_settings')
      console.log('Preferences初始化成功')
      await this.loadSettings() // 初始化后立即加载设置
    } catch (err) {
      console.error('Preferences初始化失败:', err)
    }
  }

  // 保存设置到Preferences
  async saveSettings() {
    if (!this.prefs) {
      await this.initPreferences()
    }
    
    try {
      // 存储各种类型的数据
      await this.prefs.put('theme', this.appSettings.theme)
      await this.prefs.put('fontSize', this.appSettings.fontSize)
      await this.prefs.put('notifications', this.appSettings.notifications)
      await this.prefs.put('language', this.appSettings.language)
      
      // 提交更改
      await this.prefs.flush()
      console.log('设置保存成功')
    } catch (err) {
      console.error('保存设置失败:', err)
    }
  }

  // 从Preferences加载设置
  async loadSettings() {
    if (!this.prefs) {
      await this.initPreferences()
    }
    
    try {
      const theme = await this.prefs.get('theme', 'light')
      const fontSize = await this.prefs.get('fontSize', 16)
      const notifications = await this.prefs.get('notifications', true)
      const language = await this.prefs.get('language', 'zh-CN')
      
      this.appSettings = {
        theme: theme as string,
        fontSize: fontSize as number,
        notifications: notifications as boolean,
        language: language as string
      }
      console.log('设置加载成功')
    } catch (err) {
      console.error('加载设置失败:', err)
    }
  }

  // 重置为默认设置
  async resetSettings() {
    this.appSettings = {
      theme: 'light',
      fontSize: 16,
      notifications: true,
      language: 'zh-CN'
    }
    await this.saveSettings()
  }
}

class AppSettings {
  theme: string = 'light'
  fontSize: number = 16
  notifications: boolean = true
  language: string = 'zh-CN'
}

2.3 高级特性与最佳实践

数据加密与安全

async initSecurePreferences() {
  try {
    const options: preferences.Options = {
      name: 'secure_settings',
      // 设置数据加密
      encrypt: true,
      // 自定义加密密钥(实际项目中应从安全存储获取)
      encryptKey: 'your-encryption-key'
    }
    this.prefs = await preferences.getPreferences(this.context, options)
  } catch (err) {
    console.error('安全Preferences初始化失败:', err)
  }
}

批量操作优化

// 批量保存设置,减少IO操作
async saveSettingsBatch(settings: Record<string, any>) {
  if (!this.prefs) return
  
  try {
    // 开始批量操作
    await this.prefs.beginBatch()
    
    for (const [key, value] of Object.entries(settings)) {
      await this.prefs.put(key, value)
    }
    
    // 提交批量操作
    await this.prefs.commitBatch()
    console.log('批量设置保存成功')
  } catch (err) {
    // 回滚批量操作
    await this.prefs.rollbackBatch()
    console.error('批量设置保存失败:', err)
  }
}

三、关系型数据库

3.1 关系型数据库基础

HarmonyOS的关系型数据库基于SQLite,提供了完整的SQL支持和数据管理能力。

核心优势

  • 完整的ACID事务支持
  • 丰富的SQL查询功能
  • 高效的数据索引和查询优化
  • 良好的并发处理能力

3.2 数据库创建与表管理

import relationalStore from '@ohos.data.relationalStore'

@Component
struct DatabaseExample {
  private rdbStore: relationalStore.RdbStore | null = null
  @State userList: User[] = []

  // 数据库配置
  private readonly config: relationalStore.StoreConfig = {
    name: 'user_database.db',
    securityLevel: relationalStore.SecurityLevel.S1
  }

  // 用户表SQL
  private readonly USER_TABLE_SQL = `
    CREATE TABLE IF NOT EXISTS users (
      id INTEGER PRIMARY KEY AUTOINCREMENT,
      name TEXT NOT NULL,
      email TEXT UNIQUE,
      age INTEGER,
      created_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
      updated_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP
    )
  `

  build() {
    Column({ space: 20 }) {
      Text('用户数据库管理')
        .fontSize(20)
        .fontWeight(FontWeight.Bold)
      
      this.buildUserForm()
      this.buildUserList()
      this.buildActionButtons()
    }
    .width('100%')
    .padding(20)
    .onAppear(() => {
      this.initDatabase()
    })
  }

  // 初始化数据库
  async initDatabase() {
    try {
      this.rdbStore = await relationalStore.getRdbStore(this.context, this.config)
      
      // 创建用户表
      await this.rdbStore.executeSql(this.USER_TABLE_SQL)
      console.log('数据库初始化成功')
      
      // 加载用户数据
      await this.loadUsers()
    } catch (err) {
      console.error('数据库初始化失败:', err)
    }
  }

3.3 数据操作:增删改查

// 插入用户数据
async addUser(user: Omit<User, 'id'>) {
  if (!this.rdbStore) return

  const valueBucket: relationalStore.ValuesBucket = {
    'name': user.name,
    'email': user.email,
    'age': user.age,
    'created_time': new Date().toISOString(),
    'updated_time': new Date().toISOString()
  }

  try {
    const insertId = await this.rdbStore.insert('users', valueBucket)
    console.log('用户添加成功,ID:', insertId)
    await this.loadUsers() // 刷新列表
    return insertId
  } catch (err) {
    console.error('添加用户失败:', err)
    throw err
  }
}

// 查询用户数据
async loadUsers() {
  if (!this.rdbStore) return

  const columns = ['id', 'name', 'email', 'age', 'created_time']
  const predicates = new relationalStore.RdbPredicates('users')
  
  try {
    const resultSet = await this.rdbStore.query(predicates, columns)
    
    const users: User[] = []
    while (resultSet.goToNextRow()) {
      const user: User = {
        id: resultSet.getLong(resultSet.getColumnIndex('id')),
        name: resultSet.getString(resultSet.getColumnIndex('name')),
        email: resultSet.getString(resultSet.getColumnIndex('email')),
        age: resultSet.getLong(resultSet.getColumnIndex('age')),
        createdTime: resultSet.getString(resultSet.getColumnIndex('created_time'))
      }
      users.push(user)
    }
    
    resultSet.close()
    this.userList = users
  } catch (err) {
    console.error('查询用户失败:', err)
  }
}

// 更新用户数据
async updateUser(user: User) {
  if (!this.rdbStore) return

  const valueBucket: relationalStore.ValuesBucket = {
    'name': user.name,
    'email': user.email,
    'age': user.age,
    'updated_time': new Date().toISOString()
  }

  const predicates = new relationalStore.RdbPredicates('users')
  predicates.equalTo('id', user.id)

  try {
    const rowsUpdated = await this.rdbStore.update(valueBucket, predicates)
    console.log('用户更新成功,影响行数:', rowsUpdated)
    await this.loadUsers()
  } catch (err) {
    console.error('更新用户失败:', err)
  }
}

// 删除用户数据
async deleteUser(userId: number) {
  if (!this.rdbStore) return

  const predicates = new relationalStore.RdbPredicates('users')
  predicates.equalTo('id', userId)

  try {
    const rowsDeleted = await this.rdbStore.delete(predicates)
    console.log('用户删除成功,影响行数:', rowsDeleted)
    await this.loadUsers()
  } catch (err) {
    console.error('删除用户失败:', err)
  }
}

3.4 高级查询与事务处理

复杂查询示例

// 高级查询:条件筛选、排序、分页
async searchUsers(options: SearchOptions) {
  if (!this.rdbStore) return []

  const predicates = new relationalStore.RdbPredicates('users')
  
  // 条件筛选
  if (options.name) {
    predicates.contains('name', options.name)
  }
  
  if (options.minAge) {
    predicates.greaterThanOrEqualTo('age', options.minAge)
  }
  
  if (options.maxAge) {
    predicates.lessThanOrEqualTo('age', options.maxAge)
  }

  // 排序
  const orderBy = options.sortBy || 'created_time'
  const order = options.sortOrder === 'desc' ? false : true
  predicates.orderByAsc(orderBy, order)

  // 分页
  if (options.limit) {
    predicates.limit(options.limit)
    predicates.offset(options.offset || 0)
  }

  try {
    const resultSet = await this.rdbStore.query(predicates, ['*'])
    // ... 处理结果集
  } catch (err) {
    console.error('高级查询失败:', err)
    return []
  }
}

事务处理

// 事务操作:确保数据一致性
async transferPoints(fromUserId: number, toUserId: number, points: number) {
  if (!this.rdbStore) return false

  try {
    // 开始事务
    await this.rdbStore.beginTransaction()
    
    // 扣除转出用户积分
    const deductPredicates = new relationalStore.RdbPredicates('user_points')
    deductPredicates.equalTo('user_id', fromUserId)
    
    const currentPoints = await this.getUserPoints(fromUserId)
    if (currentPoints < points) {
      throw new Error('积分不足')
    }
    
    await this.rdbStore.update({
      'points': currentPoints - points,
      'updated_time': new Date().toISOString()
    }, deductPredicates)
    
    // 增加转入用户积分
    const addPredicates = new relationalStore.RdbPredicates('user_points')
    addPredicates.equalTo('user_id', toUserId)
    
    const toUserPoints = await this.getUserPoints(toUserId)
    await this.rdbStore.update({
      'points': toUserPoints + points,
      'updated_time': new Date().toISOString()
    }, addPredicates)
    
    // 记录交易日志
    await this.rdbStore.insert('point_transactions', {
      'from_user_id': fromUserId,
      'to_user_id': toUserId,
      'points': points,
      'created_time': new Date().toISOString()
    })
    
    // 提交事务
    await this.rdbStore.commit()
    console.log('积分转移成功')
    return true
    
  } catch (err) {
    // 回滚事务
    await this.rdbStore.rollback()
    console.error('积分转移失败:', err)
    return false
  }
}

四、实战应用:个人记账应用

4.1 数据模型设计

// 记账应用的数据模型
class AccountingRecord {
  id: number = 0
  type: 'income' | 'expense' = 'expense'  // 收入/支出
  category: string = ''                 // 分类
  amount: number = 0                    // 金额
  description: string = ''              // 描述
  recordDate: string = ''               // 记录日期
  createdTime: string = ''              // 创建时间
}

// 数据库表结构
private readonly ACCOUNTING_TABLE_SQL = `
  CREATE TABLE IF NOT EXISTS accounting_records (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    type TEXT CHECK(type IN ('income', 'expense')) NOT NULL,
    category TEXT NOT NULL,
    amount REAL NOT NULL,
    description TEXT,
    record_date TEXT NOT NULL,
    created_time TEXT DEFAULT CURRENT_TIMESTAMP
  )
`

4.2 统计查询与数据分析

// 月度统计查询
async getMonthlySummary(year: number, month: number) {
  if (!this.rdbStore) return null

  try {
    // 收入统计
    const incomePredicates = new relationalStore.RdbPredicates('accounting_records')
    incomePredicates.equalTo('type', 'income')
    incomePredicates.like('record_date', `${year}-${month.toString().padStart(2, '0')}%`)
    
    const incomeResult = await this.rdbStore.query(incomePredicates, ['SUM(amount) as total_income'])
    incomeResult.goToFirstRow()
    const totalIncome = incomeResult.getDouble(incomeResult.getColumnIndex('total_income')) || 0
    incomeResult.close()
    
    // 支出统计
    const expensePredicates = new relationalStore.RdbPredicates('accounting_records')
    expensePredicates.equalTo('type', 'expense')
    expensePredicates.like('record_date', `${year}-${month.toString().padStart(2, '0')}%`)
    
    const expenseResult = await this.rdbStore.query(expensePredicates, ['SUM(amount) as total_expense'])
    expenseResult.goToFirstRow()
    const totalExpense = expenseResult.getDouble(expenseResult.getColumnIndex('total_expense')) || 0
    expenseResult.close()
    
    return {
      totalIncome,
      totalExpense,
      balance: totalIncome - totalExpense
    }
  } catch (err) {
    console.error('月度统计查询失败:', err)
    return null
  }
}

五、性能优化与最佳实践

5.1 数据库优化策略

索引优化

// 为常用查询字段创建索引
private readonly CREATE_INDEX_SQL = `
  CREATE INDEX IF NOT EXISTS idx_records_date ON accounting_records(record_date);
  CREATE INDEX IF NOT EXISTS idx_records_type ON accounting_records(type);
  CREATE INDEX IF NOT EXISTS idx_records_category ON accounting_records(category);
`

连接池管理

// 数据库连接管理
class DatabaseManager {
  private static instance: DatabaseManager
  private rdbStore: relationalStore.RdbStore | null = null
  private isInitialized: boolean = false
  
  static getInstance(): DatabaseManager {
    if (!DatabaseManager.instance) {
      DatabaseManager.instance = new DatabaseManager()
    }
    return DatabaseManager.instance
  }
  
  async getDatabase(context: Context): Promise<relationalStore.RdbStore> {
    if (!this.isInitialized) {
      await this.initDatabase(context)
    }
    return this.rdbStore!
  }
}

5.2 数据迁移方案

// 数据库版本升级和数据迁移
private async handleDatabaseUpgrade(oldVersion: number, newVersion: number) {
  if (!this.rdbStore) return
  
  try {
    if (oldVersion < 2) {
      // 版本1到版本2的迁移
      await this.rdbStore.executeSql('ALTER TABLE users ADD COLUMN avatar_url TEXT')
    }
    
    if (oldVersion < 3) {
      // 版本2到版本3的迁移
      await this.rdbStore.executeSql('CREATE TABLE user_settings (user_id INTEGER, theme TEXT)')
    }
  } catch (err) {
    console.error('数据库迁移失败:', err)
  }
}

总结

通过本文的学习,您应该掌握了HarmonyOS数据持久化的核心技能:

  1. 轻量级偏好数据:适用于简单配置和用户设置的存储
  2. 关系型数据库:处理复杂结构化数据和高级查询需求
  3. 实战应用:记账应用的完整数据管理实现
  4. 性能优化:数据库索引、连接管理和迁移策略

选择合适的持久化方案需要根据具体业务需求来决定。对于简单的键值对数据,偏好数据存储是理想选择;而对于需要复杂查询和事务支持的结构化数据,关系型数据库更为合适。

需要参加鸿蒙认证的请点击 鸿蒙认证链接

posted @ 2025-10-30 10:56  ifeng918  阅读(15)  评论(0)    收藏  举报