休眠多对多连接类级联问题

时间:2018-09-21 11:30:43

标签: java hibernate jpa cascade hibernate-onetomany

我在类Many-to-ManyFoo之间有一种Bar关系。因为我想在帮助程序表上获得更多信息,所以我必须制作一个帮助程序类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);确实是双向删除的。没有异常。

我做错什么了吗? 我非常确定,这与级联选项有关,因为我只保存了该条。


我尝试过的:

  • 在两个@OneToMany上都添加了orphanRemoval = true-注释,该注释不起作用。而且我认为这是正确的,因为我既不删除也不是Foo也不是Bar,而是删除他们的关系。

  • 从@OneToMany批注中排除CascadeType.REMOVE,但与orphanRemoval相同,我认为这不适用于这种情况。


编辑:我怀疑我的代码或模型中一定有一些东西与我的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
}

3 个答案:

答案 0 :(得分:2)

我从示例代码中进行了尝试。通过几次“误入”,再现了错误。

结果确实很简单,就像添加您提到的orphanRemoval = true一样。在Foo.getFooBars()上:

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

将复制内容发布到GitHub似乎是最容易的-希望还有进一步的细微差别或我错过的地方。

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

FooRepositoryTest类具有测试用例。它具有删除链接FooBar的验证,或者可能更容易读取记录的SQL。


修改

这是下面的评论中提到的屏幕截图: deleteOrphans() breakpoint

答案 1 :(得分:1)

我已经测试了您的方案,并进行了以下三个修改以使其起作用:

  1. 从Foo和Bar的 @OneToMany getFooBars()方法中都添加了 orphanRemoval = true 。对于您的特定情况,在Foo中添加它就足够了,但是当您从栏中删除foo时,您可能也希望获得相同的效果。
  2. 将foo.removeBar(bar)调用封装在一个用Spring的 @Transactional 注释的方法中。您可以将此方法放在新的 @Service FooService 类中。
    原因:orphanRemoval需要一个活动的事务会话才能工作。
  3. 在调用 foo.removeBar(bar)之后,删除了对 barRepository.save(bar)的调用。
    现在这是多余的,因为在事务会话中,更改是自动保存的。

答案 2 :(得分:0)

  

Java持久性2.1。第3.2.3章

     

删除操作

     

•如果 X是新实体,则删除操作会将其忽略。   但是,删除操作会级联到X引用的实体,   如果从X到其他实体的关系用注释   级联=删除或级联=全部注释元素值。

     

•如果X为   受管实体,则删除操作会使它被删除。   如果删除操作被级联到X引用的实体,   X与其他实体的关系用   级联=删除或级联=全部注释元素值。

检查是否已为实体persist(或FooFooBar)使用操作Bar