我的应用程序定义了两个实体CardEntityTagEntity,这两个实体具有多对多关系. 我实现了以下交叉引用表来模拟多对多关系:

@Entity(
    primaryKeys = ["card_id", "tag_id"],
    foreignKeys = [
        ForeignKey(
            entity = CardEntity::class,
            parentColumns = ["card_id"],// id in CardEntity
            childColumns = ["card_id"],// id in TagCardCrossRefEntity
            onDelete = ForeignKey.CASCADE,
        ),
        ForeignKey(
            entity = TagEntity::class,
            parentColumns = ["tag_id"],// id in TagEntity
            childColumns = ["tag_id"],// id in TagCardCrossRefEntity
            onDelete = ForeignKey.CASCADE,
        )
    ]
)
data class TagCardCrossRefEntity(
    @ColumnInfo(name = "card_id")
    val cardId: Long,
    @ColumnInfo(name = "tag_id")
    val tagId: Long
)

我还实现了所有其他必需的方法(如解释here所示).我所有的测试都顺利通过.我可以插入、检索和删除带有标签的卡片.

我不确定在实现交叉引用实体时,ForeignKey需要什么.我认为添加ForeignKey的原因之一是定义onDelete策略,但似乎如果我删除交叉引用实体中的ForeignKey的声明,并删除Card表中的CardEntity,则该卡的关系也将被删除.

那么,为什么我需要在交叉引用表中定义Foreign Key呢?

推荐答案

ForeignKey添加了强制实施referential integrity的约束(规则).规则是引用父表的子表中的列的值必须是父表的某一行中引用的列中的现有值.简单地说,孤儿是不允许的.

  • 查看https://database.guide/what-is-referential-integrity/(或使用类似what is referential integrity的内容进行搜索)

  • 应该注意的是,外键冲突将触发异常,而与OR无关...(忽略、替换...).

    • The documentation says
      • The ON CONFLICT clause applies to UNIQUE, NOT NULL, CHECK, and PRIMARY KEY constraints. The ON CONFLICT algorithm does not apply to FOREIGN KEY constraints. There are five conflict resolution algorithm choices: ROLLBACK, ABORT, FAIL, IGNORE, and REPLACE. The default conflict resolution algorithm is ABORT. This is what they mean:https://www.sqlite.org/lang_conflict.html

除了规则ACTION之外,还可以在删除或更新父项中的被引用列时发生违反(冲突)时设置帮助维护referential integrity的规则ACTION.最常见/最有用的ACTION是级联,因此有onDeleteonUpdate参数.

你很可能希望提到https://www.sqlite.org/foreignkeys.html

Demo

主题不是使用Room,而是SQLite是如何工作的.Room是SQLite的包装器,基本上,注释导致生成SQL.

  • e.g. look in the generated java for the @Database annotated class suffixed with _Impl and find the createAllTables method (function in Kotlin terms) and you will see the SQL for creating the tables.

该演示使用SQLite SQL并可放入SQLite工具中,使用的是Navicat for SQLite:-

/* Make sure that the demo environment is tidy*/
DROP TABLE IF EXISTS a_xref_b;
DROP TABLE IF EXISTS a_xref_b_NO_RI;
DROP TABLE IF EXISTS tablea;
DROP TABLE IF EXISTS tableb;

/* Create the core tables a and b */
CREATE TABLE IF NOT EXISTS tablea (id INTEGER PRIMARY KEY /*<<<< PARENT */, a_value TEXT /* etc */); 
CREATE TABLE IF NOT EXISTS tableb (id INTEGER PRIMARY KEY /*<<<< PARENT */, b_value TEXT /* etc */); 

/* Create the cross reference table WITHOUT foreign key constraints */
CREATE TABLE IF NOT EXISTS a_xref_b_NO_RI (a_ref INTEGER, b_ref INTEGER, PRIMARY KEY (a_ref,b_ref));

/* Load some data into the core tables */
INSERT INTO tablea VALUES (1,'Aa'),(2,'Ba'),(3,'Ca'),(4,'Da');
INSERT INTO tableb VALUES (10,'Ab'),(11,'Bb'),(12,'Cb'),(13,'Db');
/* add some cross references including a row where referential integrity does not exist (9999,7777) */
INSERT INTO a_xref_b_NO_RI VALUES (1,11),(1,13),(3,10),(3,12),(9999,7777)/*<<<< CHILDREN THAT DONT EXIST */;

/* RESULT 1 (not a little complicated to show ALL cross references)*/
SELECT ALL 
    *, 
    (SELECT group_concat(a_value)||' : '||group_concat(id) FROM tablea WHERE tablea.id = a_ref) AS from_a,
    (SELECT group_concat(b_value)||' : '||group_concat(id) FROM tableb WHERE tableb.id = b_ref) AS from_b
