【问题标题】:Kotlin Room Database Save ControlKotlin Room 数据库保存控制
【发布时间】:2021-05-19 14:30:38
【问题描述】:

我使用按钮将数据保存到 Room 数据库。但是,每次我按下按钮时,该项目都会一次又一次地注册和列出。我想写一个控件。如果它已注册,我不希望它保存它。如果您能提供帮助,我会很高兴。

文章道

@Dao
interface ArticleDao {

    @Insert(onConflict = OnConflictStrategy.REPLACE)
    suspend fun upsert(article: Article): Long

NewsRepisotry

class NewsRepository(
    val db: ArticleDatabase
) {

    suspend fun  upsert(article: Article)=db.getArticleDao().upsert(article)

    fun getSavedNews()=db.getArticleDao().getAllArticles()

文章数据库

@Database(
    entities = [Article::class],
    version = 1
)
@TypeConverters(Converters::class)
abstract class ArticleDatabase : RoomDatabase() {

    abstract fun getArticleDao(): ArticleDao

    companion object {
        @Volatile
        private var instance: ArticleDatabase? = null
        private val LOCK = Any()

        operator fun invoke(context: Context) = instance ?: synchronized(LOCK) {
            instance ?: createDatabase(context).also { instance = it }
        }

        private fun createDatabase(context: Context) =
            Room.databaseBuilder(
                context.applicationContext,
                ArticleDatabase::class.java,
                "article_db.db"
            ).build()
    }
}

文章.kt

@实体( tableName = "文章" )

data class  Article(
    @PrimaryKey(autoGenerate = true)
    var Id: Int? = null,
    val author: String?,
    val content: String?,
    val description: String?,
    val publishedAt: String?,
    val source: Source?,
    val title: String?,
    val url: String?,
    val urlToImage: String?
) : Serializable

【问题讨论】:

    标签: android database kotlin android-recyclerview android-room


    【解决方案1】:

    您的问题似乎是 @Insert(onConflict = OnConflictStrategy.REPLACE) 正在插入而不是替换。

    OnConflict 策略只有在存在冲突时才会被执行。这通常是违反了UNIQUE 约束 (有关其他违规情况,请参阅下面的链接)

    根据您对问题的解释,没有违规行为,因此没有替换行,而是插入了行。

    简而言之,您需要一个唯一索引来涵盖您的场景,以确保发生违规。

    如果没有 Article 类,就不可能说出需要进行哪些实际更改。但是,以下示例显示了 2 个类(实体)以及如何将唯一索引添加到列/属性 articleName

    所以首先猜测你的文章类可能是什么:-

    @Entity
    data class Article(
        @PrimaryKey
        var articelId: Long? = null,
        var articleName: String,
        var otherData: String
    )
    

    这里是 articleName 属性/列上具有唯一索引的等价物:-

    @Entity(indices = [Index(value = ["articleName"],unique = true)])
    data class ArticleV2(
        @PrimaryKey
        var articleId: Long? = null,
        var articleName: String,
        var otherData: String
    )
    

    示例/演示

    作为两者工作方式不同的示例,后者根据您的期望,考虑以下代码,利用上述代码(为了简洁/方便在主线程上运行):-

    两个表/实体/类的@Dao ArticleDao:-

    @Dao
    interface ArticleDao {
    
        @Insert(onConflict = OnConflictStrategy.REPLACE)
        fun upsert(article: Article): Long
    
        @Insert(onConflict = OnConflictStrategy.REPLACE)
        fun upsert(articleV2: ArticleV2)
    
        @Query("SELECT count(*) FROM article")
        fun getArticleCount(): Long
    
        @Query("SELECT count(*) FROM articlev2")
        fun getArticleV2Count(): Long
    }
    

    @Database ArticleDatabase 类:-

    @Database(
        entities = [Article::class, ArticleV2::class],
        version = 1
    )
    
    abstract class ArticleDatabase : RoomDatabase() {
    
        abstract fun getArticleDao(): ArticleDao
    
        companion object {
            @Volatile
            private var instance: ArticleDatabase? = null
            private val LOCK = Any()
    
            operator fun invoke(context: Context) = instance ?: synchronized(LOCK) {
                instance ?: createDatabase(context).also { instance = it }
            }
    
            private fun createDatabase(context: Context) =
                Room.databaseBuilder(
                    context.applicationContext,
                    ArticleDatabase::class.java,
                    "article_db.db"
                )
                    .allowMainThreadQueries()
                    .build()
        }
    }
    

    最后一个活动,通过循环 5 次在每个表中插入一行,然后在循环之后写入每个表中的行数,从而复制您的 “每次我按下按钮”表到日志。

    class MainActivity : AppCompatActivity() {
    
        lateinit var db: ArticleDatabase
        lateinit var dao: ArticleDao
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_main)
            db = ArticleDatabase.invoke(this)
            dao = db.getArticleDao()
    
            for(i in 1..5) {
                dao.upsert(Article(null, "Article", "otherdata$i"))
                dao.upsert(ArticleV2(null,"Article","otherdata$i"))
            }
            Log.d("ARTICLECOUNT","Number of Articles in article table = " + dao.getArticleCount() + "... in articleV2 table = " + dao.getArticleV2Count())
        }
    }
    

    运行(第一次)时,日志中的输出包含:-

    D/ARTICLECOUNT: Number of Articles in article table = 5... in articleV2 table = 1
    

    所以在 article 表中插入了 5 行,但在 articleV2 表中只有 1 行(因为 articleName 使用的值相同)。

    运行 Database Inspector 后显示:-

    文章

    articleV2

    • 可以看出,otherData 列已全部替换,是 otherdata5
    • 如果 OnConflict 策略为 IGNORE,则 otherData 的值为 otherdata1

    附加

    关于现在添加的评论和文章类。

    首先,谢谢楼主。我还添加了文章类。你的解释很好,但我不明白我需要改变什么。

    您需要确定什么构成重复条目。

    如果未更改的 URL 可能最有可能表示重复。

    所以你可以使用:-

    @Entity( tableName = "articles", indices = [Index(value = ["url"],unique = true)] ) //<<<<<<<<< ONLY CHANGE IS THE @Entity
    data class  Article(
        @PrimaryKey(autoGenerate = true)
        var Id: Int? = null,
        val author: String?,
        val content: String?,
        val description: String?,
        val publishedAt: String?,
        val source: Source?,
        val title: String?,
        val url: String?,
        val urlToImage: String?
    ) : Serializable
    
    • 因此,将在 url 列上创建和维护一个唯一索引(一个不允许多次存储相同值的索引)。

      • 注意 NULL 不被视为重复值(即 NULL 被认为与任何其他 NULL 不同),因此如果此值可以为 null,那么它可能不是一个明智的选择。

    但是,您可以决定多个列应构成不插入的重复项。这是一个显示如何使用多列(复合索引)的变体。

    @Entity( tableName = "articles", indices = [Index(value = ["author","content","description"],unique = true)] ) //<<<<<<<<< ONLY CHANGE IS THE @Entity
    data class  Article(
        @PrimaryKey(autoGenerate = true)
        var Id: Int? = null,
        val author: String?,
        val content: String?,
        val description: String?,
        val publishedAt: String?,
        val source: Source?,
        val title: String?,
        val url: String?,
        val urlToImage: String?
    ) : Serializable
    
    • 在这种情况下,如果作者、内容和描述相同,则为重复。

    注意在所有情况下允许所有空值(毕竟?如果没有验证来阻止所有值为空(即数据可能是无用的)

    您可能还希望考虑在插入后清除输入(某些可能只是),从而降低重复尝试的可能性。

    【讨论】:

    • 首先,谢谢先生。我还添加了文章类。您的解释非常好,但我无法确切了解我需要更改什么。
    • @MertKavrayıcı 我已经更新了答案(请参阅最后的附加,但最终您必须确定需要更改的确切内容因为这是一个设计问题,什么应该被视为有效的文章来添加或拒绝。
    • 解决了问题,先生。非常感谢。
    • 那是因为架构发生了变化。如果您可能会丢失数据,请卸载该应用程序。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2022-06-29
    • 1970-01-01
    • 2021-11-18
    • 1970-01-01
    • 1970-01-01
    • 2021-12-24
    • 1970-01-01
    相关资源
    最近更新 更多