01-10-28 Room数据库框架:Android官方SQLite封装方案
摘要
Room是Google官方推出的Android SQLite数据库封装库,通过编译时注解处理生成数据库访问代码,提供了类型安全、易于测试和维护的数据库操作API。本文深入剖析Room的核心实现原理,包括数据库架构设计、DAO代码生成机制、Migration迁移策略、LiveData集成以及多线程处理,揭示其如何简化Android数据库开发。
关键词:Room、SQLite、数据库、ORM、编译时注解、DAO、Migration、Android Jetpack
一、Room架构概览
1.1 核心架构设计
1.2 三大核心组件
二、核心注解设计
2.1 @Database注解
// room-common/src/main/java/androidx/room/Database.java
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.CLASS)
public @interface Database {
/**
* 数据库中的Entity列表
*/
Class<?>[] entities();
/**
* 数据库中的View列表
*/
Class<?>[] views() default {};
/**
* 数据库版本号
*/
int version();
/**
* 是否导出schema文件
*/
boolean exportSchema() default true;
}
// 使用示例
@Database(
entities = {User.class, Order.class},
version = 1,
exportSchema = false
)
public abstract class AppDatabase extends RoomDatabase {
public abstract UserDao userDao();
public abstract OrderDao orderDao();
private static volatile AppDatabase INSTANCE;
public static AppDatabase getInstance(Context context) {
if (INSTANCE == null) {
synchronized (AppDatabase.class) {
if (INSTANCE == null) {
INSTANCE = Room.databaseBuilder(
context.getApplicationContext(),
AppDatabase.class,
"app_database"
).build();
}
}
}
return INSTANCE;
}
}
2.2 @Entity注解
// room-common/src/main/java/androidx/room/Entity.java
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.CLASS)
public @interface Entity {
/**
* 表名,默认使用类名
*/
String tableName() default "";
/**
* 索引定义
*/
Index[] indices() default {};
/**
* 是否继承父类字段
*/
boolean inheritSuperIndices() default false;
/**
* 主键定义
*/
String[] primaryKeys() default {};
/**
* 外键定义
*/
ForeignKey[] foreignKeys() default {};
}
// 使用示例
@Entity(
tableName = "users",
indices = {
@Index(value = {"email"}, unique = true),
@Index(value = {"name", "age"})
}
)
public class User {
@PrimaryKey(autoGenerate = true)
private long id;
@ColumnInfo(name = "user_name")
private String name;
@ColumnInfo(name = "user_age")
private int age;
private String email;
@Ignore
private String tempData;
// Getters and setters
}
2.3 @Dao注解
// room-common/src/main/java/androidx/room/Dao.java
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.CLASS)
public @interface Dao {
}
// 使用示例
@Dao
public interface UserDao {
@Query("SELECT * FROM users")
List<User> getAllUsers();
@Query("SELECT * FROM users WHERE id = :userId")
User getUserById(long userId);
@Query("SELECT * FROM users WHERE name LIKE :searchQuery")
LiveData<List<User>> searchUsers(String searchQuery);
@Insert(onConflict = OnConflictStrategy.REPLACE)
long insertUser(User user);
@Insert
void insertUsers(List<User> users);
@Update
int updateUser(User user);
@Delete
void deleteUser(User user);
@Query("DELETE FROM users WHERE id = :userId")
void deleteUserById(long userId);
@Transaction
@Query("SELECT * FROM users WHERE id = :userId")
UserWithOrders getUserWithOrders(long userId);
}
三、编译时注解处理
3.1 RoomProcessor核心逻辑
// room-compiler/src/main/kotlin/androidx/room/RoomProcessor.kt
@AutoService(Processor::class)
class RoomProcessor : BasicAnnotationProcessor() {
override fun steps(): Iterable<Step> {
return listOf(
DatabaseProcessingStep(),
DaoProcessingStep(),
EntityProcessingStep()
)
}
private inner class DatabaseProcessingStep : Step {
override fun annotations(): Set<String> {
return setOf(Database::class.java.canonicalName)
}
override fun process(
elementsByAnnotation: SetMultimap<String, Element>
): Set<Element> {
val databases = elementsByAnnotation[Database::class.java.canonicalName]
databases.forEach { element ->
val typeElement = element as TypeElement
val database = DatabaseProcessor(
processingEnv,
typeElement
).process()
// 生成DatabaseImpl类
DatabaseWriter(database).write(processingEnv)
}
return emptySet()
}
}
}
3.2 Database代码生成
// room-compiler/src/main/kotlin/androidx/room/writer/DatabaseWriter.kt
class DatabaseWriter(val database: Database) : ClassWriter(database.typeName) {
override fun createTypeSpecBuilder(): TypeSpec.Builder {
val builder = TypeSpec.classBuilder(database.implTypeName)
.addModifiers(PUBLIC)
.superclass(database.typeName)
// 生成DAO实例字段
database.daoMethods.forEach { dao ->
builder.addField(
FieldSpec.builder(dao.dao.typeName, dao.dao.implTypeName.simpleName())
.addModifiers(PRIVATE, VOLATILE)
.build()
)
}
// 生成createOpenHelper方法
builder.addMethod(createOpenHelperMethod())
// 生成createInvalidationTracker方法
builder.addMethod(createInvalidationTrackerMethod())
// 生成clearAllTables方法
builder.addMethod(createClearAllTablesMethod())
// 生成DAO getter方法
database.daoMethods.forEach { dao ->
builder.addMethod(createDaoMethod(dao))
}
return builder
}
private fun createOpenHelperMethod(): MethodSpec {
val implVar = "_helper"
return MethodSpec.methodBuilder("createOpenHelper")
.addAnnotation(Override::class.java)
.addModifiers(PROTECTED)
.returns(SupportSQLiteOpenHelper::class.java)
.addParameter(DatabaseConfiguration::class.java, "configuration")
.apply {
addStatement(
"\$T \$L = new \$T(\$N, new \$T(\$N, \$L, \$L) { ... })",
SupportSQLiteOpenHelper::class.java, implVar,
FrameworkSQLiteOpenHelperFactory::class.java, "configuration",
SupportSQLiteOpenHelper.Callback::class.java, "configuration",
database.version, database.version
)
addStatement("return \$L", implVar)
}
.build()
}
private fun createDaoMethod(dao: DaoMethod): MethodSpec {
val daoFieldName = dao.dao.implTypeName.simpleName()
return MethodSpec.methodBuilder(dao.name)
.addAnnotation(Override::class.java)
.addModifiers(PUBLIC)
.returns(dao.dao.typeName)
.apply {
beginControlFlow("if (\$L != null)", daoFieldName)
addStatement("return \$L", daoFieldName)
nextControlFlow("else")
beginControlFlow("synchronized(this)")
beginControlFlow("if(\$L == null)", daoFieldName)
addStatement("\$L = new \$T(this)", daoFieldName, dao.dao.implTypeName)
endControlFlow()
addStatement("return \$L", daoFieldName)
endControlFlow()
endControlFlow()
}
.build()
}
}
3.3 DAO代码生成
// room-compiler/src/main/kotlin/androidx/room/writer/DaoWriter.kt
class DaoWriter(val dao: Dao, val dbParam: DatabaseWriter.DaoParamField) : ClassWriter(dao.typeName) {
override fun createTypeSpecBuilder(): TypeSpec.Builder {
val builder = TypeSpec.classBuilder(dao.implTypeName)
.addModifiers(PUBLIC, FINAL)
.addSuperinterface(dao.typeName)
// 添加数据库引用字段
builder.addField(
FieldSpec.builder(dbParam.type, dbParam.name, PRIVATE, FINAL).build()
)
// 添加构造器
builder.addMethod(createConstructor())
// 生成所有方法实现
dao.queryMethods.forEach { method ->
builder.addMethod(createQueryMethod(method))
}
dao.insertionMethods.forEach { method ->
builder.addMethod(createInsertMethod(method))
}
dao.updateMethods.forEach { method ->
builder.addMethod(createUpdateMethod(method))
}
dao.deletionMethods.forEach { method ->
builder.addMethod(createDeleteMethod(method))
}
return builder
}
private fun createQueryMethod(method: QueryMethod): MethodSpec {
val returnType = method.returnType
val methodBuilder = MethodSpec.methodBuilder(method.name)
.addAnnotation(Override::class.java)
.addModifiers(PUBLIC)
.returns(returnType.typeName)
// 添加参数
method.parameters.forEach { param ->
methodBuilder.addParameter(param.typeName, param.name)
}
// 生成SQL查询代码
val queryVar = scope.getTmpVar("_sql")
methodBuilder.addStatement(
"final \$T \$L = \$S",
String::class.java, queryVar, method.query.original
)
// 生成RoomSQLiteQuery
val roomSqlQueryVar = scope.getTmpVar("_statement")
methodBuilder.addStatement(
"final \$T \$L = \$T.acquire(\$L, \$L)",
RoomSQLiteQuery::class.java, roomSqlQueryVar,
RoomSQLiteQuery::class.java, queryVar,
method.parameters.size
)
// 绑定参数
method.parameters.forEachIndexed { index, param ->
bindQueryParameter(methodBuilder, param, index + 1, roomSqlQueryVar)
}
// 执行查询并处理结果
if (returnType is LiveDataType) {
// LiveData返回类型
createLiveDataQuery(methodBuilder, method, roomSqlQueryVar)
} else {
// 普通返回类型
createNormalQuery(methodBuilder, method, roomSqlQueryVar)
}
return methodBuilder.build()
}
private fun createInsertMethod(method: InsertionMethod): MethodSpec {
val methodBuilder = MethodSpec.methodBuilder(method.name)
.addAnnotation(Override::class.java)
.addModifiers(PUBLIC)
.returns(method.returnType.typeName)
method.parameters.forEach { param ->
methodBuilder.addParameter(param.typeName, param.name)
}
// 生成插入代码
methodBuilder.addStatement("\$L.assertNotSuspendingTransaction()", dbParam.name)
methodBuilder.addStatement("\$L.beginTransaction()", dbParam.name)
methodBuilder.beginControlFlow("try")
val insertStmtVar = scope.getTmpVar("_stmt")
methodBuilder.addStatement(
"\$T \$L = \$L.getInsertionStatement()",
SupportSQLiteStatement::class.java, insertStmtVar, method.entity.insertionAdapterField
)
// 绑定参数并执行
method.parameters.forEach { param ->
bindInsertParameter(methodBuilder, param, insertStmtVar)
methodBuilder.addStatement("\$L.executeInsert()", insertStmtVar)
}
methodBuilder.addStatement("\$L.setTransactionSuccessful()", dbParam.name)
if (method.returnType.typeName != TypeName.VOID) {
methodBuilder.addStatement("return \$L", scope.getTmpVar("_result"))
}
methodBuilder.nextControlFlow("finally")
methodBuilder.addStatement("\$L.endTransaction()", dbParam.name)
methodBuilder.endControlFlow()
return methodBuilder.build()
}
}
四、数据库迁移机制
4.1 Migration接口
// room-runtime/src/main/java/androidx/room/migration/Migration.java
public abstract class Migration {
public final int startVersion;
public final int endVersion;
public Migration(int startVersion, int endVersion) {
this.startVersion = startVersion;
this.endVersion = endVersion;
}
/**
* 执行迁移逻辑
*/
public abstract void migrate(@NonNull SupportSQLiteDatabase database);
}
// 使用示例
static final Migration MIGRATION_1_2 = new Migration(1, 2) {
@Override
public void migrate(@NonNull SupportSQLiteDatabase database) {
// 添加新列
database.execSQL("ALTER TABLE users ADD COLUMN phone TEXT");
}
};
static final Migration MIGRATION_2_3 = new Migration(2, 3) {
@Override
public void migrate(@NonNull SupportSQLiteDatabase database) {
// 创建新表
database.execSQL("CREATE TABLE IF NOT EXISTS `orders` " +
"(`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, " +
"`user_id` INTEGER NOT NULL, " +
"`total` REAL NOT NULL)");
// 创建索引
database.execSQL("CREATE INDEX `index_orders_user_id` ON `orders` (`user_id`)");
}
};
// 应用迁移
AppDatabase db = Room.databaseBuilder(context, AppDatabase.class, "database")
.addMigrations(MIGRATION_1_2, MIGRATION_2_3)
.build();
4.2 迁移路径查找
// room-runtime/src/main/java/androidx/room/migration/MigrationPathFinder.java
class MigrationPathFinder {
/**
* 查找迁移路径
*/
public List<Migration> findMigrationPath(int start, int end) {
if (start == end) {
return Collections.emptyList();
}
boolean migratingUp = end > start;
List<Migration> result = new ArrayList<>();
return findUpMigrationPath(result, migratingUp, start, end);
}
private List<Migration> findUpMigrationPath(
List<Migration> result,
boolean upgrade,
int start,
int end
) {
while (upgrade ? start < end : start > end) {
Set<Migration> targetNodes = mMigrations.get(start);
if (targetNodes == null) {
return null;
}
int targetVersion = upgrade ? end : start;
int finalVersion = upgrade ? start : end;
Migration migration = null;
int minDistance = Integer.MAX_VALUE;
// 查找最短路径
for (Migration m : targetNodes) {
int potentialVersion = upgrade ? m.endVersion : m.startVersion;
if (potentialVersion <= targetVersion && potentialVersion > finalVersion) {
int distance = Math.abs(potentialVersion - targetVersion);
if (distance < minDistance) {
migration = m;
minDistance = distance;
}
}
}
if (migration == null) {
return null;
}
result.add(migration);
start = upgrade ? migration.endVersion : migration.startVersion;
}
return result;
}
}
4.3 自动迁移(Room 2.4+)
@Database(
entities = [User::class],
version = 2,
autoMigrations = [
AutoMigration(from = 1, to = 2)
]
)
abstract class AppDatabase : RoomDatabase() {
abstract fun userDao(): UserDao
}
// 复杂自动迁移(需要自定义逻辑)
@Database(
entities = [User::class],
version = 3,
autoMigrations = [
AutoMigration(
from = 2,
to = 3,
spec = Migration2To3::class
)
]
)
abstract class AppDatabase : RoomDatabase() {
@DeleteColumn(tableName = "users", columnName = "temp_data")
@RenameColumn(tableName = "users", fromColumnName = "name", toColumnName = "user_name")
class Migration2To3 : AutoMigrationSpec
}
五、LiveData和Flow集成
5.1 LiveData支持
@Dao
interface UserDao {
// 返回LiveData,自动监听数据变化
@Query("SELECT * FROM users")
fun getAllUsersLive(): LiveData<List<User>>
@Query("SELECT * FROM users WHERE id = :userId")
fun getUserLive(userId: Long): LiveData<User>
}
// 使用
val usersLiveData = database.userDao().getAllUsersLive()
usersLiveData.observe(lifecycleOwner) { users ->
// 数据更新时自动触发
adapter.submitList(users)
}
5.2 LiveData实现原理
// room-runtime/src/main/java/androidx/room/RoomTrackingLiveData.java
class RoomTrackingLiveData<T> extends LiveData<T> {
final RoomDatabase mDatabase;
final boolean mInTransaction;
final Callable<T> mComputeFunction;
private final InvalidationTracker.Observer mObserver;
private AtomicBoolean mInvalid = new AtomicBoolean(true);
private AtomicBoolean mComputing = new AtomicBoolean(false);
private AtomicBoolean mRegisteredObserver = new AtomicBoolean(false);
final Runnable mRefreshRunnable = new Runnable() {
@Override
public void run() {
if (mRegisteredObserver.compareAndSet(false, true)) {
mDatabase.getInvalidationTracker().addObserver(mObserver);
}
boolean computed;
do {
computed = false;
if (mComputing.compareAndSet(false, true)) {
try {
T value = null;
while (mInvalid.compareAndSet(true, false)) {
computed = true;
try {
// 执行查询
value = mComputeFunction.call();
} catch (Exception e) {
throw new RuntimeException("Exception while computing database"
+ " live data.", e);
}
}
if (computed) {
postValue(value);
}
} finally {
mComputing.set(false);
}
}
} while (computed && mInvalid.get());
}
};
RoomTrackingLiveData(
RoomDatabase database,
String[] tableNames,
boolean inTransaction,
Callable<T> computeFunction
) {
mDatabase = database;
mInTransaction = inTransaction;
mComputeFunction = computeFunction;
mObserver = new InvalidationTracker.Observer(tableNames) {
@Override
public void onInvalidated(@NonNull Set<String> tables) {
ArchTaskExecutor.getInstance().executeOnMainThread(mInvalidRefreshRunnable);
}
};
}
@Override
protected void onActive() {
super.onActive();
mDatabase.getQueryExecutor().execute(mRefreshRunnable);
}
@Override
protected void onInactive() {
super.onInactive();
ArchTaskExecutor.getInstance().executeOnDiskIO(mInvalidationRunnable);
}
}
5.3 Flow支持(Kotlin协程)
@Dao
interface UserDao {
// 返回Flow
@Query("SELECT * FROM users")
fun getAllUsersFlow(): Flow<List<User>>
@Query("SELECT * FROM users WHERE id = :userId")
fun getUserFlow(userId: Long): Flow<User?>
}
// 使用
lifecycleScope.launch {
database.userDao().getAllUsersFlow()
.collect { users ->
// 数据更新时自动触发
adapter.submitList(users)
}
}
六、性能优化
6.1 批量操作
@Dao
interface UserDao {
// 批量插入
@Insert
suspend fun insertUsers(users: List<User>)
// 批量更新
@Update
suspend fun updateUsers(users: List<User>)
// 批量删除
@Delete
suspend fun deleteUsers(users: List<User>)
// 事务保证原子性
@Transaction
suspend fun updateUsersAndOrders(users: List<User>, orders: List<Order>) {
insertUsers(users)
insertOrders(orders)
}
}
6.2 索引优化
@Entity(
tableName = "users",
indices = [
// 单列索引
@Index(value = ["email"], unique = true),
// 复合索引
@Index(value = ["name", "age"]),
// 全文搜索索引(需要FTS)
@Index(value = ["content"], orders = [Index.Order.ASC])
]
)
data class User(
@PrimaryKey(autoGenerate = true)
val id: Long = 0,
val name: String,
val age: Int,
val email: String
)
6.3 查询优化
// [通过] 投影查询(只查询需要的列)
@Query("SELECT id, name FROM users")
fun getUserNames(): List<UserName>
data class UserName(val id: Long, val name: String)
// [通过] 分页查询
@Query("SELECT * FROM users LIMIT :limit OFFSET :offset")
fun getUsersPaged(limit: Int, offset: Int): List<User>
// [通过] 使用Paging 3
@Query("SELECT * FROM users ORDER BY name ASC")
fun getUsersPagingSource(): PagingSource<Int, User>
七、测试支持
7.1 内存数据库测试
@RunWith(AndroidJUnit4::class)
class UserDaoTest {
private lateinit var database: AppDatabase
private lateinit var userDao: UserDao
@Before
fun createDb() {
val context = ApplicationProvider.getApplicationContext<Context>()
database = Room.inMemoryDatabaseBuilder(
context,
AppDatabase::class.java
).build()
userDao = database.userDao()
}
@After
fun closeDb() {
database.close()
}
@Test
fun insertAndGetUser() = runBlocking {
val user = User(name = "Alice", age = 25, email = "alice@example.com")
val id = userDao.insertUser(user)
val loadedUser = userDao.getUserById(id)
assertThat(loadedUser.name, equalTo("Alice"))
}
@Test
fun getAllUsers() = runBlocking {
val users = listOf(
User(name = "Alice", age = 25, email = "alice@example.com"),
User(name = "Bob", age = 30, email = "bob@example.com")
)
userDao.insertUsers(users)
val allUsers = userDao.getAllUsers()
assertThat(allUsers.size, equalTo(2))
}
}
7.2 Migration测试
@RunWith(AndroidJUnit4::class)
class MigrationTest {
private val TEST_DB = "migration-test"
@get:Rule
val helper: MigrationTestHelper = MigrationTestHelper(
InstrumentationRegistry.getInstrumentation(),
AppDatabase::class.java.canonicalName,
FrameworkSQLiteOpenHelperFactory()
)
@Test
fun migrate1To2() {
// 创建版本1数据库并插入数据
helper.createDatabase(TEST_DB, 1).apply {
execSQL("INSERT INTO users (id, name, age) VALUES (1, 'Alice', 25)")
close()
}
// 执行迁移到版本2
helper.runMigrationsAndValidate(TEST_DB, 2, true, MIGRATION_1_2)
// 验证迁移后的数据
val db = getMigratedRoomDatabase()
val user = db.userDao().getUserById(1)
assertThat(user.name, equalTo("Alice"))
}
private fun getMigratedRoomDatabase(): AppDatabase {
val database = Room.databaseBuilder(
InstrumentationRegistry.getInstrumentation().targetContext,
AppDatabase::class.java,
TEST_DB
).addMigrations(MIGRATION_1_2, MIGRATION_2_3).build()
helper.closeWhenFinished(database)
return database
}
}
八、总结与最佳实践
8.1 核心优势
- 编译时验证:SQL语法错误在编译期发现
- 类型安全:避免运行时类型转换错误
- LiveData集成:自动监听数据变化
- 迁移管理:清晰的版本升级路径
- 测试友好:支持内存数据库测试
8.2 最佳实践
// [通过] 推荐:使用单例模式
class DatabaseProvider {
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,
"app_database"
)
.addMigrations(MIGRATION_1_2, MIGRATION_2_3)
.build()
INSTANCE = instance
instance
}
}
}
}
// [通过] 推荐:使用suspend函数
@Dao
interface UserDao {
@Insert
suspend fun insertUser(user: User): Long
@Query("SELECT * FROM users")
suspend fun getAllUsers(): List<User>
}
// [通过] 推荐:使用事务保证数据一致性
@Transaction
suspend fun transferMoney(fromId: Long, toId: Long, amount: Double) {
updateBalance(fromId, -amount)
updateBalance(toId, amount)
}
// [未通过] 避免:在主线程执行数据库操作
// 使用协程或RxJava处理异步操作
// [未通过] 避免:频繁打开关闭数据库
// 使用单例模式复用数据库实例
8.3 性能数据
- 查询性能:接近原生SQLite(额外开销<5%)
- 编译时间:增加10-30秒(中型项目)
- APK增量:约100-300KB(生成的代码)
- 内存占用:约1-3MB(数据库连接池)
Room通过编译时代码生成和类型安全设计,为Android开发者提供了优雅的数据库访问方案,是现代Android应用的标准选择。
参考资源:


1729

被折叠的 条评论
为什么被折叠?



