Room 数据库入门:核心三件套

聊到 Android 本地存储,你肯定绕不开 Room。说实话,我早年做项目还在手撸 SQLiteOpenHelper,写那堆 contract 类、helper 类、cursor 遍历……改个字段得改三四处,烦得很。后来 Google 推出了 Room,我只用了半天就彻底「真香」了。它本质上是对 SQLite 的一层抽象,但帮我们省掉了大量模板代码。

简单说,Room 由三个部分组成:Entity(表定义)、DAO(数据访问对象)、Database(数据库实例)。你把这三样东西写清楚,增删改查就全自动化了。

Room 三大组件关系图 @Database AppDatabase @Dao UserDao @Dao BookDao @Entity User @Entity Book @Entity Loan 一个 Database 可以管理多个 Entity,每个 Entity 对应一张表,每个 DAO 负责对某个 Entity 的操作

1. Entity —— 把数据类变成数据库表

Entity 说白了就是你的数据模型。用 @Entity 注解标记一个普通 Kotlin 数据类,Room 会自动把它映射成一张 SQLite 表。举个常见例子:

@Entity(tableName = "users")
data class User(
    @PrimaryKey(autoGenerate = true)
    val id: Int = 0,
    @ColumnInfo(name = "full_name")
    val name: String,
    val age: Int,
    val email: String?
)

要点拆解:

  • @PrimaryKey 指定主键,可以用 autoGenerate = true 让 Room 自动生成。
  • @ColumnInfo 可以自定义字段名,默认会用变量名作为列名。
  • @Ignore 注解让某个字段不被持久化,比如临时计算字段。
  • 表名默认和类名一致,但建议用 tableName 显式指定。
我的习惯: Entity 里尽量不要写业务逻辑,只做数据容器的活儿。我之前见过有人把网络请求也塞进 Entity,结果迁移时爆炸。

2. DAO —— 数据访问的「管家」

DAO 是一个接口,你用 @Dao 标记,在里面定义各种数据库操作。Room 会在编译时帮你生成实现代码。来看一个基本的 UserDao:

@Dao
interface UserDao {
    @Query("SELECT * FROM users")
    fun getAllUsers(): List<User>

    @Insert(onConflict = OnConflictStrategy.REPLACE)
    fun insertUser(user: User)

    @Update
    fun updateUser(user: User)

    @Delete
    fun deleteUser(user: User)

    @Query("SELECT * FROM users WHERE age > :minAge")
    fun getUsersOlderThan(minAge: Int): List<User>
}

这里面有四个「一键操作」:

  • @Insert、@Update、@Delete 会按参数自动生成 SQL,你都不用写 SQL。
  • @Query 让你写自定义 SQL,需要自己注意 ? 或 :参数名 的语法。
  • 方法可以返回 LiveData 或 Flow 实现响应式监听——这个后面我们会细讲。
我曾经踩过的坑: 注意 @Insert 的返回值可以设为 Long(新插入行的 ID),但如果同时插入多条,就不好拿了。还有,@Update 默认根据主键匹配,如果传进来的 Entity 主键不存在,它不会报错,但也不会插入——就是个静默的 no-op。

3. Database —— 连接 Entity 与 DAO 的桥梁

第三步,创建一个抽象类继承 RoomDatabase,用 @Database 注解注册所有 Entity 并指定版本号。这个类负责提供 DAO 实例。

@Database(
    entities = [User::class, Book::class],
    version = 1,
    exportSchema = true
)
abstract class AppDatabase : RoomDatabase() {
    abstract fun userDao(): UserDao
    abstract fun bookDao(): BookDao

    companion object {
        @Volatile
        private var INSTANCE: AppDatabase? = null

        fun getDatabase(context: Context): AppDatabase {
            return INSTANCE ?: synchronized(this) {
                val instance = Room.databaseBuilder(
                    context.applicationContext,
                    AppDatabase::class.java,
                    "my_awesome_database.db"
                ).build()
                INSTANCE = instance
                instance
            }
        }
    }
}

初始化注意点:

  • 用 applicationContext 避免内存泄漏。
  • @Volatile + synchronized 保证单例线程安全。
  • exportSchema = true 会输出数据库 Schema 到文件,方便版本管理。
为什么需要单例? Room 实例的创建开销不小,同一个进程中只应该存在一个 Database 实例。多实例可能导致数据不一致或崩溃。

4. 从 SQLite 到 Room:一个对比表格

为了让你更直观地感受 Room 带来的简化,我列了个对比:

环节 传统 SQLite Room
表定义 写 CREATE TABLE + Contract 类 @Entity 注解 on data class
增删改查 手写 SQL + ContentValues @Insert / @Update / @Delete / @Query
版本迁移 自己写 onUpgrade,极易出错 Migration 对象 + autoMigrations 可选
类型安全 编译期不检查,运行时可能崩溃 编译期校验 SQL 和类型
响应式观察 需要自己实现 Observer 模式 直接返回 LiveData / Flow

你看完这个表应该就懂了——Room 把 SQLite 那些俗套工作全包了。我在早期项目里用 SQLite 写过 200 多行的 Helper 类,后来用 Room 重构后只剩 50 行,维护起来也轻松得多。

5. 一个完整的 Room 集成步骤

最后我帮你捋一捋,把今天讲的内容串成可执行的动作:

  1. 添加依赖:在 build.gradle 的 dependencies 里加入 Room 的 runtime、compiler(KSP 或 kapt)和 kotlin 扩展库。
  2. 创建 Entity:用 @Entity 标记数据类,@PrimaryKey 指定主键。
  3. 创建 DAO:用 @Dao 标记接口,定义 @Insert、@Query 等方法。
  4. 创建 Database:继承 RoomDatabase,用 @Database 注册 Entity 列表,暴露 DAO 方法。
  5. 初始化:在 Application 或 Repository 中调用 Room.databaseBuilder 创建单例。
关于数据库文件名: 我一般用 "{app_name}_db" 的命名风格,后面加个 .db 后缀。如果你希望数据库放在固定位置(比如 SD 卡),可以用 Room.databaseBuilder(context, ... , filePath).build() 的形式,不过不推荐普通 App 这么做。