Android 房间属于(一对一)关系

Android Room belongsTo (one to one) relationship

假设我有这样一篇文章 table:

Article {
   id;
   title;
   content;
   main_reference_id;
   secondary_reference_id;
}

还有一个参考 table 有点像这样:

Reference {
   id;
   other_reference_related_columns...;
}

现在我想在获取文章的同时还使用另一个 POJO 获取 main referencesecondary reference

data class ArticleFull(
    @Embedded
    val article: article,
    @Relation(parentColumn = "main_reference_id", entityColumn = "id")
    val main_reference: Reference,
    @Relation(parentColumn = "secondary_reference_id", entityColumn = "id")
    val other_reference: Reference
)

但我不确定我写的是否正确使用了 @Relation 注释。

N.B.: 我是Laravel/Eloquent出身,所以比较熟悉这些belongsTo, hasOne, hasMany, belongsToMany,等等关系类型。

谢谢。

But I'm not sure what I wrote is the right usage of @Relation annotation or not.

是的,没关系。

这是一个工作示例。这显示了 ArticleFull POJO 的使用:-

首先是实体(表):-

参考:-

@Entity
data class Reference(
    @PrimaryKey
    val id: Long? = null,
    val other_data: String
)

:-

@Entity(
    foreignKeys = [
        ForeignKey(
            entity = Reference::class,
            parentColumns = ["id"],
            childColumns = ["main_reference_id"],
            onDelete = ForeignKey.CASCADE,
            onUpdate = ForeignKey.CASCADE
        ),
        ForeignKey(
            entity = Reference::class,
            parentColumns = ["id"],
            childColumns = ["secondary_reference_id"],
            onDelete = ForeignKey.CASCADE,
            onUpdate = ForeignKey.CASCADE
        )
    ]
)
data class Article(
    @PrimaryKey
    val id: Long? = null,
    val title: String,
    val content: String,
    @ColumnInfo(index = true)
    val main_reference_id: Long,
    @ColumnInfo(index = true)
    val secondary_reference_id: Long
)
  • 添加了外键约束,它们有助于加强参照完整性,它们是可选的。 onDelete 和 onUpdate 在外键中是可选的。

一个@Daoclass(抽象class而不是接口,抽象class更通用)ArticleAndReferenceDao :-

@Dao
abstract class ArticleAndReferenceDao {
    @Insert
    abstract fun insert(reference: Reference): Long
    @Insert
    abstract fun insert(article: Article): Long
    @Transaction
    @Query("SELECT * FROM article")
    abstract fun getAllArticleFull(): List<ArticleFull>
    @Transaction@Query("SELECT * FROM article WHERE id=:articleId")
    abstract fun getArticleFullByArticleId(articleId: Long): List<ArticleFull>
}

一个@Database class ArticleDatabase :-

@Database(entities = [Reference::class,Article::class],version = 1)
abstract class ArticleDatabase: RoomDatabase() {
    abstract fun getArticleAndReferenceDao(): ArticleAndReferenceDao

    companion object {

        @Volatile
        private var instance: ArticleDatabase? = null

        fun getArticleDatabaseInstance(context: Context): ArticleDatabase {
            if(instance == null) {
                instance = Room.databaseBuilder(
                    context,
                    ArticleDatabase::class.java,
                    "article.db"
                )
                    .allowMainThreadQueries()
                    .build()
            }
            return instance as ArticleDatabase
        }
    }
}

最后是一些 Activity 代码,注意为了方便和简洁起见,使用 .allowMainThreadQueries 允许代码在主线程上 运行 :-

class MainActivity : AppCompatActivity() {

    lateinit var articleDatabase: ArticleDatabase
    lateinit var articleAndReferenceDao: ArticleAndReferenceDao
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        articleDatabase = ArticleDatabase.getArticleDatabaseInstance(this)
        articleAndReferenceDao = articleDatabase.getArticleAndReferenceDao()

