Room 三剑客:Entity、DAO 与 Database

说实话,Room 这个库我挺喜欢的。它不是那种给你一堆新概念让你重新学一遍的东西,反而更像是在 Android 本地存储领域做了一个「标准化」的事。说白了,就是把我们以前写 SQLite 那套原始操作,用注解的方式包装得干干净净。

Entity、DAO、Database,这就是 Room 的三个核心部件。我管它们叫「三剑客」。你搞懂了这三者的关系和职责,Room 的基本功就算扎实了。下面我挨个聊聊。

1. Entity:你的数据模型

Entity 就是数据库里的表。在 Room 里,一个 Kotlin 数据类配上 @Entity 注解,就能映射成一张表。我曾经见过刚接触 Room 的同事把 Entity 当成普通的 Bean 来写,结果字段类型乱用,导致运行时崩溃。嗯,这里得注意。

@Entity(tableName = "users")
data class UserEntity(
    @PrimaryKey(autoGenerate = true)
    val id: Long = 0,
    val name: String,
    val age: Int,
    @ColumnInfo(name = "created_at")
    val createdAt: Long = System.currentTimeMillis()
)

几个关键点我提一下:

  • 主键:每个 Entity 必须有一个 @PrimaryKey。支持单个字段或复合主键(用 primaryKeys 属性)。
  • 字段映射:Kotlin 的字段名默认就是列名。想改名?用 @ColumnInfo 指定。
  • 忽略字段:不想存到数据库的字段,加 @Ignore 注解就行。我一般在 Entity 里放一些临时计算用的字段就会用到它。
我个人习惯把 Entity 的字段都用基本类型或可空类型。避免用 List 这种复杂类型,除非你真的用了 TypeConverter。否则 Room 编译期就会报错,你省不了事。

2. DAO:数据操作的契约

DAO 是 Data Access Object 的缩写,我更喜欢叫它「数据访问层」。它是一个接口,你用注解声明增删改查的方法,Room 在编译期帮你生成实现代码。这不是运行时反射,是编译期代码生成,所以性能很不错。

@Dao
interface UserDao {
    @Query("SELECT * FROM users WHERE id = :userId")
    suspend fun getUserById(userId: Long): UserEntity?

    @Insert(onConflict = OnConflictStrategy.REPLACE)
    suspend fun insertUser(user: UserEntity): Long

    @Update
    suspend fun updateUser(user: UserEntity)

    @Delete
    suspend fun deleteUser(user: UserEntity)
}

这里要说的东西不少。

  • @Query:最灵活的注解。写原生 SQL,Room 会在编译期校验语法。我遇到过因为表名拼写错误导致编译失败的,嗯,这种错总比运行时炸掉好。
  • @Insert:支持传入单个对象或集合。冲突策略我常用 ON_REPLACE,省得单独处理去重。
  • @Update 和 @Delete:根据主键匹配。如果你只传了一个 UserEntity 但主键不对应任何记录,更新和删除不会报错,但也不会生效。这个要注意。
曾经有个同事在 DAO 里写 suspend 函数但忘了加协程依赖,结果 Room 直接编译不过。记住了,Room 的 DAO 方法支持 suspend 函数,但前提是你的项目引入了 kotlinx-coroutines 依赖。

3. Database:数据库的入口

Database 是一个抽象类,继承 RoomDatabase。它把 Entity 和 DAO 串起来,还负责版本管理、迁移、创建等任务。

@Database(
    entities = [UserEntity::class, AddressEntity::class],
    version = 1,
    exportSchema = true
)
abstract class AppDatabase : RoomDatabase() {
    abstract fun userDao(): UserDao
    abstract fun addressDao(): AddressDao

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

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

几个重点:

  • entities 数组:列出所有 Entity 类。Room 会根据这些类创建对应的表。
  • version:数据库版本号。每次改动表结构都要加 1,并配合 Migration 或 fallbackToDestructiveMigration 使用。
  • exportSchema:设为 true 会在编译期输出数据库 schema 的 JSON 文件。这个文件在版本迁移时特别有用,我建议开启。
  • 单例模式:Database 实例建议用单例。创建开销不小,多个实例还可能引发并发问题。

4. 三者如何协作?

你想想看,整个流程是这样的:

  1. 你定义 Entity 来描述数据模型和表结构。
  2. 你定义 DAO 接口来声明对表的操作。
  3. 你创建 Database 抽象类,把 Entity 和 DAO 注册进去。
  4. 使用 Database 实例获取 DAO,然后调用 DAO 的方法操作数据。

Room 在编译期会做三件事:生成 DAO 的实现类、创建表结构、校验 SQL 语法。这个过程我挺放心的,毕竟编译期能抓到的错,总比运行时抓要好。

核心结论:Entity 是数据容器,DAO 是操作接口,Database 是管理入口。三者分工明确,各司其职。你用 Room 时别想着跳过哪一步,也别把它们的职责混在一起。

5. 知识结构速览

下面这个表格总结了三剑客的核心要点,方便你快速回顾:

组件 职责 关键注解/方法 我的建议
Entity 映射数据库表结构 @Entity, @PrimaryKey, @ColumnInfo, @Ignore 字段类型尽量简单,避免复杂对象
DAO 定义数据操作接口 @Insert, @Update, @Delete, @Query 多用 suspend 函数,配合协程使用
Database 数据库的创建与管理 @Database, Room.databaseBuilder, Migration 单例模式创建,开启 exportSchema

6. Room 三剑客协作流程图

下面这张图帮你理清三者的关系和协作流程。看完应该能对 Room 的整体结构有个直观认识。

Room 三剑客协作流程 Entity @Entity @PrimaryKey @ColumnInfo @Ignore 数据模型 / 表结构 DAO @Dao @Insert / @Update @Delete / @Query suspend 函数 数据操作接口 Database @Database entities = [...] version / Migration Room.databaseBuilder 入口 & 版本管理 协作流程 1. 定义 Entity(数据模型 + 表映射) 2. 定义 DAO 接口(CRUD 操作声明) 3. 创建 Database(注册 Entity 和 DAO) 4. 通过 Database 获取 DAO 实例 → 操作数据 编译期:代码生成 → SQL 校验 → 表创建

7. 避坑指南

最后,分享几个我摸爬滚打换来的教训:

  • Entity 字段不要用 Int 存时间戳?我建议用 Long,否则 2038 年你会后悔的。
  • DAO 里的 @Query 尽量写具体的列名,别用 SELECT *。为什么?因为表结构变更时,列名冲突很难排查。
  • Database 版本升级一定写 Migration。有一次我直接 fallbackToDestructiveMigration,用户数据全丢了……从那以后我再也不敢偷懒了。

Room 的三剑客就是这么回事。从 Entity 到 DAO 再到 Database,环环相扣,结构清晰。你只要把它们之间的关系搞懂了,后面的离线架构、数据同步、版本迁移,学起来都会顺很多。