HarmonyOS开发:数据层迁移——Room/CoreData到RDB

举报
Jack20 发表于 2026/06/28 21:07:05 2026/06/28
【摘要】 HarmonyOS开发:数据层迁移——Room/CoreData到RDB📌 核心要点:Room的注解式ORM和CoreData的对象图模型在鸿蒙上都不存在,鸿蒙的RDB是更接近SQLite原生的关系型数据库,迁移的关键是理解"注解→手动建表"的思维转变。 背景与动机你的App数据存在哪?Room?CoreData?SQLite?Realm?不管你用哪个,数据层迁移都是最不能出错的部分。U...

HarmonyOS开发:数据层迁移——Room/CoreData到RDB

📌 核心要点:Room的注解式ORM和CoreData的对象图模型在鸿蒙上都不存在,鸿蒙的RDB是更接近SQLite原生的关系型数据库,迁移的关键是理解"注解→手动建表"的思维转变。

背景与动机

你的App数据存在哪?Room?CoreData?SQLite?Realm?

不管你用哪个,数据层迁移都是最不能出错的部分。UI写错了顶多页面丑一点,网络层写错了顶多加载慢一点,数据层写错了——用户数据丢了,那就是生产事故。

鸿蒙的关系型数据库(RDB)本质上是对SQLite的封装,和Android的SQLiteOpenHelper类似,但比Room原始得多。没有@Entity注解自动建表,没有@Dao注解自动生成查询,没有@TypeConverter自动类型转换。

你可能会觉得这是退步。但从另一个角度看,RDB给了你更精细的控制——SQL语句你完全自己写,查询计划你完全自己优化,没有ORM的"惊喜"。

这篇文章,帮你把数据层从Room/CoreData迁移到RDB,同时封装出好用的数据访问层。

核心原理

数据层架构对比

graph TB
    subgraph Room["Android Room 架构"]
        R1["@Entity 注解定义表"] --> R2["@Dao 注解定义查询"]
        R2 --> R3[RoomDatabase 抽象类]
        R3 --> R4[SQLite 底层]
        R4 --> R5[自动建表/迁移]
    end
    
    subgraph CoreData["iOS CoreData 架构"]
        C1[".xcdatamodeld 模型文件"] --> C2[NSManagedObject 子类]
        C2 --> C3[NSManagedObjectContext]
        C3 --> C4[SQLite 底层]
        C4 --> C5[自动Schema迁移]
    end
    
    subgraph RDB["鸿蒙 RDB 架构"]
        H1[手动建表SQL] --> H2[手动CRUD方法]
        H2 --> H3[relationalStore 封装]
        H3 --> H4[SQLite 底层]
        H4 --> H5[手动版本迁移]
    end
    
    R1 -.->|注解→手动SQL| H1
    R2 -.->|自动→手动| H2
    R5 -.->|自动→手动| H5
    
    classDef roomStyle fill:#3DDC84,stroke:#2E7D32,color:#000,font-weight:bold
    classDef coreStyle fill:#F05138,stroke:#C73A25,color:#fff,font-weight:bold
    classDef rdbStyle fill:#0A59F7,stroke:#0A3CC7,color:#fff,font-weight:bold
    
    class R1,R2,R3,R4,R5 roomStyle
    class C1,C2,C3,C4,C5 coreStyle
    class H1,H2,H3,H4,H5 rdbStyle

核心概念映射

Room/CoreData概念 RDB对应 说明
@Entity CREATE TABLE语句 手动写建表SQL
@PrimaryKey PRIMARY KEY约束 SQL中声明
@ColumnInfo 列定义 SQL中声明
@Dao 数据访问类 手动封装CRUD方法
@Insert insert()方法 手动写insert语句
@Update update()方法 手动写update语句
@Delete delete()方法 手动写delete语句
@Query executeSql() 手动写SQL查询
@TypeConverter 手动序列化 JSON.stringify/parse
Migration 版本迁移逻辑 手动写ALTER TABLE
NSManagedObject TypeScript接口 纯数据结构

Room→RDB类型映射

