Room 数据库简介:架构、SQLite 关系与三大组件

做 Android 本地存储,绕不开 Room。我个人刚接触时也纳闷:直接用 SQLite 不香吗?后来才发现,SQLite 那套手动管理游标、写 SQL 模板的活儿,在复杂项目里简直就是噩梦。Room 本质上是对 SQLite 的一层封装,但它带来的不是“多一层麻烦”,而是帮你把脏活累活都干了。

Room 与 SQLite:到底差在哪?

说直白点,SQLite 是数据库引擎,Room 是上层框架。你写的 SQL 最终还是跑在 SQLite 上。那为什么还要用 Room?我遇到过好几次团队里有人直接裸写 SQLiteHelper,结果数据库升级时漏了一个字段,整个应用闪退——嗯,这种坑踩过一次就够了。

Room 提供了编译时 SQL 校验、自动生成样板代码、与 LiveData / Flow 无缝集成。说白了,它让你用面向对象的方式操作数据库,不用手动管游标、不用重复写那些烦人的 ContentValues。下面这张图能帮你快速理清 Room 的定位:

Room 框架架构与 SQLite 关系 Application / ViewModel Room Database(抽象类) 数据库创建、升级、连接管理 Entity(数据表映射) DAO(数据访问对象) SQLite Database SQL 执行引擎、事务、索引 引用 提供 执行 SQL 表结构定义 注意:Room 不直接操作 SQLite API,它通过编译 时生成的实现类完成调用 💡 箭头方向表示数据/请求流向:Application → Database → DAO → SQLite

三大核心组件:Entity、DAO、Database

理解了整体关系,我们再逐个拆解。我习惯把 Entity 看作“表结构的 Kotlin/Java 化身”,DAO 是“你写 SQL 的地方”,Database 则是“把这些串起来的胶水”。

1. Entity —— 数据表映射

每一个 Entity 类对应一张数据库表,字段对应表的列。你只需要用注解标注主键,简单得离谱:

@Entity(tableName = "users")
data class User(
    @PrimaryKey val id: Long,
    @ColumnInfo(name = "full_name") val name: String,
    val age: Int
)

我当年踩过一个坑:忘了加 @ColumnInfo 注解,Room 直接用 Kotlin 变量名做列名,结果后端返回的是 full_name,映射不上。嗯,所以字段名跟表列名不一样时,一定记得显式声明。

小技巧:Entity 里可以加 @Ignore 注解跳过某些字段,比如一个临时计算的年龄层。Room 不会把它存入数据库。

2. DAO —— 数据访问对象

DAO 是一个接口或抽象类,你在里面定义各种数据库操作方法,用注解标注 SQL 语句。Room 在编译时会自动帮你生成实现类。看个例子:

@Dao
interface UserDao {
    @Query("SELECT * FROM users WHERE age > :minAge")
    suspend fun getUsersOlderThan(minAge: Int): List<User>

    @Insert
    suspend fun insertUser(user: User)

    @Delete
    suspend fun deleteUser(user: User)
}

注意这里的 suspend 关键字——配合协程,数据库操作切到 IO 线程,UI 线程不会被卡。我个人习惯所有 DAO 方法都用挂起函数,这样调用方不需要手动管理线程。

曾经犯过的错:我在一个多表联查的 @Query 里拼错了表名,编译期 Room 直接报错。多亏它是编译时校验,要是运行时才发现,线上事故就来了。所以那些觉得 Room 搭建起来麻烦的同学,想想它帮你省了多少调试时间。

3. Database —— 数据库的入口

Database 是一个抽象类,继承自 RoomDatabase。你需要定义它关联的 Entity 列表、DAO 的抽象方法,以及版本号。构建时 Room 会帮你创建表、管理版本迁移:

@Database(
    entities = [User::class, Address::class],
    version = 1,
    exportSchema = false
)
abstract class AppDatabase : RoomDatabase() {
    abstract fun userDao(): UserDao
    abstract fun addressDao(): AddressDao
}

记得在 Application 里单例初始化:

val db = Room.databaseBuilder(
    context.applicationContext,
    AppDatabase::class.java,
    "app_database.db"
).build()
核心要点:Database 负责整个生命周期——打开连接、创建表、处理迁移。DAO 的实例从 Database 获取,数据库的引用应该全局唯一,避免反复创建造成内存泄漏。

三者的协作流程

我把日常调用流程整理成下面几步,你看完应该就明白了:

  1. App 启动 → 通过 Room.databaseBuilder 创建 AppDatabase 实例。
  2. 业务层 调用 database.userDao() 拿到 UserDao。
  3. DAO 方法 执行 SQL(Insert/Query/Update/Delete),底层调用 SQLiteDatabase API。
  4. 返回结果:查询直接返回 Entity 对象或列表,插入/删除返回影响行数。

整个过程你不需要写一行 SQLiteOpenHelper 或 Cursor 代码。Room 在编译时生成的实现类帮你把这些全包了。

选型时的几点建议

结合我自己的项目经验,给你几个实用建议:

  • 如果数据关系复杂(比如多表联查、复杂事务),Room 更合适。它的 Entity 和 DAO 天然支持关联查询。
  • 如果只是存 key-value,DataStore 更轻量。Room 对简单键值对来说太重了。
  • 数据库迁移用 Room 的 Migration 类加版本号控制,永远不要手动改数据库文件名。
  • 调试阶段可以在 databaseBuilder 后加 .fallbackToDestructiveMigration(),但上线前一定要去掉——你想想看,用户数据说没就没,这锅谁背?
我的个人习惯:在 `@Database` 中把 exportSchema = true 打开(release 需关闭),Room 会输出 JSON schema 文件,方便查看表结构变更历史。配合版本对比,数据库迁移几乎不会出错。