        val ref1 = articleAndReferenceDao.insert(Reference(other_data = "Reference1"))
        val ref2 = articleAndReferenceDao.insert(Reference(other_data = "Reference2"))
        val ref3 = articleAndReferenceDao.insert(Reference(other_data = "Reference3"))
        val ref4 = articleAndReferenceDao.insert(Reference(other_data = "Reference4"))

        articleAndReferenceDao.insert(Article(title = "Article1",main_reference_id = ref1,secondary_reference_id = ref2, content = "Content for Article1"))
        articleAndReferenceDao.insert(Article(title = "Article2", main_reference_id = ref3, secondary_reference_id = ref4,content = "Content for Article2"))

        // AND/OR

        articleAndReferenceDao.insert(
            Article(
            title = "Article3",
                content = "Content for Article 3",
                main_reference_id = articleAndReferenceDao.insert(Reference(other_data = "Reference5")),
                secondary_reference_id = articleAndReferenceDao.insert(Reference(other_data = "reference6"))
            )
        )

        for(d: ArticleFull in articleAndReferenceDao.getAllArticleFull()) {
            Log.d("ARTICLEINFO"," Article is ${d.article.content} ID is ${d.article.id} " +
                    "\n\tMain Reference is ${d.main_reference.other_data} ID is ${d.main_reference.id}" +
                    "\n\tSecondary  Reference is ${d.other_reference.other_data} ID is ${d.other_reference.id}")
        }  
    }
}

运行 以上结果在日志中包含:-

D/ARTICLEINFO:  Article is Content for Article1 ID is 1 
        Main Reference is Reference1 ID is 1
        Secondary  Reference is Reference2 ID is 2
D/ARTICLEINFO:  Article is Content for Article2 ID is 2 
        Main Reference is Reference3 ID is 3
        Secondary  Reference is Reference4 ID is 4
D/ARTICLEINFO:  Article is Content for Article 3 ID is 3 
        Main Reference is Reference5 ID is 5
        Secondary  Reference is reference6 ID is 6

额外

您也可以对所有三个部分使用@Embedded。

优点是:-

  • 更灵活的过滤,即您可以过滤子项(虽然可以使用@Relationship,但您需要定义 JOIN)
  • 不是针对 @Relationship 的多个基础查询,而是单个查询检索所有数据

缺点是:-

  • 更复杂的查询
  • 如果列名不是唯一的,则需要使用@ColumnInfo 的prefix = 注释来消除歧义,因此需要更复杂的查询来相应地命名输出列。

所以你可以:-

data class ArticleFullAlternative(

    @Embedded
    val article: Article,
    @Embedded(prefix = "main_")
    val main_reference: Reference,
    @Embedded(prefix = "other_")
    val other_reference: Reference
)

连同@Query,例如:-

@Query("SELECT article.*, " +
        /* as prefix = "main_" has been used then rename output columns accordingly */
        "m.id AS main_id, m.other_data AS main_other_data, " +
        /* as prefix = "other_" has been used then rename output columns accordingly */
        "o.id AS other_id, o.other_data AS other_other_data " +
        "FROM article " +
        "JOIN reference AS m /*<<<<< to disambiguate column names */ ON main_reference_id = m.id " +
        "JOIN reference AS o /*<<<<< to disambiguate column names */ ON main_reference_id = o.id ")
abstract fun getArticleFullAlternative(): List<ArticleFullAlternative>

在 Activity 中使用的示例可以是:-

    for(afa: ArticleFullAlternative in articleAndReferenceDao.getArticleFullAlternative()) {
        Log.d("ALTARTICLEINFO"," Article is ${afa.article.content} ID is ${afa.article.id} " +
                "\n\tMain Reference is ${afa.main_reference.other_data} ID is ${afa.main_reference.id}" +
                "\n\tSecondary  Reference is ${afa.other_reference.other_data} ID is ${afa.other_reference.id}")
    }
  • 这会产生完全相同的输出