Room/SQLite类型 RDB类型 TypeScript类型
INTEGER INTEGER number
REAL REAL number
TEXT TEXT string
BLOB BLOB ArrayBuffer
BOOLEAN INTEGER(0/1) number

代码实战

基础用法:Room Entity迁移到RDB

先看一个Room的Entity定义:

// Room: Entity定义
@Entity(tableName = "users")
data class User(
    @PrimaryKey val id: String,
    @ColumnInfo(name = "name") val name: String,
    @ColumnInfo(name = "email") val email: String,
    @ColumnInfo(name = "age") val age: Int = 0,
    @ColumnInfo(name = "avatar_url") val avatarUrl: String = "",
    @ColumnInfo(name = "created_at") val createdAt: Long = System.currentTimeMillis()
)

// Room: Dao定义
@Dao
interface UserDao {
    @Query("SELECT * FROM users")
    suspend fun getAll(): List<User>
    
    @Query("SELECT * FROM users WHERE id = :id")
    suspend fun getById(id: String): User?
    
    @Insert(onConflict = OnConflictStrategy.REPLACE)
    suspend fun insert(user: User)
    
    @Update
    suspend fun update(user: User)
    
    @Delete
    suspend fun delete(user: User)
    
    @Query("SELECT * FROM users WHERE name LIKE :keyword")
    suspend fun searchByName(keyword: String): List<User>
}

迁移到RDB:

// 鸿蒙: 手动建表 + 数据访问类
import { relationalStore } from '@kit.ArkData'

// 数据接口(对应Room的Entity)
export interface UserInfo {
  id: string
  name: string
  email: string
  age: number
  avatarUrl: string
  createdAt: number
}

// 建表SQL(对应Room的@Entity注解)
const CREATE_TABLE_SQL = `
  CREATE TABLE IF NOT EXISTS users (
    id TEXT PRIMARY KEY,
    name TEXT NOT NULL,
    email TEXT NOT NULL,
    age INTEGER DEFAULT 0,
    avatar_url TEXT DEFAULT '',
    created_at INTEGER DEFAULT 0
  )
`

// 数据访问类(对应Room的@Dao)
export class UserDao {
  private store: relationalStore.RdbStore | null = null
  
  // 初始化数据库
  async init(context: Context): Promise<void> {
    let config: relationalStore.StoreConfig = {
      name: 'app_database.db',
      securityLevel: relationalStore.SecurityLevel.S1
    }
    this.store = await relationalStore.getRdbStore(context, config)
    await this.store.executeSql(CREATE_TABLE_SQL)
  }
  
  // 查询全部(对应@Query("SELECT * FROM users"))
  async getAll(): Promise<UserInfo[]> {
    if (!this.store) return []
    
    let predicates = new relationalStore.RdbPredicates('users')
    let resultSet = await this.store.query(predicates)
    let users: UserInfo[] = []
    
    while (resultSet.goToNextRow()) {
      users.push(this.rowToUser(resultSet))
    }
    resultSet.close()
    return users
  }
  
  // 按ID查询(对应@Query("SELECT * FROM users WHERE id = :id"))
  async getById(id: string): Promise<UserInfo | null> {
    if (!this.store) return null
    
    let predicates = new relationalStore.RdbPredicates('users')
    predicates.equalTo('id', id)
    let resultSet = await this.store.query(predicates)
    
    let user: UserInfo | null = null
    if (resultSet.goToFirstRow()) {
      user = this.rowToUser(resultSet)
    }
    resultSet.close()
    return user
  }
  
  // 插入(对应@Insert)
  async insert(user: UserInfo): Promise<number> {
    if (!this.store) return -1
    
    let valueBucket: relationalStore.ValuesBucket = {
      id: user.id,
      name: user.name,
      email: user.email,
      age: user.age,
      avatar_url: user.avatarUrl,
      created_at: user.createdAt
    }
    return await this.store.insert('users', valueBucket)
  }
  
