Java Spring级联元素集合删除

Posted

技术标签:

【中文标题】Java Spring级联元素集合删除【英文标题】:Java Spring cascade elementcollection delete 【发布时间】:2020-08-12 23:22:21 【问题描述】:

由于某种原因,当我尝试删除其中包含 elementcollection 的父元素时,我的 delete 没有级联,这两个类如下:

@Entity
@Table(name="Timestamps")
@JsonIgnoreProperties(ignoreUnknown = true)
public class ProductList 
    private boolean success;
    @Id
    private Date lastUpdated;

    private String time = "minute";

    @ElementCollection
    @MapKeyColumn(name="product_id")
    @CollectionTable(name="Products")
    private Map<String,Product> products;

还有:

@Embeddable
@JsonIgnoreProperties(ignoreUnknown = true)
public class Product
    @Embedded
    private Status quick_status;

目前这是我在班级中唯一的字段,因为我已经删除了所有其他字段,试图弄清楚为什么当我尝试删除父对象时删除不会级联到 Products 表。以下是我正在运行的查询:

DELETE FROM Timestamps list WHERE list.last_updated !=0;

last_updated 值总是非零,所以我只是使用这个查询来测试删除,但即使我在 mysql shell 中运行查询,我也会得到“无法删除或更新父行:外键约束失败”我认为 elementcollection 注释应该是自动级联的,有什么我遗漏的吗?

编辑,当下面是 Hibernate 发送的 sql 命令时,你会注意到第三个它缺少级联。

Hibernate: create table products (product_list_last_updated datetime(6) not null, buy_price float not null, sell_price float not null, product_id varchar(255) not null, primary key (product_list_last_updated, product_id)) engine=InnoDB
Hibernate: create table timestamps (last_updated datetime(6) not null, success bit not null, time varchar(255), primary key (last_updated)) engine=InnoDB
Hibernate: alter table products add constraint FKo31ur4gpvx1d5720rgs3qaawi foreign key (product_list_last_updated) references timestamps (last_updated)

编辑 2:下面是 ProductListRepository 类的@Query,我包含在与删除相关的查询中。

@Repository
public interface ProductListRepository extends CrudRepository<ProductList, Integer>
    @Modifying
    @Query(value = "DELETE FROM Timestamps list WHERE list.last_updated !=0", nativeQuery = true)
    void deleteOld();

【问题讨论】:

您如何发布删除声明:DELETE FROM Timesta...?直接在您的 SQL 编辑器中?或者,在@Query?或者是其他东西?如果您尝试使用对应的产品存储库删除它会发生什么情况:例如:productRepository.delete(productsToBeDeleted) @Rafa 好吧,我已经尝试了这两种方法,最终目标是让它与 Query 注释一起运行,但我一直在使用 MySQL shell 对其进行测试,因为如果我可以让它工作那么在存储库类中使用它应该没问题。我也试过 productRepository.deleteAll();它确实级联了删除,但是为什么它可以工作,但指定使用 Query 注释并将其直接输入 MySQL shell 不起作用? @IDKWhatImDoing ,您想要实现的方式似乎很疯狂。为什么要在删除日志时删除产品???如果在删除产品时删除日志,我会更有意义。没有感觉仍然存储已删除产品的日志。 【参考方案1】:

(警告:我不知道设计师的想法。)

对我来说,自动删除数据很可怕。我的一点点误会可能会导致我没想到的数据被删除。

当然,在精心设计的教科书式架构中,数据将具有完美的结构,应该清楚哪些要删除,哪些不应该删除。

但教科书模式仅限于教科书。

【讨论】:

虽然在级联删除中射中自己的脚可能是其他关系的问题,例如@OneToMany 这里在删除父级时级联删除是唯一合乎逻辑的过程,因为集合没有父元素的嵌入元素没有意义,因为它们是父元素的一部分。类似于通常使用 @Embedded 将单个类嵌入到实体中的方式,会将子字段的列嵌入到父表中并删除工作,这里的想法是相同的,它只需要一个不同的表用于一对多的喜欢关系【参考方案2】:

有几个变量在起作用。 @ElementCollection 有一些限制。见:https://en.wikibooks.org/wiki/Java_Persistence/ElementCollection

