Room 数据库入门:核心三件套
聊到 Android 本地存储,你肯定绕不开 Room。说实话,我早年做项目还在手撸 SQLiteOpenHelper,写那堆 contract 类、helper 类、cursor 遍历……改个字段得改三四处,烦得很。后来 Google 推出了 Room,我只用了半天就彻底「真香」了。它本质上是对 SQLite 的一层抽象,但帮我们省掉了大量模板代码。
简单说,Room 由三个部分组成:Entity(表定义)、DAO(数据访问对象)、Database(数据库实例)。你把这三样东西写清楚,增删改查就全自动化了。
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 集成步骤
最后我帮你捋一捋,把今天讲的内容串成可执行的动作:
- 添加依赖:在
build.gradle的dependencies里加入 Room 的 runtime、compiler(KSP 或 kapt)和 kotlin 扩展库。 - 创建 Entity:用
@Entity标记数据类,@PrimaryKey指定主键。 - 创建 DAO:用
@Dao标记接口,定义@Insert、@Query等方法。 - 创建 Database:继承
RoomDatabase,用@Database注册 Entity 列表,暴露 DAO 方法。 - 初始化:在 Application 或 Repository 中调用
Room.databaseBuilder创建单例。
关于数据库文件名: 我一般用
"{app_name}_db" 的命名风格,后面加个 .db 后缀。如果你希望数据库放在固定位置(比如 SD 卡),可以用 Room.databaseBuilder(context, ... , filePath).build() 的形式,不过不推荐普通 App 这么做。