Room 数据库中的@ForeignKey 和@Relation 注释有什么区别?

Roa*_*oab 9 android android-room android-architecture-components

我无法理解这些注释之间的区别。在我的用例中,我想在表之间创建一对多关系。并找到了两个选项:一个是@ForeignKey,另一个是@Relation

我还发现,如果我更新该行(例如使用 OnCoflictStrategy.Replace),我将丢失该行的外键,这是真的吗?

Mik*_*keT 16

一个@ForeignKey定义需要子列(S)在父列(S)存在的约束(又名规则)。如果试图打破该规则,则会发生冲突(可以通过 onDelete/onUpdate 定义以各种方式处理)。

一个@Relationship用于定义,其中一些孩子的(也许是外键儿童)对象的父对象返回的关系。

在它之下,@Relation自动(有效地)连接表并生成子对象的数量。虽然@ForeignKey只影响架构(onDelete/onUpdate 处理除外),但它不会导致连接相应的表。

也许考虑以下几点:-

服务实体

@Entity(
    tableName = "services"
)
class Services {

    @PrimaryKey(autoGenerate = true)
    @ColumnInfo(name = "services_id")
    var id: Long = 0
    var service_date: String = ""
    var user_mobile_no: String = ""

}
Run Code Online (Sandbox Code Playgroud)

服务详细信息实体:-

@Entity(
    tableName = "service_detail",
    foreignKeys = [
        ForeignKey(
            entity = Services::class,
            parentColumns = ["services_id"],
            childColumns = ["services_id"],onDelete = ForeignKey.SET_DEFAULT
        )
    ]
)
class ServiceDetail {

    @PrimaryKey
    var id: Long? = null;
    var services_id: Long = 0;
    @ColumnInfo(defaultValue = "1")
    var service_type_id: Long = 0;

    constructor()

    @Ignore
    constructor(services_id: Long, service_type_id: Long) {
        this.services_id = services_id
        this.service_type_id = service_type_id
    }
}
Run Code Online (Sandbox Code Playgroud)
  • 这就是说,为了添加 ServiceDetail,services_id 列的值必须是services表的services_id列中存在的值,否则会发生冲突。此外,如果从 services 表中删除了一行,那么 service_detail 表中引用该行的任何行也将被删除(否则该行不能从 services 表中删除)。

现在考虑这个普通类(POJO),它不是实体(又名表):-

class ServiceWithDetail {

    @Embedded
    var services: Services? = null

    @Relation(entity = ServiceDetail::class,parentColumn = "services_id",entityColumn = "services_id")
    var serviceDetail: List<ServiceDetail>? = null
}
Run Code Online (Sandbox Code Playgroud)

这大致是说,当您请求 ServiceWithDetail 对象时,然后获取一个 services 对象以及相关 service_detail 对象的列表

你会有一个 Dao,例如:-

@Query("SELECT * FROM services")
fun getAllServices() :List<ServiceWithDetail>
Run Code Online (Sandbox Code Playgroud)

因此,它将从 services 表中获取所有服务以及相关的服务(即 services_detail 中的 services_id 与正在处理的当前服务行的 services_id 相同)。

冲突策略

REPLACE执行以下操作:-

当发生 UNIQUE 或 PRIMARY KEY 约束冲突时,REPLACE 算法在插入或更新当前行之前删除导致约束冲突的预先存在的行,并且命令继续正常执行。

如果发生 NOT NULL 约束冲突,REPLACE 冲突解决方案会使用该列的默认值替换 NULL 值,或者如果该列没有默认值,则使用 ABORT 算法。如果发生 CHECK 约束或外键约束冲突,REPLACE 冲突解决算法的工作方式类似于 ABORT。

当 REPLACE 冲突解决策略删除行以满足约束时,当且仅当启用递归触发器时才会触发删除触发器。

不会为由 REPLACE 冲突解决策略删除的行调用更新挂钩。REPLACE 也不会增加更改计数器。本段中定义的异常行为可能会在未来版本中更改。代替

