传播级联删除引发外键约束失败
Propagate cascade delete raises foreign key constraint fails
我正在使用 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);
}
}
错误是:
Caused by: java.sql.SQLException: 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
))
启用跟踪时的输出:
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
的引用来打破链条,但这当然会导致数据库中出现孤立记录。
这里最好的策略是什么?
我通过在每个关系中删除不是关系所有者的实体来使其工作。
@ManyToMany
关系中的级联删除不仅适用于 link table,也适用于关系的另一端。
从@ManyToMany
关联中删除时是一个well-known问题(这很好解释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 (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);
}
}
错误是:
Caused by: java.sql.SQLException: Cannot delete or update a parent row: a foreign key constraint fails (
my_db
.resource_article
, CONSTRAINTFK5wqvprkwx05fb5hgt6w9h7nbk
FOREIGN KEY (resource_id
) REFERENCESresource
(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
的引用来打破链条,但这当然会导致数据库中出现孤立记录。
这里最好的策略是什么?
我通过在每个关系中删除不是关系所有者的实体来使其工作。
@ManyToMany
关系中的级联删除不仅适用于 link table,也适用于关系的另一端。
从@ManyToMany
关联中删除时是一个well-known问题(这很好解释here例如),但我没有注意,因为我的注释不直接@ManyToMany
但是双 @OneToMany
关联,我虽然 JPA 会像在简单的 @OneToMany
关系中一样级联删除,但看来我错了。
在删除请求之前,我已经使用 @preRemove
清理了关系:
在ResourceArticle
中:
@PreRemove
public void preRemove() {
article.getResourceArticles().remove(this);
}
在ResourceArticleOption
中:
@PreRemove
public void preRemove() {
option.getResourceArticleOptions().remove(this);
}
然后一切正常。