  // 更新(对应@Update)
  async update(user: UserInfo): Promise<number> {
    if (!this.store) return -1
    
    let valueBucket: relationalStore.ValuesBucket = {
      name: user.name,
      email: user.email,
      age: user.age,
      avatar_url: user.avatarUrl,
      created_at: user.createdAt
    }
    let predicates = new relationalStore.RdbPredicates('users')
    predicates.equalTo('id', user.id)
    return await this.store.update(valueBucket, predicates)
  }
  
  // 删除(对应@Delete)
  async delete(id: string): Promise<number> {
    if (!this.store) return -1
    
    let predicates = new relationalStore.RdbPredicates('users')
    predicates.equalTo('id', id)
    return await this.store.delete(predicates)
  }
  
  // 搜索(对应@Query("SELECT * FROM users WHERE name LIKE :keyword"))
  async searchByName(keyword: string): Promise<UserInfo[]> {
    if (!this.store) return []
    
    let predicates = new relationalStore.RdbPredicates('users')
    predicates.like('name', `%${keyword}%`)
    let resultSet = await this.store.query(predicates)
    let users: UserInfo[] = []
    
    while (resultSet.goToNextRow()) {
      users.push(this.rowToUser(resultSet))
    }
    resultSet.close()
    return users
  }
  
  // 行数据转对象
  private rowToUser(resultSet: relationalStore.ResultSet): UserInfo {
    return {
      id: resultSet.getString(resultSet.getColumnIndex('id')),
      name: resultSet.getString(resultSet.getColumnIndex('name')),
      email: resultSet.getString(resultSet.getColumnIndex('email')),
      age: resultSet.getLong(resultSet.getColumnIndex('age')),
      avatarUrl: resultSet.getString(resultSet.getColumnIndex('avatar_url')),
      createdAt: resultSet.getLong(resultSet.getColumnIndex('created_at'))
    }
  }
}

代码量比Room多不少,但逻辑更透明——每一步你都清楚在干什么。

进阶用法:封装Room风格的ORM

手动写CRUD太繁琐?来封装一个轻量ORM:

// 鸿蒙: 轻量ORM封装

// 表定义接口
interface ColumnDef {
  name: string
  type: 'TEXT' | 'INTEGER' | 'REAL' | 'BLOB'
  primaryKey?: boolean
  autoIncrement?: boolean
  notNull?: boolean
  defaultValue?: string | number
}

interface TableDef {
  name: string
  columns: ColumnDef[]
}

// ORM基类
abstract class BaseDao<T> {
  protected store: relationalStore.RdbStore | null = null
  protected abstract tableDef: TableDef
  
  // 初始化
  async init(store: relationalStore.RdbStore): Promise<void> {
    this.store = store
    await this.createTable()
  }
  
  // 自动建表
  private async createTable(): Promise<void> {
    if (!this.store) return
    
    let columns = this.tableDef.columns.map(col => {
      let def = `${col.name} ${col.type}`
      if (col.primaryKey) def += ' PRIMARY KEY'
      if (col.autoIncrement) def += ' AUTOINCREMENT'
      if (col.notNull) def += ' NOT NULL'
      if (col.defaultValue !== undefined) def += ` DEFAULT ${col.defaultValue}`
      return def
    }).join(', ')
    
    let sql = `CREATE TABLE IF NOT EXISTS ${this.tableDef.name} (${columns})`
    await this.store.executeSql(sql)
  }
  
  // 通用查询
  async queryAll(): Promise<T[]> {
    if (!this.store) return []
    let predicates = new relationalStore.RdbPredicates(this.tableDef.name)
    let resultSet = await this.store.query(predicates)
    return this.resultSetToList(resultSet)
  }
  
  // 条件查询
  async queryWhere(predicate: (predicates: relationalStore.RdbPredicates) => void): Promise<T[]> {
    if (!this.store) return []
    let predicates = new relationalStore.RdbPredicates(this.tableDef.name)
    predicate(predicates)
    let resultSet = await this.store.query(predicates)
    return this.resultSetToList(resultSet)
  }
  
  // 按ID查询
  async queryById(id: string | number): Promise<T | null> {
    let pkColumn = this.tableDef.columns.find(c => c.primaryKey)
    if (!pkColumn) return null
    
    let results = await this.queryWhere(p => p.equalTo(pkColumn.name, id))
    return results.length > 0 ? results[0] : null
  }
  
