引言
Room 是 Google 推出的 Android 架构组件之一,它在 SQLite 之上提供了一个抽象层,使得数据库操作更加简单、安全和高效。Room 通过编译时验证、强大的查询功能和与其他架构组件的无缝集成,成为了 Android 开发中首选的本地数据库解决方案。本文将深入探讨 Room 的核心概念、高级特性以及最佳实践。
Room 架构概述
核心组件
Room 由三个主要组件构成:
- Entity(实体):代表数据库中的表
- DAO(数据访问对象):定义数据库操作方法
- Database(数据库):数据库持有者,作为应用程序持久化数据的主要访问点
// 1. Entity - 定义数据表结构
@Entity(tableName = "users")
data class User(
@PrimaryKey
val id: String,
@ColumnInfo(name = "user_name")
val name: String,
val email: String,
@ColumnInfo(name = "created_at")
val createdAt: Long = System.currentTimeMillis(),
@ColumnInfo(name = "is_active")
val isActive: Boolean = true
)
// 2. DAO - 定义数据库操作
@Dao
interface UserDao {
@Query("SELECT * FROM users WHERE is_active = 1")
suspend fun getActiveUsers(): List<User>
@Query("SELECT * FROM users WHERE id = :userId")
suspend fun getUserById(userId: String): User?
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertUser(user: User)
@Update
suspend fun updateUser(user: User)
@Delete
suspend fun deleteUser(user: User)
}
// 3. Database - 数据库配置
@Database(
entities = [User::class],
version = 1,
exportSchema = false
)
@TypeConverters(Converters::class)
abstract class AppDatabase : RoomDatabase() {
abstract fun userDao(): UserDao
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"
).build()
INSTANCE = instance
instance
}
}
}
}
高级 Entity 定义
复杂实体结构
// 复杂的实体定义
@Entity(
tableName = "articles",
indices = [
Index(value = ["title"], unique = true),
Index(value = ["category_id", "published_date"])
],
foreignKeys = [
ForeignKey(
entity = Category::class,
parentColumns = ["id"],
childColumns = ["category_id"],
onDelete = ForeignKey.CASCADE
)
]
)
data class Article(
@PrimaryKey
val id: String,
@ColumnInfo(name = "title")
val title: String,
@ColumnInfo(name = "content")
val content: String,
@ColumnInfo(name = "category_id")
val categoryId: String,
@ColumnInfo(name = "author_id")
val authorId: String,
@ColumnInfo(name = "published_date")
val publishedDate: Date,
@ColumnInfo(name = "view_count")
val viewCount: Int = 0,
@ColumnInfo(name = "is_featured")
val isFeatured: Boolean = false,
@ColumnInfo(name = "tags")
val tags: List<String> = emptyList(),
@Embedded
val metadata: ArticleMetadata
)
// 嵌入式对象
data class ArticleMetadata(
@ColumnInfo(name = "created_at")
val createdAt: Long,
@ColumnInfo(name = "updated_at")
val updatedAt: Long,
@ColumnInfo(name = "created_by")
val createdBy: String
)
// 分类实体
@Entity(tableName = "categories")
data class Category(
@PrimaryKey
val id: String,
val name: String,
val description: String?,
@ColumnInfo(name = "parent_id")
val parentId: String?
)
类型转换器
// 类型转换器
class Converters {
@TypeConverter
fun fromTimestamp(value: Long?): Date? {
return value?.let { Date(it) }
}
@TypeConverter
fun dateToTimestamp(date: Date?): Long? {
return date?.time
}
@TypeConverter
fun fromStringList(value: List<String>): String {
return Gson().toJson(value)
}
@TypeConverter
fun toStringList(value: String): List<String> {
return Gson().fromJson(value, object : TypeToken<List<String>>() {}.type)
}
@TypeConverter
fun fromArticleStatus(status: ArticleStatus): String {
return status.name
}
@TypeConverter
fun toArticleStatus(status: String): ArticleStatus {
return ArticleStatus.valueOf(status)
}
}
enum class ArticleStatus {
DRAFT, PUBLISHED, ARCHIVED
}
高级查询操作
复杂查询和关联
@Dao
interface ArticleDao {
// 基本查询
@Query("SELECT * FROM articles WHERE category_id = :categoryId ORDER BY published_date DESC")
suspend fun getArticlesByCategory(categoryId: String): List<Article>
// 分页查询
@Query("SELECT * FROM articles ORDER BY published_date DESC LIMIT :limit OFFSET :offset")
suspend fun getArticlesPaged(limit: Int, offset: Int): List<Article>
// 条件查询
@Query("""
SELECT * FROM articles
WHERE (:categoryId IS NULL OR category_id = :categoryId)
AND (:searchQuery IS NULL OR title LIKE '%' || :searchQuery || '%' OR content LIKE '%' || :searchQuery || '%')
AND published_date BETWEEN :startDate AND :endDate
ORDER BY
CASE WHEN :sortBy = 'date' THEN published_date END DESC,
CASE WHEN :sortBy = 'views' THEN view_count END DESC,
CASE WHEN :sortBy = 'title' THEN title END ASC
""")
suspend fun searchArticles(
categoryId: String?,
searchQuery: String?,
startDate: Long,
endDate: Long,
sortBy: String
): List<Article>
// 聚合查询
@Query("SELECT COUNT(*) FROM articles WHERE category_id = :categoryId")
suspend fun getArticleCountByCategory(categoryId: String): Int
@Query("SELECT AVG(view_count) FROM articles WHERE published_date > :date")
suspend fun getAverageViewCount(date: Long): Double
// 关联查询
@Query("""
SELECT a.*, c.name as category_name
FROM articles a
INNER JOIN categories c ON a.category_id = c.id
WHERE a.is_featured = 1
""")
suspend fun getFeaturedArticlesWithCategory(): List<ArticleWithCategory>
// 子查询
@Query("""
SELECT * FROM articles
WHERE category_id IN (
SELECT id FROM categories WHERE parent_id = :parentCategoryId
)
""")
suspend fun getArticlesByParentCategory(parentCategoryId: String): List<Article>
// 批量操作
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertArticles(articles: List<Article>)
@Query("DELETE FROM articles WHERE id IN (:articleIds)")
suspend fun deleteArticlesByIds(articleIds: List<String>)
// 更新特定字段
@Query("UPDATE articles SET view_count = view_count + 1 WHERE id = :articleId")
suspend fun incrementViewCount(articleId: String)
@Query("UPDATE articles SET is_featured = :featured WHERE id = :articleId")
suspend fun updateFeaturedStatus(articleId: String, featured: Boolean)
}
// 查询结果数据类
data class ArticleWithCategory(
@Embedded val article: Article,
@ColumnInfo(name = "category_name") val categoryName: String
)
Flow 和 LiveData 支持
@Dao
interface ReactiveArticleDao {
// 使用 Flow 进行响应式查询
@Query("SELECT * FROM articles ORDER BY published_date DESC")
fun observeAllArticles(): Flow<List<Article>>
@Query("SELECT * FROM articles WHERE category_id = :categoryId")
fun observeArticlesByCategory(categoryId: String): Flow<List<Article>>
// 使用 LiveData
@Query("SELECT * FROM articles WHERE is_featured = 1")
fun getFeaturedArticlesLiveData(): LiveData<List<Article>>
// 复杂的响应式查询
@Query("""
SELECT a.*, c.name as category_name,
(SELECT COUNT(*) FROM articles WHERE category_id = a.category_id) as category_count
FROM articles a
LEFT JOIN categories c ON a.category_id = c.id
ORDER BY a.published_date DESC
""")
fun observeArticlesWithDetails(): Flow<List<ArticleWithDetails>>
}
data class ArticleWithDetails(
@Embedded val article: Article,
@ColumnInfo(name = "category_name") val categoryName: String?,
@ColumnInfo(name = "category_count") val categoryCount: Int
)
数据库迁移
版本迁移策略
// 数据库迁移
class DatabaseMigrations {
companion object {
// 从版本 1 到版本 2 的迁移
val MIGRATION_1_2 = object : Migration(1, 2) {
override fun migrate(database: SupportSQLiteDatabase) {
// 添加新列
database.execSQL("ALTER TABLE articles ADD COLUMN summary TEXT")
// 创建新表
database.execSQL("""
CREATE TABLE IF NOT EXISTS tags (
id TEXT PRIMARY KEY NOT NULL,
name TEXT NOT NULL,
color TEXT NOT NULL
)
""")
// 创建关联表
database.execSQL("""
CREATE TABLE IF NOT EXISTS article_tag_cross_ref (
article_id TEXT NOT NULL,
tag_id TEXT NOT NULL,
PRIMARY KEY(article_id, tag_id),
FOREIGN KEY(article_id) REFERENCES articles(id) ON DELETE CASCADE,
FOREIGN KEY(tag_id) REFERENCES tags(id) ON DELETE CASCADE
)
""")
}
}
// 从版本 2 到版本 3 的迁移
val MIGRATION_2_3 = object : Migration(2, 3) {
override fun migrate(database: SupportSQLiteDatabase) {
// 重命名表
database.execSQL("ALTER TABLE articles RENAME TO articles_old")
// 创建新表结构
database.execSQL("""
CREATE TABLE articles (
id TEXT PRIMARY KEY NOT NULL,
title TEXT NOT NULL,
content TEXT NOT NULL,
summary TEXT,
category_id TEXT NOT NULL,
author_id TEXT NOT NULL,
published_date INTEGER NOT NULL,
view_count INTEGER NOT NULL DEFAULT 0,
is_featured INTEGER NOT NULL DEFAULT 0,
status TEXT NOT NULL DEFAULT 'DRAFT',
created_at INTEGER NOT NULL,
updated_at INTEGER NOT NULL,
created_by TEXT NOT NULL
)
""")
// 迁移数据
database.execSQL("""
INSERT INTO articles (id, title, content, summary, category_id, author_id,
published_date, view_count, is_featured, status,
created_at, updated_at, created_by)
SELECT id, title, content, summary, category_id, author_id,
published_date, view_count, is_featured, 'PUBLISHED',
created_at, updated_at, created_by
FROM articles_old
""")
// 删除旧表
database.execSQL("DROP TABLE articles_old")
// 创建索引
database.execSQL("CREATE INDEX index_articles_status ON articles(status)")
}
}
// 复杂的数据迁移
val MIGRATION_3_4 = object : Migration(3, 4) {
override fun migrate(database: SupportSQLiteDatabase) {
// 添加用户表
database.execSQL("""
CREATE TABLE users (
id TEXT PRIMARY KEY NOT NULL,
username TEXT NOT NULL UNIQUE,
email TEXT NOT NULL UNIQUE,
password_hash TEXT NOT NULL,
full_name TEXT,
avatar_url TEXT,
created_at INTEGER NOT NULL,
updated_at INTEGER NOT NULL,
is_active INTEGER NOT NULL DEFAULT 1
)
""")
// 迁移现有的作者数据到用户表
database.execSQL("""
INSERT INTO users (id, username, email, password_hash, full_name, created_at, updated_at)
SELECT DISTINCT author_id, author_id, author_id || '@example.com', 'temp_hash',
'Unknown Author', ${System.currentTimeMillis()}, ${System.currentTimeMillis()}
FROM articles
""")
// 添加外键约束
database.execSQL("CREATE INDEX index_articles_author_id ON articles(author_id)")
}
}
}
}
// 更新数据库配置
@Database(
entities = [Article::class, Category::class, Tag::class, ArticleTagCrossRef::class, User::class],
version = 4,
exportSchema = true
)
@TypeConverters(Converters::class)
abstract class AppDatabase : RoomDatabase() {
abstract fun articleDao(): ArticleDao
abstract fun categoryDao(): CategoryDao
abstract fun tagDao(): TagDao
abstract fun userDao(): UserDao
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(
DatabaseMigrations.MIGRATION_1_2,
DatabaseMigrations.MIGRATION_2_3,
DatabaseMigrations.MIGRATION_3_4
)
.build()
INSTANCE = instance
instance
}
}
}
}
关系映射
一对多和多对多关系
// 标签实体
@Entity(tableName = "tags")
data class Tag(
@PrimaryKey val id: String,
val name: String,
val color: String
)
// 文章-标签关联表
@Entity(
tableName = "article_tag_cross_ref",
primaryKeys = ["article_id", "tag_id"],
foreignKeys = [
ForeignKey(
entity = Article::class,
parentColumns = ["id"],
childColumns = ["article_id"],
onDelete = ForeignKey.CASCADE
),
ForeignKey(
entity = Tag::class,
parentColumns = ["id"],
childColumns = ["tag_id"],
onDelete = ForeignKey.CASCADE
)
]
)
data class ArticleTagCrossRef(
@ColumnInfo(name = "article_id") val articleId: String,
@ColumnInfo(name = "tag_id") val tagId: String
)
// 关系数据类
data class ArticleWithTags(
@Embedded val article: Article,
@Relation(
parentColumn = "id",
entityColumn = "id",
associateBy = Junction(
ArticleTagCrossRef::class,
parentColumn = "article_id",
entityColumn = "tag_id"
)
)
val tags: List<Tag>
)
data class TagWithArticles(
@Embedded val tag: Tag,
@Relation(
parentColumn = "id",
entityColumn = "id",
associateBy = Junction(
ArticleTagCrossRef::class,
parentColumn = "tag_id",
entityColumn = "article_id"
)
)
val articles: List<Article>
)
data class CategoryWithArticles(
@Embedded val category: Category,
@Relation(
parentColumn = "id",
entityColumn = "category_id"
)
val articles: List<Article>
)
// 复杂的嵌套关系
data class CategoryWithArticlesAndTags(
@Embedded val category: Category,
@Relation(
entity = Article::class,
parentColumn = "id",
entityColumn = "category_id"
)
val articlesWithTags: List<ArticleWithTags>
)
关系查询 DAO
@Dao
interface RelationDao {
// 获取文章及其标签
@Transaction
@Query("SELECT * FROM articles WHERE id = :articleId")
suspend fun getArticleWithTags(articleId: String): ArticleWithTags?
@Transaction
@Query("SELECT * FROM articles")
suspend fun getAllArticlesWithTags(): List<ArticleWithTags>
// 获取标签及其文章
@Transaction
@Query("SELECT * FROM tags WHERE id = :tagId")
suspend fun getTagWithArticles(tagId: String): TagWithArticles?
// 获取分类及其文章
@Transaction
@Query("SELECT * FROM categories")
suspend fun getCategoriesWithArticles(): List<CategoryWithArticles>
// 复杂的嵌套查询
@Transaction
@Query("SELECT * FROM categories WHERE id = :categoryId")
suspend fun getCategoryWithArticlesAndTags(categoryId: String): CategoryWithArticlesAndTags?
// 管理关联关系
@Insert(onConflict = OnConflictStrategy.IGNORE)
suspend fun insertArticleTagCrossRef(crossRef: ArticleTagCrossRef)
@Delete
suspend fun deleteArticleTagCrossRef(crossRef: ArticleTagCrossRef)
@Query("DELETE FROM article_tag_cross_ref WHERE article_id = :articleId")
suspend fun deleteAllTagsForArticle(articleId: String)
@Query("DELETE FROM article_tag_cross_ref WHERE tag_id = :tagId")
suspend fun deleteAllArticlesForTag(tagId: String)
}
性能优化
查询优化
// 性能优化策略
class DatabaseOptimization {
// 1. 索引优化
@Entity(
tableName = "optimized_articles",
indices = [
Index(value = ["category_id"]), // 单列索引
Index(value = ["published_date", "view_count"]), // 复合索引
Index(value = ["title"], unique = true), // 唯一索引
Index(value = ["author_id", "status"]) // 查询优化索引
]
)
data class OptimizedArticle(
@PrimaryKey val id: String,
val title: String,
val content: String,
@ColumnInfo(name = "category_id") val categoryId: String,
@ColumnInfo(name = "author_id") val authorId: String,
@ColumnInfo(name = "published_date") val publishedDate: Long,
@ColumnInfo(name = "view_count") val viewCount: Int,
val status: String
)
@Dao
interface OptimizedDao {
// 2. 分页查询优化
@Query("""
SELECT * FROM optimized_articles
WHERE category_id = :categoryId
ORDER BY published_date DESC
LIMIT :pageSize OFFSET :offset
""")
suspend fun getArticlesPaged(
categoryId: String,
pageSize: Int,
offset: Int
): List<OptimizedArticle>
// 3. 只查询需要的列
@Query("SELECT id, title, published_date FROM optimized_articles ORDER BY published_date DESC")
suspend fun getArticleSummaries(): List<ArticleSummary>
// 4. 使用 EXISTS 而不是 IN
@Query("""
SELECT * FROM optimized_articles a
WHERE EXISTS (
SELECT 1 FROM article_tag_cross_ref atcr
WHERE atcr.article_id = a.id AND atcr.tag_id = :tagId
)
""")
suspend fun getArticlesByTagOptimized(tagId: String): List<OptimizedArticle>
// 5. 批量操作
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertArticlesBatch(articles: List<OptimizedArticle>)
@Query("UPDATE optimized_articles SET view_count = view_count + 1 WHERE id IN (:articleIds)")
suspend fun incrementViewCountBatch(articleIds: List<String>)
}
data class ArticleSummary(
val id: String,
val title: String,
@ColumnInfo(name = "published_date") val publishedDate: Long
)
}
数据库配置优化
// 数据库配置优化
class DatabaseConfiguration {
companion object {
fun createOptimizedDatabase(context: Context): AppDatabase {
return Room.databaseBuilder(
context.applicationContext,
AppDatabase::class.java,
"app_database"
)
.addMigrations(
DatabaseMigrations.MIGRATION_1_2,
DatabaseMigrations.MIGRATION_2_3,
DatabaseMigrations.MIGRATION_3_4
)
.setJournalMode(RoomDatabase.JournalMode.WAL) // 启用 WAL 模式
.setQueryCallback({ sqlQuery, bindArgs ->
if (BuildConfig.DEBUG) {
Log.d("RoomQuery", "SQL: $sqlQuery, Args: $bindArgs")
}
}, Executors.newSingleThreadExecutor())
.addCallback(object : RoomDatabase.Callback() {
override fun onCreate(db: SupportSQLiteDatabase) {
super.onCreate(db)
// 数据库创建时的初始化操作
initializeDatabase(db)
}
override fun onOpen(db: SupportSQLiteDatabase) {
super.onOpen(db)
// 数据库打开时的优化设置
db.execSQL("PRAGMA foreign_keys=ON")
db.execSQL("PRAGMA journal_mode=WAL")
db.execSQL("PRAGMA synchronous=NORMAL")
db.execSQL("PRAGMA cache_size=10000")
db.execSQL("PRAGMA temp_store=MEMORY")
}
})
.build()
}
private fun initializeDatabase(db: SupportSQLiteDatabase) {
// 插入初始数据
db.execSQL("""
INSERT INTO categories (id, name, description, parent_id) VALUES
('tech', 'Technology', 'Technology related articles', null),
('science', 'Science', 'Science related articles', null),
('business', 'Business', 'Business related articles', null)
""")
db.execSQL("""
INSERT INTO tags (id, name, color) VALUES
('android', 'Android', '#3DDC84'),
('kotlin', 'Kotlin', '#7F52FF'),
('java', 'Java', '#ED8B00')
""")
}
}
}
测试策略
数据库测试
// Room 数据库测试
@RunWith(AndroidJUnit4::class)
class DatabaseTest {
private lateinit var database: AppDatabase
private lateinit var articleDao: ArticleDao
private lateinit var categoryDao: CategoryDao
@Before
fun createDb() {
val context = ApplicationProvider.getApplicationContext<Context>()
database = Room.inMemoryDatabaseBuilder(
context,
AppDatabase::class.java
).build()
articleDao = database.articleDao()
categoryDao = database.categoryDao()
}
@After
fun closeDb() {
database.close()
}
@Test
fun insertAndRetrieveArticle() = runTest {
// Given
val category = Category("tech", "Technology", "Tech articles", null)
val article = Article(
id = "1",
title = "Test Article",
content = "Test content",
categoryId = "tech",
authorId = "author1",
publishedDate = Date(),
metadata = ArticleMetadata(
createdAt = System.currentTimeMillis(),
updatedAt = System.currentTimeMillis(),
createdBy = "author1"
)
)
// When
categoryDao.insertCategory(category)
articleDao.insertArticle(article)
// Then
val retrievedArticle = articleDao.getUserById("1")
assertThat(retrievedArticle).isNotNull()
assertThat(retrievedArticle?.title).isEqualTo("Test Article")
}
@Test
fun testArticleWithTags() = runTest {
// Given
val article = createTestArticle()
val tag1 = Tag("android", "Android", "#3DDC84")
val tag2 = Tag("kotlin", "Kotlin", "#7F52FF")
// When
articleDao.insertArticle(article)
database.tagDao().insertTags(listOf(tag1, tag2))
database.relationDao().insertArticleTagCrossRef(
ArticleTagCrossRef(article.id, tag1.id)
)
database.relationDao().insertArticleTagCrossRef(
ArticleTagCrossRef(article.id, tag2.id)
)
// Then
val articleWithTags = database.relationDao().getArticleWithTags(article.id)
assertThat(articleWithTags).isNotNull()
assertThat(articleWithTags?.tags).hasSize(2)
assertThat(articleWithTags?.tags?.map { it.name }).containsExactly("Android", "Kotlin")
}
@Test
fun testDatabaseMigration() {
// 测试数据库迁移
val migrationTestHelper = MigrationTestHelper(
InstrumentationRegistry.getInstrumentation(),
AppDatabase::class.java
)
// 创建版本 1 的数据库
var db = migrationTestHelper.createDatabase("test_db", 1)
// 插入测试数据
db.execSQL("""
INSERT INTO articles (id, title, content, category_id, author_id, published_date, view_count, is_featured, created_at, updated_at, created_by)
VALUES ('1', 'Test', 'Content', 'tech', 'author1', ${System.currentTimeMillis()}, 0, 0, ${System.currentTimeMillis()}, ${System.currentTimeMillis()}, 'author1')
""")
db.close()
// 迁移到版本 2
db = migrationTestHelper.runMigrationsAndValidate(
"test_db",
2,
true,
DatabaseMigrations.MIGRATION_1_2
)
// 验证迁移后的数据
val cursor = db.query("SELECT * FROM articles WHERE id = '1'")
assertThat(cursor.moveToFirst()).isTrue()
assertThat(cursor.getString(cursor.getColumnIndex("title"))).isEqualTo("Test")
cursor.close()
}
private fun createTestArticle(): Article {
return Article(
id = "test1",
title = "Test Article",
content = "Test content",
categoryId = "tech",
authorId = "author1",
publishedDate = Date(),
metadata = ArticleMetadata(
createdAt = System.currentTimeMillis(),
updatedAt = System.currentTimeMillis(),
createdBy = "author1"
)
)
}
}
最佳实践
Repository 模式集成
// Repository 模式与 Room 集成
class ArticleRepository(
private val articleDao: ArticleDao,
private val relationDao: RelationDao,
private val apiService: ApiService
) {
// 响应式数据流
fun observeArticles(): Flow<List<Article>> {
return articleDao.observeAllArticles()
}
// 缓存策略
suspend fun getArticles(forceRefresh: Boolean = false): Result<List<Article>> {
return try {
if (forceRefresh || shouldRefreshData()) {
refreshArticlesFromNetwork()
}
val articles = articleDao.getAllArticles()
Result.success(articles)
} catch (e: Exception) {
Result.failure(e)
}
}
private suspend fun refreshArticlesFromNetwork() {
try {
val networkArticles = apiService.getArticles()
articleDao.insertArticles(networkArticles)
updateLastRefreshTime()
} catch (e: Exception) {
// 网络错误时使用本地数据
Log.w("ArticleRepository", "Failed to refresh from network", e)
}
}
private fun shouldRefreshData(): Boolean {
val lastRefresh = getLastRefreshTime()
val now = System.currentTimeMillis()
return (now - lastRefresh) > TimeUnit.HOURS.toMillis(1) // 1小时刷新一次
}
// 搜索功能
suspend fun searchArticles(
query: String,
categoryId: String? = null,
sortBy: String = "date"
): List<Article> {
return articleDao.searchArticles(
categoryId = categoryId,
searchQuery = query.takeIf { it.isNotBlank() },
startDate = 0,
endDate = System.currentTimeMillis(),
sortBy = sortBy
)
}
// 事务操作
@Transaction
suspend fun createArticleWithTags(
article: Article,
tagIds: List<String>
): Result<Unit> {
return try {
articleDao.insertArticle(article)
tagIds.forEach { tagId ->
relationDao.insertArticleTagCrossRef(
ArticleTagCrossRef(article.id, tagId)
)
}
Result.success(Unit)
} catch (e: Exception) {
Result.failure(e)
}
}
// 批量操作
suspend fun syncArticles(articles: List<Article>): Result<Unit> {
return try {
articleDao.insertArticles(articles)
Result.success(Unit)
} catch (e: Exception) {
Result.failure(e)
}
}
}
总结
Room 数据库为 Android 开发提供了强大而灵活的本地数据存储解决方案。通过本文的深入探讨,我们了解了:
- 核心架构:Entity、DAO、Database 三大组件的设计和使用
- 高级特性:复杂查询、关系映射、类型转换器等
- 性能优化:索引策略、查询优化、配置调优
- 数据迁移:版本升级和数据迁移的最佳实践
- 测试策略:全面的数据库测试方法
- 架构集成:与 Repository 模式和 MVVM 架构的结合
Room 的强大之处在于它提供了类型安全的数据库操作、编译时查询验证、与协程和 Flow 的无缝集成,以及对复杂关系映射的支持。通过遵循最佳实践和合理的架构设计,开发者可以构建出高性能、可维护的数据持久化层,为应用程序提供可靠的数据支撑。
上一篇 下一篇