FROM a_xref_b_NO_RI;
/* DELETE some rows from b*/
DELETE FROM tableb WHERE id > 11 AND id < 1000;
/* RESULT 2 (now what happens) */
SELECT ALL 
    *, 
    (SELECT group_concat(a_value)||' : '||group_concat(id) FROM tablea WHERE tablea.id = a_ref) AS from_a,
    (SELECT group_concat(b_value)||' : '||group_concat(id) FROM tableb WHERE tableb.id = b_ref) AS from_b
FROM a_xref_b_NO_RI;

/*========= FOREIGN KEYS ==========*/
/* Empty table b* */
DELETE FROM tableb;
/* Create the xref table WITH FK constraints and actions */
CREATE TABLE IF NOT EXISTS a_xref_b (
    a_ref INTEGER REFERENCES tablea(id) ON DELETE CASCADE ON UPDATE CASCADE , 
    b_ref INTEGER REFERENCES tableb(id) ON DELETE CASCADE ON UPDATE CASCADE, 
    PRIMARY KEY (a_ref,b_ref)
);
/* Re load table b */
INSERT INTO tableb VALUES (10,'Ab'),(11,'Bb'),(12,'Cb'),(13,'Db');
/* Add the xref rows (NOTE not 9999,7777 as an exception occurr) */
INSERT INTO a_xref_b VALUES (1,11),(1,13),(3,10),(3,12);
/* RESULT 3 (equiv of RESULT 1) */
SELECT ALL 
    *, 
    (SELECT group_concat(a_value)||' : '||group_concat(id) FROM tablea WHERE tablea.id = a_ref) AS from_a,
    (SELECT group_concat(b_value)||' : '||group_concat(id) FROM tableb WHERE tableb.id = b_ref) AS from_b
FROM a_xref_b;
/* do the same delete as above */
DELETE FROM tableb WHERE id > 11 AND id < 1000;
/* update some referenced columns */
UPDATE tablea SET id = id * 10;
/* RESULT 4 (equiv of RESULT 3) */
SELECT ALL 
    *, 
    (SELECT group_concat(a_value)||' : '||group_concat(id) FROM tablea WHERE tablea.id = a_ref) AS from_a,
    (SELECT group_concat(b_value)||' : '||group_concat(id) FROM tableb WHERE tableb.id = b_ref) AS from_b
FROM a_xref_b;
/* TRY TO INSERT orphans */
INSERT OR IGNORE INTO a_xref_b VALUES(9999,7777);
/* CLEAN UP DEMO ENVIRONMENT */
DROP TABLE IF EXISTS a_xref_b;
DROP TABLE IF EXISTS a_xref_b_NO_RI;
DROP TABLE IF EXISTS tablea;
DROP TABLE IF EXISTS tableb;
  • 请参阅 comments

RESULTS (output of the 4 SELECTs):-

  • RESULT 1

  • enter image description here

    • 高亮显示不存在RI的地方
    • 例如,9999和7777是孤儿(无父母的子元素)
  • RESULT 2

  • enter image description here

    • 由于从Tableb中删除行而导致的更多孤儿
  • RESULT 3 (using FKEYS orphans not allowed)

  • enter image description here

    • 没有孤儿(请注意,插入9999,7777将失败(见下文))
  • RESULT 4 (after equivalent deletions and update to change referenced parent values for tablea)

  • enter image description here

  • 可以看到,对表A中的id(乘以10)的更改(更新)已级联到子级,即a_xref_b表中的a_ref列,因此关系保持不变.

  • ATTEMPT TO ADD ORPHANS

  • 消息(失败)=

:-

/* TRY TO INSERT orphans */
INSERT OR IGNORE INTO a_xref_b VALUES(9999,7777)
> FOREIGN KEY constraint failed
> Time: 0s
  • 请注意,由于故障,清理不会运行

Android相关问答推荐

编写Inbox需要具有匹配兼容性的Kotlin版本

无法安装后重新编译android代码'

Android意图过滤器不限制应用程序仅处理YouTube链接

图像在Android Studio中显示,但不在设备中显示

我如何剪裁一个可由另一个合成的

strings.xml中字符串数组中的占位符

Jetpack Compose:带芯片的Textfield

从惰性列中删除项目时Jetpack Compose崩溃

如何在Jetpack Compose中将对象的移动从一个路径平滑地切换到另一个路径?

从片段导航回来

更改活动(上下文)对接收到的uri的访问权限的影响?

减少Compose中可滚动选项卡之间的间距

在 Android 房间迁移中获取上下文

是否可以按照干净的体系 struct 将活动实例传递给视图模型

状态更改后 colored颜色 未正确更改

找不到(包名称).在以下位置搜索:

如何在 react native 应用程序中显示复选框?当它在 android 模拟器中可见时

如何从 Jetpack Compose 中的 Radio 组中获取价值

如何使用 Glide 将图像下载到设备内部存储

多个项目 react-native android 构建错误