使用 ElementCollection 而不是 OneToMany 的限制 是目标对象不能被查询、持久化、合并 独立于它们的父对象。他们严格 私有(依赖)对象,与嵌入式映射相同。 ElementCollection 上没有级联选项,目标对象 总是与它们的父级保持、合并、删除。 ElementCollection 仍然可以使用 fetch 类型并默认为 LAZY 与其他集合映射相同。

它按预期工作,因为 productRepository.deleteAll() 有效。

为什么不能使用原生查询? 因为原生查询是“按原样”执行的,这意味着它不会考虑实体中的注释。

另外,因为它是用@ElementCollection 注释的,所以你的constraint foreign key 中没有包含任何ON DELETE CASCADE

也就是说,这个alter table... 没有ON DELETE CASCADE

alter table products add constraint FKo31ur4gpvx1d5720rgs3qaawi foreign key (product_list_last_updated) references timestamps (last_updated)

建议的解决方案 1

使用预期的级联选项从 @ElementCollection 更改为 @OneToMany

建议的解决方案 2

删除 nativeQuery = true 并改用 JPA 查询。它应该类似于:

@Modifying
@Query("DELETE FROM ProductList list WHERE list.lastUpdated != 0")
void deleteOld();

建议的解决方案 3

ProductListRepository 中使用 Spring Data 命名查询。比如:

deleteByLastUpdatedNot(Date date);
or 
deleteByLastUpdatedLessThan(Date date);

【讨论】:

正如您在回答中所述,alter table 没有ON CASCADE DELETE,但这是问题的症结所在。为什么它不会自动应用级联?正如引用中所说的“目标对象总是与其父对象一起持久化、合并、删除”,但是如果它不级联删除,它如何与父对象一起删除子对象?这似乎不必要地限制了您与数据库的交互方式。 至于解决方案,解决方案 1 是我最终可能不得不使用的,尽管这是一种解决方法,我更愿意继续使用 @ElementCollection 注释。解决方案 2 不起作用,它遇到与本机查询相同的错误,这是有道理的,因为使用非本机查询只是编写查询的一种更好的方式,但它最终会被处理为我正在使用的本机查询,因为那是它可以运行的唯一方法。最后一个也可以,但它有其自身的局限性,我更愿意使用@Query 指定我自己的查询 嗨@IDKWhatImDoing,ON CASCADE DELETE 是createc 以及级联选项,例如:@OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)。哼,我不知道Solution2 在@ElementCollection 上不起作用。我个人觉得 JPA 非常固执己见,有时甚至死板。对于高灵活性JDBC Template 是您编写自己的查询的好选择。 Option2 是批量删除。 HIbernate 不会为可嵌入集合实体生成 delete-sql-statements。这就是问题所在。 productRepository.deleteAll() 为每个元素生成 1 个 delete-sql 语句。这是非常低效的。由于所有这些限制,我看不到 @ElementCollection 比 @ManyToOne 有任何好处。【参考方案3】:

在 mysql shell 中,您无法执行查询,因为您没有在 mysql 中设置外键 ON DELETE CASCADE。将 ON DELETE CASCADE 添加到您的外键将允许 mysql shell 在删除时级联。

日志一经删除就删除产品似乎不合逻辑。

我认为结构必须是这样的:

创建表products(id INT NOT NULL AUTO_INCREMENT, buy_price FLOAT, sell_price FLOAT, lastUpdateDate DATETIME, 主键 (id)); 创建表timestamps(idINT NOT NULL AUTO_INCREMENT,productINT NOT NULL,successBIT NOT NULL,updateDateDATETIME NOT NULL,主键(id),外键(@ 987654333@) 参考products(id) 删除级联);

所以现在当你删除一个产品时,它的日志就会被删除。

【讨论】:

以上是关于Java Spring级联元素集合删除的主要内容,如果未能解决你的问题,请参考以下文章

为啥手动定义的 Spring Data JPA 删除查询不会触发级联?

Spring data JPA:如何启用级联删除而不引用父级中的子级?

hibernate 级联删除

Spring认证-Spring注入集合

Spring入门第四课

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