因此,您所经历的行为的可能性。但是,这取决于更新正在执行的操作。如果 ForeignKey(s) 的值不同,那么它们应该,假设没有外键冲突,用新的有效值替换外键值。如果外键值未更改,则替换行将具有相同的外键。

  • @AlitonOliveira 这是一个迟到的回复,但为了未来的用户,答案概述如下:https://developer.android.com/training/data-storage/room/relationships#one-to-one (2认同)

Hum*_*Bee 8

虽然这两个概念都用于为 Room 数据库带来结构,但它们的用例不同之处在于:

  • @ForeignKey用于在插入/修改数据时强制执行关系结构
  • @Relation用于在检索/查看数据时强制执行关系结构。

为了更好地理解ForeignKeys考虑以下示例的必要性:

@Entity
data class Artist(
    @PrimaryKey val artistId: Long,
    val name: String
)

@Entity
data class Album(
    @PrimaryKey val albumId: Long,
    val title: String,
    val artistId: Long
)

Run Code Online (Sandbox Code Playgroud)

使用此数据库的应用程序有权假设专辑表中的每一行都存在艺术家表中的对应行。不幸的是,如果用户使用外部工具编辑数据库或者如果应用程序中存在错误,则可能会将与艺术家表中任何行都不对应的行插入到专辑表中。或行可能会从被删除艺术家表,在搁置孤行表不符合任何在剩余行的艺术家。这可能会导致应用程序或应用程序稍后出现故障,或者至少使应用程序编码更加困难。

一种解决方案是向数据库模式添加一个 SQL 外键约束,以加强ArtistAlbum表之间的关系。

@Entity
data class Artist(
    @PrimaryKey val id: Long,
    val name: String
)

@Entity(
    foreignKeys = [ForeignKey(
        entity = Artist::class,
        parentColumns = arrayOf("id"),
        childColumns = arrayOf("artistId"),
        onUpdate = ForeignKey.CASCADE,
        onDelete = ForeignKey.CASCADE
    )]
)
data class Album(
    @PrimaryKey val albumId: Long,
    val title: String,
    val artistId: Long
)

Run Code Online (Sandbox Code Playgroud)

现在,每当您插入新专辑时,SQL 都会检查是否存在具有该 ID 的艺术家,然后您才能继续进行交易。此外,如果您更新艺术家的信息或将其从艺术家表中删除,SQL 将检查该艺术家的任何专辑并更新/删除它们。这就是魔法ForeignKey.CASCADE

但这不会自动使它们在查询期间一起返回,因此输入@Relation

// Our data classes from before
@Entity
data class Artist(
    @PrimaryKey val id: Long,
    val name: String
)

@Entity(
    foreignKeys = [ForeignKey(
        entity = Artist::class,
        parentColumns = arrayOf("id"),
        childColumns = arrayOf("artistId"),
        onUpdate = ForeignKey.CASCADE,
        onDelete = ForeignKey.CASCADE
    )]
)
data class Album(
    @PrimaryKey val albumId: Long,
    val title: String,
    val artistId: Long
)

// Now embedded for efficient querying
data class ArtistAndAlbums(
    @Embedded val artist: Artist,
    @Relation(
         parentColumn = "id",
         entityColumn = "artistId"
    )
    val album: List<Album> // <-- This is a one-to-many relationship, since each artist has many albums, hence returning a List here
)

Run Code Online (Sandbox Code Playgroud)

现在,您可以使用以下命令轻松获取艺术家列表及其专辑:

@Transaction 
@Query("SELECT * FROM Artist")
fun getArtistsAndAlbums(): List<ArtistAndAlbums>
Run Code Online (Sandbox Code Playgroud)

以前您必须编写很长的样板 SQL 查询来连接和返回它们。

注意:@Transaction注释需要让 SQLite 一次性执行两个搜索查询(一个在 Artist 表中查找,一个在 Album 表中查找),而不是单独执行。

资料来源:

摘自 Android 开发者文档:

有时,您希望在数据库逻辑中将实体或数据对象表示为一个有凝聚力的整体,即使该对象包含多个字段。在这些情况下,您可以使用 @Embedded 批注来表示您希望在表中分解为其子字段的对象。然后,您可以像查询其他单个列一样查询嵌入的字段。

外键允许您指定跨实体的约束,以便 SQLite 在您修改数据库时确保关系有效。

SQLite 的外键文档