  // 插入
  async insert(entity: T): Promise<number> {
    if (!this.store) return -1
    let valueBucket = this.entityToBucket(entity)
    return await this.store.insert(this.tableDef.name, valueBucket)
  }
  
  // 批量插入
  async insertBatch(entities: T[]): Promise<number> {
    if (!this.store) return -1
    let buckets = entities.map(e => this.entityToBucket(e))
    return await this.store.batchInsert(this.tableDef.name, buckets)
  }
  
  // 更新
  async update(entity: T, whereClause: (predicates: relationalStore.RdbPredicates) => void): Promise<number> {
    if (!this.store) return -1
    let valueBucket = this.entityToBucket(entity)
    let predicates = new relationalStore.RdbPredicates(this.tableDef.name)
    whereClause(predicates)
    return await this.store.update(valueBucket, predicates)
  }
  
  // 删除
  async deleteWhere(whereClause: (predicates: relationalStore.RdbPredicates) => void): Promise<number> {
    if (!this.store) return -1
    let predicates = new relationalStore.RdbPredicates(this.tableDef.name)
    whereClause(predicates)
    return await this.store.delete(predicates)
  }
  
  // 子类实现:行数据转对象
  protected abstract rowToEntity(resultSet: relationalStore.ResultSet): T
  
  // 子类实现:对象转ValueBucket
  protected abstract entityToBucket(entity: T): relationalStore.ValuesBucket
  
  // 通用:ResultSet转列表
  private resultSetToList(resultSet: relationalStore.ResultSet): T[] {
    let list: T[] = []
    while (resultSet.goToNextRow()) {
      list.push(this.rowToEntity(resultSet))
    }
    resultSet.close()
    return list
  }
}

// 使用:定义User的Dao
class UserDaoV2 extends BaseDao<UserInfo> {
  protected tableDef: TableDef = {
    name: 'users',
    columns: [
      { name: 'id', type: 'TEXT', primaryKey: true, notNull: true },
      { name: 'name', type: 'TEXT', notNull: true },
      { name: 'email', type: 'TEXT', notNull: true },
      { name: 'age', type: 'INTEGER', defaultValue: 0 },
      { name: 'avatar_url', type: 'TEXT', defaultValue: '' },
      { name: 'created_at', type: 'INTEGER', defaultValue: 0 }
    ]
  }
  
  protected rowToEntity(resultSet: relationalStore.ResultSet): UserInfo {
    return {
      id: resultSet.getString(resultSet.getColumnIndex('id')),
      name: resultSet.getString(resultSet.getColumnIndex('name')),
      email: resultSet.getString(resultSet.getColumnIndex('email')),
      age: resultSet.getLong(resultSet.getColumnIndex('age')),
      avatarUrl: resultSet.getString(resultSet.getColumnIndex('avatar_url')),
      createdAt: resultSet.getLong(resultSet.getColumnIndex('created_at'))
    }
  }
  
  protected entityToBucket(user: UserInfo): relationalStore.ValuesBucket {
    return {
      id: user.id,
      name: user.name,
      email: user.email,
      age: user.age,
      avatar_url: user.avatarUrl,
      created_at: user.createdAt
    }
  }
  
  // 自定义查询方法
  async searchByName(keyword: string): Promise<UserInfo[]> {
    return this.queryWhere(p => p.like('name', `%${keyword}%`))
  }
  
  async getUsersByAge(minAge: number, maxAge: number): Promise<UserInfo[]> {
    return this.queryWhere(p => {
      p.greaterThanOrEqualTo('age', minAge)
      p.lessThanOrEqualTo('age', maxAge)
    })
  }
}

使用封装后的Dao:

// 初始化
let userDao = new UserDaoV2()
await userDao.init(store)

// CRUD操作
await userDao.insert({ id: '1', name: '张三', email: 'zhang@test.com', age: 25, avatarUrl: '', createdAt: Date.now() })
let allUsers = await userDao.queryAll()
let user = await userDao.queryById('1')
let searchResults = await userDao.searchByName('张')
await userDao.update(updatedUser, p => p.equalTo('id', '1'))
await userDao.deleteWhere(p => p.equalTo('id', '1'))

