Room数据库onDelete=RESTRICT失效,求保留关联父行或虚外键方案
你设置RESTRICT外键的初衷是阻止删除有子行的父行,但调用deleteAll()时是直接执行全表删除操作,SQLite检测到存在关联的document_products记录后,就会触发外键约束报错——这其实是RESTRICT的正常行为:它会直接终止违反约束的删除操作,而非自动跳过关联的父行。
方案一:仅删除无关联的父行(保留有子行的产品)
修改ProductDAO的删除方法,只删除未被document_products引用的产品,避免触发外键约束:
@Query("DELETE FROM products WHERE products_id NOT IN (SELECT DISTINCT document_products_productIdMap FROM document_products)") suspend fun deleteUnusedProducts(): Int
这个查询会先提取所有被document_products关联的productId,再删除products表中不在该集合内的记录,确保有子行的父行被完整保留。
方案二:保留外键字段但取消数据库层面的约束
如果只想把document_products中的productId作为逻辑外键,不需要数据库强制执行关联约束,可以移除document_products实体中针对Product的ForeignKey注解(保留索引以优化查询性能):
@JsonClass(generateAdapter = true) @Entity( tableName = "document_products", foreignKeys = [ ForeignKey( entity = Document::class, parentColumns = ["id"], childColumns = ["documentId"], onDelete = CASCADE ) // 移除Product对应的ForeignKey约束 ], indices = [Index("document_products_productIdMap"), Index("documentId"), Index( value = ["document_products_productIdMap", "documentId", "labelType"], unique = true )] )
修改后数据库不再校验productId与products.id的关联关系,调用deleteAll()时可以删除所有products数据,同时保留document_products的记录。但要注意这种方式会带来数据不一致风险(比如document_products中可能存在无效的productId),需要在业务逻辑中自行处理数据校验工作。
补充:延迟约束检查(仅适用于事务场景)
如果只是需要延迟约束检查时机(比如在事务中先删除产品再处理子行),可以给Product对应的ForeignKey添加deferred = true参数:
ForeignKey( entity = Product::class, parentColumns = ["products_id"], childColumns = ["document_products_productIdMap"], onDelete = RESTRICT, deferred = true )
但这种方式只是把约束检查延迟到事务结束,若事务内未处理关联子行,最终还是会触发报错,仅适用于特定的事务操作场景。
内容的提问来源于stack exchange,提问作者NiceToMytyuk

