Logo
Section 1 / 3

数据访问对象(DAO)

学习如何在应用程序中处理缓存。

导入

数据访问对象(DAO)

学习如何在应用程序中处理缓存。

使用接口

DAO 是一个包含查询本地数据库数据方法的接口。请务必注意所使用的注解,因为它们是使方法正常运行所必需的。

这些方法通过 @Insert、@Query 和 @Delete 注解在仓库中被调用和实现。这简化了我们在这些注解下编写方法的方式,能够在后台自动生成所需的代码。这种方式有助于减少编码时间,让我们能够专注于应用程序最重要的部分。

创建 ArticleDao

我们将使用 @Insert 注解将文章插入数据库中的 articles 表,onConflict 参数设置为 OnConflictStrategy.REPLACE,表示如果发生冲突,则用新数据替换表中已存在的行。getAllArticles() 方法用于从本地数据库检索所有文章,通过使用 @Query 注解并指定一条 SELECT 查询语句,从 articles 表的所有行和列中获取所有文章。

该方法返回一个 Flow 类型的 List<Article>,意味着它是可观察的,我们可以监听其变化并更新 UI。

1@Dao 2interface ArticleDao { 3 4 @Insert(onConflict = OnConflictStrategy.REPLACE) 5 suspend fun insertArticle(article: Article): Long 6 7 @Query("SELECT * FROM articles") 8 fun getAllArticles(): Flow<List<Article>> 9}

创建 FavouritesDao

与上面创建 ArticleDao 的方式相同,我们如下创建 FavouritesDao:

1@Dao 2interface FavouritesDao { 3 @Insert(onConflict = OnConflictStrategy.REPLACE) 4 suspend fun insertFavouriteArticles(favouriteArticles: FavouriteArticles) 5 6 @Query("SELECT * FROM favourite_articles ORDER BY publishedAt DESC") 7 fun getFavouriteArticles(): Flow<List<FavouriteArticles>> 8}

创建数据库

我们将创建一个本地数据存储,用于缓存数据的存储和读取,作为访问本地数据库的主要入口点。

以下是构建数据库的完整代码,但首先,我们需要将其中一个模型类转换为数据库实体。

模型类转实体

我们将 Article 类转换为实体,使其成为一个表,属性则作为表的字段。

1@Entity(tableName = "articles") 2data class Article( 3 @PrimaryKey(autoGenerate = true) 4 var id: Int? = null, 5 6 val author: String, 7 val content: String, 8 val description: String, 9 val publishedAt: String, 10 val source: Source, 11 val title: String, 12 val url: String, 13 val urlToImage: String 14) : Serializable
1@Entity(tableName = "favourite_articles") 2data class FavouriteArticles( 3 @PrimaryKey(autoGenerate = true) 4 var id: Int? = null, 5 val author: String?, 6 val content: String?, 7 val description: String?, 8 val publishedAt: String?, 9 val source: Source?, 10 val title: String?, 11 val isBookmarked: Boolean?, 12 val url: String, 13 val urlToImage: String? 14) : Serializable

我们通过使用 @Entity 注解来完成这一转换,指定表名,并将 ID 转换为主键。使用 Room 数据库的最大优点是大量样板代码被抽象掉了,我们只需通过注解即可在后台自动生成代码。

1@Database(entities = [Article::class, FavouriteArticles::class], version = 1) 2@TypeConverters(Converters::class) 3abstract class ArticleDatabase : RoomDatabase() { 4 5 abstract fun getArticleDao(): ArticleDao 6 abstract fun getFavouriteDao(): FavouritesDao 7 8 companion object { 9 @Volatile 10 private var instance: ArticleDatabase? = null 11 private val LOCK = Any() 12 13 operator fun invoke(context: Context) = instance ?: synchronized(LOCK) { 14 instance ?: createDatabase(context).also { instance = it } 15 } 16 17 private fun createDatabase(context: Context) = 18 Room.databaseBuilder( 19 context.applicationContext, 20 ArticleDatabase::class.java, 21 "article_db.db" 22 ) 23 .fallbackToDestructiveMigration() 24 .build() 25 } 26}

所有 Room 数据库都必须使用 @Database 注解,并提供一个将被转换为数据库实体的数据类。之后,我们引用 Converter 类,用于将自定义类转换为 Room 可以理解并持久化的已知类型。

接着,我们创建一个 companion 对象。在其中,我们创建数据库实例,声明为可空以保证空安全。然后,我们使用 invoke 操作符创建数据库实例。最后一步是创建数据库。

我们将数据库创建在 companion 对象中,以便在类初始化时立即实例化,使其成为类级别的变量,可以直接调用而无需创建类实例。

在其他语言(如 Java)中,companion 对象被称为静态对象。

1class Converters { 2 @TypeConverter 3 fun fromSource(source: Source): String { 4 return source.name 5 } 6 7 @TypeConverter 8 fun toSource(name: String): Source { 9 return Source(name, name) 10 } 11}

上述类型转换器用于将数据转换为可存储在数据库中的状态,并在需要时将其还原为原始状态。

  • fromSource() 方法接收一个 Source 类型,返回一个 String 类型。
  • toSource() 方法将一个 String 类型转换回 Source 类型。

工具类

新闻仓库