使用自定义删除方法的 JPA 级联删除失败

JPA cascading delete fails with custom delete method

我 运行 在 spring 数据 jpa 中使用自定义删除方法时出错。基本上有一个包,里面装着物品,删除包的时候,里面的所有物品都要删除。

实体如下:

@Entity
@Table(name = "bag")
public class Bag {
    @Id private Long id;
    @Column("uid") private Long uid;
    @Column("name") private String name;

    @OneToMany(mappedBy = "bag", cascade = CascadeType.ALL, orphanRemoval = true)
    private List<Item> items;
}

@Entity
@Table(name = "item")
public class Item {
    @Id private Long id;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "bid", referencedColumnName = "id")
    private Bag bag;
}

和存储库:

@Repository
public interface BagRepository extends JpaRepository<Bag, Long> {
    Bag findByUidAndName(Long uid, String name);

    @Transactional
    @Modifying
    @Query(value = "DELETE FROM `bag` WHERE `uid` = :uid AND `name` = :name", nativeQuery = true)
    void deleteByUidAndName(@Param("uid") Long uid, @Param("name") String name);
}

当我调用 bagRepository.deleteByUidAndName(uid, name) 时,我从休眠中得到一个与外键约束相关的异常。设置 spring.jpa.show-sql=true 表明它不会在删除包之前先尝试删除项目。

但是,如果我调用 Bag bag = bagRepository.findByUidAndName(uid, name) 然后 bagRepository.deleteById(bag.getId()) 一切都很好。

我想知道自定义此删除方法有什么问题以及如何修复它。

如果通过 bagRepository.deleteById(bag.getId()) 删除实体,Hibernate 将从父实体删除到子实体,因为您在关系上定义了 cascade = CascadeType.ALL。当我们对目标实体执行某些操作时,相同的操作将应用于关联的实体。 逻辑在 Hibernate 中,不使用数据库级联。

    @OneToMany(mappedBy = "bag", cascade = CascadeType.ALL, orphanRemoval = true)
    private List<Item> items;

如果 bagRepository.deleteByUidAndName(uid, name) 您定义了要删除的本机查询。这意味着将忽略 Hibernate 逻辑并执行查询 as-is。在这种情况下,您直接使用数据库并通过本机删除记录 SQL 您需要在数据库级别定义 ON DELETE CASCADE 以具有类似的逻辑。

    @Query(value = "DELETE FROM `bag` WHERE `uid` = :uid AND `name` = :name", nativeQuery = true)
    void deleteByUidAndName(@Param("uid") Long uid, @Param("name") String name);

解决方法一,@OnDelete(action = OnDeleteAction.CASCADE)

如果您有 auto-generated table,您可以向关系添加 Hibernate-specific 注释 @OnDelete。在 table 生成期间 ON DELETE CASCADE 将应用于外键约束。
关系定义:

    @OneToMany(mappedBy = "bag", cascade = CascadeType.ALL, orphanRemoval = true)
    @OnDelete(action = OnDeleteAction.CASCADE)
    private List<Item> items;

自动生成的约束:

    alter table item 
       add constraint FK19sn210fxmx43i8r3icevbeup 
       foreign key (bid) 
       references bag 
       on delete cascade

实施:

import org.hibernate.annotations.OnDelete;
import org.hibernate.annotations.OnDeleteAction;

import javax.persistence.*;
import java.util.List;

@Entity
@Table(name = "bag")
public class Bag {
    @Id
    private Long id;
    @Column(name = "uid")
    private Long uid;
    @Column(name = "name")
    private String name;

    @OneToMany(mappedBy = "bag", cascade = CascadeType.ALL, orphanRemoval = true)
    @OnDelete(action = OnDeleteAction.CASCADE)
    private List<Item> items;
}

方案二,@JoinColumn 注释带外键 ON DELETE CASCADE
ON DELETE CASCADEItem 实体指定外键

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "bid", referencedColumnName = "id",
            foreignKey = @ForeignKey(
                    name="FK_ITEMS_ID",
                    foreignKeyDefinition = "FOREIGN KEY (ID) REFERENCES ITEM(BID) ON DELETE CASCADE"))
    private Bag bag;

实施:

import javax.persistence.*;

@Entity
@Table(name = "item")
public class Item {
    @Id
    private Long id;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "bid", referencedColumnName = "id",
            foreignKey = @ForeignKey(
                    name="FK_ITEMS_ID",
                    foreignKeyDefinition = "FOREIGN KEY (ID) REFERENCES ITEM(BID) ON DELETE CASCADE"))
    private Bag bag;
}

方案三,不使用原生查询
在这种情况下,将应用 Hibernate 逻辑。
像这样定义存储库:

@Repository
public interface BagRepository extends JpaRepository<Bag, Long> {
    Bag findByUidAndName(Long uid, String name);

    @Transactional
    @Modifying   
    void deleteByUidAndName(@Param("uid") Long uid, @Param("name") String name);
}

方案四,手动添加ON DELETE CASCADE到数据库
如果您的 table 不是 auto-generated,您可以手动将 ON DELETE CASCADE 添加到数据库。

    alter table item 
       add constraint FK_BAG_BID
       foreign key (bid) 
       references bag 
       on delete cascade