完整示例:数据库版本迁移

Room的Migration和CoreData的Lightweight Migration在RDB里都要手动实现:

// 鸿蒙: 数据库版本迁移
class DatabaseManager {
  private store: relationalStore.RdbStore | null = null
  private currentVersion: number = 3  // 当前最新版本
  
  async init(context: Context): Promise<void> {
    let config: relationalStore.StoreConfig = {
      name: 'app_database.db',
      securityLevel: relationalStore.SecurityLevel.S1
    }
    
    this.store = await relationalStore.getRdbStore(context, config)
    let version = this.store.version
    
    // 逐版本迁移
    if (version === 0) {
      // 全新安装,创建所有表
      await this.createAllTables()
      this.store.version = this.currentVersion
    } else if (version < this.currentVersion) {
      // 增量迁移
      await this.migrate(version, this.currentVersion)
      this.store.version = this.currentVersion
    }
  }
  
  // 全量建表
  private async createAllTables(): Promise<void> {
    if (!this.store) return
    
    await this.store.executeSql(`
      CREATE TABLE IF NOT EXISTS users (
        id TEXT PRIMARY KEY,
        name TEXT NOT NULL,
        email TEXT NOT NULL,
        age INTEGER DEFAULT 0,
        avatar_url TEXT DEFAULT '',
        created_at INTEGER DEFAULT 0,
        phone TEXT DEFAULT '',
        address TEXT DEFAULT ''
      )
    `)
    
    await this.store.executeSql(`
      CREATE TABLE IF NOT EXISTS orders (
        id TEXT PRIMARY KEY,
        user_id TEXT NOT NULL,
        amount REAL DEFAULT 0,
        status TEXT DEFAULT 'pending',
        created_at INTEGER DEFAULT 0,
        FOREIGN KEY (user_id) REFERENCES users(id)
      )
    `)
    
    await this.store.executeSql(`
      CREATE INDEX IF NOT EXISTS idx_orders_user_id ON orders(user_id)
    `)
  }
  
  // 增量迁移
  private async migrate(fromVersion: number, toVersion: number): Promise<void> {
    if (!this.store) return
    
    // V1 → V2: 给users表添加phone列
    if (fromVersion < 2) {
      await this.store.executeSql('ALTER TABLE users ADD COLUMN phone TEXT DEFAULT ""')
      console.info('数据库迁移: V1 → V2 完成')
    }
    
    // V2 → V3: 给users表添加address列,创建orders表
    if (fromVersion < 3) {
      await this.store.executeSql('ALTER TABLE users ADD COLUMN address TEXT DEFAULT ""')
      await this.store.executeSql(`
        CREATE TABLE IF NOT EXISTS orders (
          id TEXT PRIMARY KEY,
          user_id TEXT NOT NULL,
          amount REAL DEFAULT 0,
          status TEXT DEFAULT 'pending',
          created_at INTEGER DEFAULT 0,
          FOREIGN KEY (user_id) REFERENCES users(id)
        )
      `)
      await this.store.executeSql('CREATE INDEX IF NOT EXISTS idx_orders_user_id ON orders(user_id)')
      console.info('数据库迁移: V2 → V3 完成')
    }
  }
  
  // 获取Store实例
  getStore(): relationalStore.RdbStore | null {
    return this.store
  }
}

Room的Migration用注解声明,RDB的迁移用代码实现。虽然写起来更繁琐,但你可以做更复杂的迁移逻辑——数据清洗、表重构、索引优化,Room做不到的RDB都能做。

踩坑与注意事项

坑1:ResultSet必须close()

和HTTP请求必须destroy()一样,RDB的ResultSet必须手动close()。忘了关?游标泄漏,数据库连接耗尽。

// ❌ 错误:忘记close
let resultSet = await store.query(predicates)
while (resultSet.goToNextRow()) {
  // 处理数据
}
// 没有close!

