Spring Data Repository无法删除ManyToOne实体。

41

我目前正在尝试使用Spring Data存储库来删除我的某些实体。删除调用没有任何异常/错误消息,但实体之后并未被删除。

这些是我的实体:

public class Board implements Serializable {

    @Id
    @GeneratedValue(generator = "uuid2")
    @GenericGenerator(name = "uuid2", strategy = "uuid2")
    @Column(columnDefinition = "BINARY(16)")
    private UUID uuid;

    @OneToMany(fetch=FetchType.EAGER, cascade=CascadeType.ALL, orphanRemoval = true, mappedBy = "board")
    private List<Post> posts = new ArrayList<Post>();
}

public class Post implements Serializable {

    @Id
    @GeneratedValue
    private long id;

    @ManyToOne(optional = false)
    @JoinColumn(name="board_uuid", updatable = false, nullable = false)
    @JsonBackReference
    private Board board;
}

这个存储库尽可能简单:

@Repository
public interface PostRepository extends CrudRepository<Post, Long> {
}

delete调用类似于

postRepository.delete(50);

你有什么想法,为什么这个更改没有反映在数据库中?

编辑1:

我找到了一种解决方法,但我还是不明白真正的问题所在。 如果我像这样删除帖子,它就“有效”了(由于约束违规,有几个例外情况,但帖子仍然被删除):

post.setBoard(null);
postRepo.delete(post);

编辑2:

当我查看执行的SQL语句时,我发现Hibernate甚至没有尝试删除。唯一发生的事情是这两个select语句:

Hibernate: select post0_.id as id1_1_0_, post0_.board_uuid as board_uu6_1_0_, post0_.content as content2_1_0_, post0_.x as x3_1_0_, post0_.y as y4_1_0_, post0_.z as z5_1_0_, board1_.uuid as uuid1_0_1_ from Post post0_ left outer join Board board1_ on post0_.board_uuid=board1_.uuid where post0_.id=?
Hibernate: select posts0_.board_uuid as board_uu6_0_0_, posts0_.id as id1_1_0_, posts0_.id as id1_1_1_, posts0_.board_uuid as board_uu6_1_1_, posts0_.content as content2_1_1_, posts0_.x as x3_1_1_, posts0_.y as y4_1_1_, posts0_.z as z5_1_1_ from Post posts0_ where posts0_.board_uuid=?

编辑 3

结果发现在帖子上的cascade=CascadeType.ALL似乎是问题所在。如果没有它,删除就能正常工作了(但是现在我错过了对帖子更改的级联)。


实际的代码更像是这样: public void delete(Post post) { postRepo.delete(post.getId()); } 所以,是的。 - Nitek
在OneToMany/ManyToOne或者JoinColumn注解中,您有不同的选项吗? - Nitek
可更新的 = 假 ... 不确定是否会引起问题。你尝试将其设置为真了吗? - Stefaan Neyts
是的,我做到了。当我从讨论板块中删除帖子并保存板块时,一切都很正常,但我想直接删除一个帖子... - Nitek
1
我也遇到了同样的问题。你找到解决方法了吗? - cosbor11
显示剩余4条评论
6个回答

84
问题似乎在于你使用了 cascade=CascadeType.ALL,这也包括了 CascadeType.PERSISTCascadeType.PERSIST 意味着子实体完全由父实体管理,您不能直接删除它。要删除它,只需从父级中删除即可。
你可以只添加其他 CascadeTypes 而不是全部,例如 CascadeType.REMOVE,如果你唯一想做的就是在删除父实体时同时删除子实体。

9
你知道是否有不抛出异常的原因吗?我的意思是,我试图删除某些约束条件下的内容,但无法完成。如果能够通知这种情况,那不是更好吗? - cesaregb
我遇到了类似的问题。在我的板子上,我只有一篇文章。我删除了这篇文章,那么Board中的List<Post>也为空了。但是后来,它并没有被删除,List<Post>仍然有这篇文章。是什么造成了这种差异?没有代码更改。 - Kisor Biswal

8
建立在上面用户2936091的出色答案之上,我想提到一个(相关的)解决方法,今天我偶然发现:如果父实体未被获取到Hibernate上下文中,则您可以直接删除它。
在我的情况下,这是通过在@ManyToOne关系上设置fetch = FetchType.LAZY来实现的。无论如何,我希望这种更改可以提高性能,并注意到没有急切获取父级时,Hibernate可以通过存储库方法调用自由地将其删除。

1

cascade=CascadeType.PERSIST 用于在双方更新插入,但对于删除操作,它将会限制。

@ManyToOne(cascade=CascadeType.PERSIST, fetch = FetchType.LAZY)
//  @JoinColumn(name = "qid")
    @JoinColumn(name = "qid", referencedColumnName = "qid", foreignKey = @ForeignKey(name = "qid"), nullable = false)
    // @JsonIgnore
    @JsonBackReference
    private QueueGroup queueGroup;

0
一个简单的替代方案:
@Repository
public interface PostRepository extends CrudRepository<Post, Long> {

@Modifying(clearAutomatically = true, flushAutomatically = true)
@Query(value = "DELETE FROM post WHERE id = ?1", nativeQuery = true)
void deleteById(long postId);

}

0
这是因为你在Post类上设置了mappedBy = "board",这样做告诉我们Post的主人是Board。

我已经在Board类中设置了它,所以如果我正确理解"mappedBy",我告诉它在Post中的"board"属性是主属性。 - Nitek
这是一篇不错的文章,讲解了Hibernate中的mappedBy属性。 - Stefaan Neyts

0

@user2936091 所说的是正确的,但它并没有解决问题。我也遇到了和你完全相同的问题。

解决方法是从父类中删除 fetch=FetchType.EAGER。当使用 EAGER 获取类型时,帖子对象中的 board 实例将会有

 private List<Post> posts = new ArrayList<Post>();

由于它被急切地加载,因此已经填充,这意味着它仍然具有对自身的引用,就像@user2936091所提到的那样。

我的解决方法仍然急切地加载相关属性是创建一个DBService类,并像这样从其中加载对象

@Service
public class DBService {

    BoardRepository boardRepo;

    @Transactional
    public Board getBoardProcess(Long id) {
       Board board = boardRepo.findById(id).get();
       board.getPosts().size(); // This will trigger the db query so that posts gets loaded
       // It needs to be done within a transactional so that the session is still the same to populate the fields. If it's done from outside of this method. It will not work
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接