传播级联删除引发外键约束失败

Posted

技术标签:

【中文标题】传播级联删除引发外键约束失败【英文标题】:Propagate cascade delete raises foreign key constraint fails 【发布时间】:2019-10-15 05:36:54 【问题描述】:

我将 Spring Boot (2.1.0.RELEASE) 与 Spring Data JPA 一起使用。数据库是 mysql

我有一些连锁级联删除的问题。

我有以下型号:

我不使用@ManyToMany,因为我需要在生成的表中添加额外的字段,所以我的实体如下(无用的属性已被删除):

@Audited
@Entity
@Table(name = "request")
public class Request 

    @OneToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL, orphanRemoval = true)
    private Resource resource;




@Audited
@Entity
@Table(name = "resource")
public class Resource 

    @OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL, orphanRemoval = true, mappedBy = "resource")
    private Set<ResourceArticle> resourceArticles;



@Audited
@Entity
@Table(name = "resource_article")
public class ResourceArticle 

    @ManyToOne(fetch = FetchType.EAGER)
    @JoinColumn(name = "article_id")
    private Article article;

    @ManyToOne(fetch = FetchType.EAGER)
    @JoinColumn(name = "resource_id")
    private Resource resource;

    @OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL, orphanRemoval = true, mappedBy = "resourceArticle")
    private Set<ResourceArticleOption> options;



@Audited
@Entity
@Table(name = "resource_article_option")
public class ResourceArticleOption 

    @ManyToOne(fetch = FetchType.EAGER)
    @JoinColumn(name = "option_id")
    private Option option;

    @ManyToOne(fetch = FetchType.EAGER)
    @JoinColumn(name = "resource_article_id")
    private ResourceArticle resourceArticle;


然后我的经理发出请求 delete 扩展 CrudRepository

/* Repositroy */
public interface RequestRepository extends CrudRepository<Request, Long> 


/* Manager */
@Transactional
@Component("requestMgr")
public class RequestManager 

    @Autowired
    RequestRepository requestRepository;

    public void delete(Request request) 
        requestRepository.delete(request);
    



/* Viewmodel */
public class RequestVm 

    @WireVariable
    private RequestManager requestMgr;

    public void deleteRequest(Request req) 
        requestMgr.delete(req);
    


错误是:

原因:java.sql.SQLException:无法删除或更新父级 行:外键约束失败 (my_db.resource_article, 约束 FK5wqvprkwx05fb5hgt6w9h7nbk 外键 (resource_id) 参考 resource (id))

启用跟踪时的输出:

delete from request where id=?
binding parameter [1] as [BIGINT] - [24]
delete from resource where id=?
binding parameter [1] as [BIGINT] - [71]
SQL Error: 1451, SQLState: 23000
(conn=30) Cannot delete or update a parent row: a foreign key constraint fails (`my_db`.`resource_article`, CONSTRAINT `FK5wqvprkwx05fb5hgt6w9h7nbk` FOREIGN KEY (`resource_id`) REFERENCES `resource` (`id`))

奇怪的是它试图按request > resource > resource_article > resource_article_option 的顺序删除?

需要CascadeType.ALL,因为我想坚持和删除。

我可以通过在删除之前设置对null 的引用来打破链条,但当然会导致数据库中出现孤立记录。

这里最好的策略是什么?

【问题讨论】:

请注意,您可以级联多个操作而无需级联所有操作。这并不一定意味着您不需要或不需要CascadeType.ALL,或者使用它会导致您的问题,只是您给出使用它的原因无效(JPA 识别的操作不仅仅是PERSISTREMOVE). 据我所知或所知,删除 Resource via 扩展 CrudRepository 的存储库应遵守为父实体声明的 JPA 级联属性。这似乎没有发生,这让我怀疑有些东西不是你所呈现的那样,或者有些重要的东西被遗漏了。也许您的实体的部署版本没有启用级联?也许您已经自定义了您的存储库,使其 delete() 方法以某种方式绕过 JPA? 谢谢约翰。确实我说错了,我知道我可以使用多种级联类型,但据我所知,我没有理由不级联所有东西,所以我使用ALL。我在这里没有介绍的其他属性是基本的@Column,应该不会造成任何问题。我自愿省略的唯一一件事是我在此之上还有另一个表,REQUEST 表与RESOURCE 具有 1-1 关系,删除是根据此请求完成的。最后请注意,我使用 Envers 进行审计,我的实体有 @Audited 注释,所以对于每个表我都有另一个 xxx_audit_log 表。我已经更新了我的帖子。 【参考方案1】:

我通过在每个关系中删除不是关系所有者的实体来使其工作。

@ManyToMany 关系中的级联删除不仅适用于链接表,还适用于关系的另一端。

@ManyToMany关联中删除时是众所周知的问题(这很好解释here例如),但我没有注意,因为我的注释不是直接@ManyToMany而是双重@OneToMany关联, 我虽然 JPA 会像简单的@OneToMany 关系那样级联删除,但似乎我错了。

在删除请求之前,我使用@preRemove 清理了关系:

ResourceArticle

@PreRemove
public void preRemove() 
    article.getResourceArticles().remove(this);

ResourceArticleOption

@PreRemove
public void preRemove() 
    option.getResourceArticleOptions().remove(this);

然后一切正常。

【讨论】:

以上是关于传播级联删除引发外键约束失败的主要内容,如果未能解决你的问题,请参考以下文章

Spring boot delete 没有级联,它将外键设置为 null,然后在 null 约束上失败

禁止级联删除的外键约束

Android中使用SQLite的外键约束?在删除级联

有外键约束的表,删除父表的数据,会出现哪几种情况?

SQL外键约束&&多表查询&&级联删除&&子查询

SQL外键约束&&多表查询&&级联删除&&子查询