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

What is the difference between @ForeignKey and @Relation annotations in Room database?

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

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

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

一个@Relationship用于定义一个关系,在该关系中父对象中返回了多个子对象(可能是外键子对象)。

在其下方 @Relation 自动(有效)加入 table 并生成子对象的数量。虽然 @ForeignKey 只会影响架构(onDelete/onUpdate 处理除外),但不会导致相应的 table 被加入。

也许考虑以下:-

服务实体

@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 = ""

}

服务详细信息实体:-

@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
    }
}
  • 这是说,为了添加 ServiceDetail,services_id 列的值必须是 services_id 列中存在的值服务 Table,否则会发生冲突。此外,如果从服务 table 中删除了一行,那么 service_detail table 中引用该行的任何行也将被删除(否则无法从服务中删除该行table).

现在考虑这个正常的 class (POJO),它不是一个实体(又名 table):-

class ServiceWithDetail {

    @Embedded
    var services: Services? = null

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

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

你会有这样的道:-

@Query("SELECT * FROM services")
fun getAllServices() :List<ServiceWithDetail>

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

onConflictStrategy

REPLACE 执行以下操作:-

When a UNIQUE or PRIMARY KEY constraint violation occurs, the REPLACE algorithm deletes pre-existing rows that are causing the constraint violation prior to inserting or updating the current row and the command continues executing normally.

If a NOT NULL constraint violation occurs, the REPLACE conflict resolution replaces the NULL value with the default value for that column, or if the column has no default value, then the ABORT algorithm is used. If a CHECK constraint or foreign key constraint violation occurs, the REPLACE conflict resolution algorithm works like ABORT.

When the REPLACE conflict resolution strategy deletes rows in order to satisfy a constraint, delete triggers fire if and only if recursive triggers are enabled.

The update hook is not invoked for rows that are deleted by the REPLACE conflict resolution strategy. Nor does REPLACE increment the change counter. The exceptional behaviors defined in this paragraph might change in a future release.REPLACE

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

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

  • @ForeignKey 用于在 INSERTING / MODYFING 您的数据
  • 时强制执行关系结构
  • @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
)

使用此数据库的应用程序有权假定 专辑 table 中的每一行在 艺术家 table。不幸的是,如果用户使用外部工具编辑数据库,或者如果应用程序中存在错误,行可能会插入到 Album table 中,但不对应于任何艺术家 table 中的行。或者可能会从 Artist table 中删除行,在 Album table 中留下不对应的孤立行艺术家 中的任何剩余行。这可能会导致一个或多个应用程序在以后出现故障,或者至少会使应用程序的编码变得更加困难。

一个解决方案是向数据库模式添加一个 SQL 外键约束以加强 ArtistAlbum[=63 之间的关系=] table.

@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
)

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

的魔力

但这不会在查询期间自动使它们 return 在一起,因此输入 @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
)

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

@Transaction 
@Query("SELECT * FROM Artist")
fun getArtistsAndAlbums(): List<ArtistAndAlbums>

虽然以前您必须编写很长的样板 SQL 查询才能加入并 return 它们。

注意:需要 @Transaction 注释才能使 SQLite 执行两个搜索查询(在艺术家 table 中进行一次查找,在专辑中进行一次查找 table) 一次而不是分开。

来源:

摘自 Android 开发人员文档:

Sometimes, you'd like to express an entity or data object as a cohesive whole in your database logic, even if the object contains several fields. In these situations, you can use the @Embedded annotation to represent an object that you'd like to decompose into its subfields within a table. You can then QUERY the embedded fields just as you would for other individual columns.

Foreign keys allows you to specify constraints across Entities such that SQLite will ensure that the relationship is valid when you MODIFY the database.

SQL网站的 ForeignKey documentation.