使用 join-class 级联问题休眠多对多

Hibernate Many-to-Many with join-class Cascading issue

我在 class FooBar 之间有 Many-to-Many 关系。因为我想获得有关助手 table 的更多信息,所以我不得不制作一个助手 class FooBar,如下所述:The best way to map a many-to-many association with extra columns when using JPA and Hibernate

我创建了一个 Foo,并创建了一些条形图(保存到 DB)。然后,当我使用

将其中一个栏添加到 foo 时
foo.addBar(bar);            // adds it bidirectionally
barRepository.save(bar);    // JpaRepository

然后创建了 FooBar 的数据库条目 - 正如预期的那样。

但是当我想再次从 foo 中删除同一个栏时,使用

foo.removeBar(bar);         // removes it bidirectionally
barRepository.save(bar);    // JpaRepository

那么较早创建的 FooBar 条目不会 从数据库中删除。 通过调试,我看到 foo.removeBar(bar); 确实双向删除了。没有抛出异常。

我是不是做错了什么? 我很确定它与级联选项有关,因为我只保存了栏。


我尝试过的:


编辑: 我怀疑我的代码或模型中一定有什么东西与我的 orphanRemoval 混淆,因为现在已经有 2 个答案说它有效(orphanRemoval=true).

最初的问题已得到解答,但如果有人知道什么可能导致我的 orphanRemoval 无法正常工作,我将非常感谢您的意见。谢谢


代码:Foo、Bar、FooBar

public class Foo {

    private Collection<FooBar> fooBars = new HashSet<>();

    // constructor omitted for brevity

    @OneToMany(cascade = CascadeType.ALL, mappedBy = "foo", fetch = FetchType.EAGER)
    public Collection<FooBar> getFooBars() {
        return fooBars;
    }

    public void setFooBars(Collection<FooBar> fooBars) {
        this.fooBars = fooBars;
    }

    // use this to maintain bidirectional integrity
    public void addBar(Bar bar) {
        FooBar fooBar = new FooBar(bar, this);

        fooBars.add(fooBar);
        bar.getFooBars().add(fooBar);
    }

    // use this to maintain bidirectional integrity
    public void removeBar(Bar bar){
        // I do not want to disclose the code for findFooBarFor(). It works 100%, and is not reloading data from DB
        FooBar fooBar = findFooBarFor(bar, this); 

        fooBars.remove(fooBar);
        bar.getFooBars().remove(fooBar);
    }

}

public class Bar {

    private Collection<FooBar> fooBars = new HashSet<>();

    // constructor omitted for brevity

    @OneToMany(fetch = FetchType.EAGER, mappedBy = "bar", cascade = CascadeType.ALL)
    public Collection<FooBar> getFooBars() {
        return fooBars;
    }

    public void setFooBars(Collection<FooBar> fooBars) {
        this.fooBars = fooBars;
    }
}

public class FooBar {

    private FooBarId id; // embeddable class with foo and bar (only ids)
    private Foo foo;
    private Bar bar;

    // this is why I had to use this helper class (FooBar), 
    // else I could have made a direct @ManyToMany between Foo and Bar
    private Double additionalInformation; 

    public FooBar(Foo foo, Bar bar){
        this.foo = foo;
        this.bar = bar;
        this.additionalInformation = .... // not important
        this.id = new FooBarId(foo.getId(), bar.getId());
    }

    @EmbeddedId
    public FooBarId getId(){
        return id;
    }

    public void setId(FooBarId id){
        this.id = id;
    }

    @ManyToOne
    @MapsId("foo")
    @JoinColumn(name = "fooid", referencedColumnName = "id")
    public Foo getFoo() {
        return foo;
    }

    public void setFoo(Foo foo) {
        this.foo = foo;
    }

    @ManyToOne
    @MapsId("bar")
    @JoinColumn(name = "barid", referencedColumnName = "id")
    public Bar getBar() {
        return bar;
    }

    public void setBar(Bar bar) {
        this.bar = bar;
    }

    // getter, setter for additionalInformation omitted for brevity
}

Java Persistence 2.1. Chapter 3.2.3

Operation remove

• If X is a new entity, it is ignored by the remove operation. However, the remove operation is cascaded to entities referenced by X, if the relationship from X to these other entities is annotated with the cascade=REMOVE or cascade=ALL annotation element value.

• If X is a managed entity, the remove operation causes it to become removed. The remove operation is cascaded to entities referenced by X, if the relationships from X to these other entities is annotated with the cascade=REMOVE or cascade=ALL annotation element value.

检查您是否已经为实体Foo(或FooBarBar)使用操作persist

我从示例代码中尝试了这一点。有几个 'sketchings in' 这重现了错误。

解决方案确实很简单,只需添加您提到的 orphanRemoval = true。在 Foo.getFooBars() 上:

@OneToMany(cascade = CascadeType.ALL, mappedBy = "foo", fetch = FetchType.EAGER, orphanRemoval = true)
public Collection<FooBar> getFooBars() {
    return fooBars;
}

似乎最容易 post 复制到 GitHub - 希望还有更细微的差别或我错过的东西。

这是基于 Spring Boot 和 H2 内存数据库,因此不应该在其他环境下工作 - 如果有疑问,请尝试 mvn clean test

FooRepositoryTestclass有测试用例。它有一个删除链接 FooBar 的验证,或者它可能更容易阅读被记录的 SQL。


编辑

这是下面评论中提到的截图:

我已经测试了您的方案并进行了以下三处修改以使其正常工作:

  1. orphanRemoval=true 添加到两个 @OneToMany getFooBars() 方法中Foo 和酒吧。对于您的特定场景,将它添加到 Foo 中就足够了,但是当您从 bar 中删除 foo 时,您可能想要同样的效果。
  2. 在用 Spring 的 @Transactional 注释的方法中包含 foo.removeBar(bar) 调用.你可以把这个方法放在一个新的@ServiceFooServiceclass中。
    原因:orphanRemoval 需要一个活动的事务会话才能工作。
  3. 在调用 foo.removeBar(bar) 后删除了对 barRepository.save(bar) 的调用。
    现在这是多余的,因为在事务会话中更改会自动保存。