// ✅ 正确:用try/finally确保close
let resultSet = await store.query(predicates)
try {
  while (resultSet.goToNextRow()) {
    // 处理数据
  }
} finally {
  resultSet.close()
}

坑2:列名和属性名不一致

Room用@ColumnInfo映射列名和属性名,RDB里你需要在rowToEntity和entityToBucket中手动处理。

// 数据库列名: avatar_url(下划线风格)
// TypeScript属性名: avatarUrl(驼峰风格)
// 需要手动映射!
protected rowToEntity(resultSet: relationalStore.ResultSet): UserInfo {
  return {
    avatarUrl: resultSet.getString(resultSet.getColumnIndex('avatar_url'))  // 列名→属性名
  }
}

protected entityToBucket(user: UserInfo): relationalStore.ValuesBucket {
  return {
    avatar_url: user.avatarUrl  // 属性名→列名
  }
}

坑3:事务支持

RDB支持事务,但API和Room不同:

// 鸿蒙: 事务操作
await store.beginTransaction()
try {
  await store.insert('users', userBucket)
  await store.insert('orders', orderBucket)
  store.commit()
} catch (e) {
  store.rollBack()
  throw e
}

坑4:没有@TypeConverter

Room的@TypeConverter可以自动处理复杂类型(Date→Long、List→JSON字符串)。RDB没有这个能力,你需要手动序列化:

// 手动类型转换
interface ArticleInfo {
  id: string
  tags: string[]  // 数组类型,数据库不支持
}

// 存储时序列化
protected entityToBucket(article: ArticleInfo): relationalStore.ValuesBucket {
  return {
    id: article.id,
    tags: JSON.stringify(article.tags)  // 数组→JSON字符串
  }
}

// 读取时反序列化
protected rowToEntity(resultSet: relationalStore.ResultSet): ArticleInfo {
  let tagsStr = resultSet.getString(resultSet.getColumnIndex('tags'))
  return {
    id: resultSet.getString(resultSet.getColumnIndex('id')),
    tags: JSON.parse(tagsStr) as string[]  // JSON字符串→数组
  }
}

坑5:数据库文件位置

鸿蒙的数据库文件存储在应用的沙箱目录中,路径和Android不同。你不能直接指定数据库文件路径,而是通过StoreConfig的name参数指定数据库名,系统自动管理路径。

HarmonyOS 6适配说明

HarmonyOS 6在数据层有几个重要改进:

  1. relationalStore性能优化:批量插入性能提升约50%,大数据量查询的内存占用降低30%。

  2. 加密数据库支持:新增了数据库加密能力,SecurityLevel.S3及以上级别的数据库会自动加密。

  3. WAL模式:默认启用Write-Ahead Logging,并发读写性能更好。

  4. 数据备份恢复:新增了数据库备份和恢复API,支持应用数据迁移。

  5. ORM工具辅助:DevEco Studio 6新增了数据库可视化工具,可以查看表结构、执行SQL、浏览数据,类似Android的DB Browser。

总结

数据层迁移的核心是"注解→手动SQL"的思维转变。Room帮你省掉的代码,RDB需要你手动补回来。但手动写SQL的好处是——你对数据库有完全的控制权。

维度 学习难度 使用频率 重要程度
建表/CRUD ⭐⭐ ⭐⭐⭐⭐⭐ ⭐⭐⭐⭐⭐
ORM封装 ⭐⭐⭐ ⭐⭐⭐⭐ ⭐⭐⭐⭐⭐
版本迁移 ⭐⭐⭐⭐ ⭐⭐⭐ ⭐⭐⭐⭐⭐
事务处理 ⭐⭐⭐ ⭐⭐⭐ ⭐⭐⭐⭐
类型转换 ⭐⭐⭐ ⭐⭐⭐⭐ ⭐⭐⭐⭐
ResultSet管理 ⭐⭐ ⭐⭐⭐⭐⭐ ⭐⭐⭐⭐⭐

建议迁移时先封装一个BaseDao,把通用的CRUD逻辑抽出来。这样每个表的Dao只需要关注建表SQL和字段映射,代码量能减少60%以上。别在业务代码里直接写SQL,那和不用ORM有什么区别?

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。