简体   繁体   English

Hibernate具有连接类级联问题的多对多问题

[英]Hibernate Many-to-Many with join-class Cascading issue

I have a Many-to-Many relationship between the class Foo and Bar . 我在FooBar之间有Many-to-Many关系。 Because I want to have additional information on the helper table, I had to make a helper class FooBar as explained here: The best way to map a many-to-many association with extra columns when using JPA and Hibernate 因为我想获得有关辅助表的其他信息,所以我必须按照此处的说明创建一个辅助类FooBar使用JPA和Hibernate时,使用额外列映射多对多关联的最佳方法

I created a Foo, and created some bars (saved to DB). 我创建了一个Foo,并创建了一些条形图(保存到DB)。 When I then add one of the bars to the foo using 当我然后使用其中一个条添加到foo

foo.addBar(bar);            // adds it bidirectionally
barRepository.save(bar);    // JpaRepository

then the DB-entry for FooBar is created - as expected. 然后创建FooBar的DB条目 - 正如预期的那样。

But when I want to remove that same bar again from the foo, using 但是,当我想再次从foo中删除相同的栏时,使用

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

then the earlier created FooBar-entry is NOT deleted from the DB. 然后, 不会从数据库中删除先前创建的FooBar条目。 With debugging I saw that the foo.removeBar(bar); 通过调试我看到了foo.removeBar(bar); did indeed remove bidirectionally. 确实删除了双向。 No Exceptions are thrown. 没有异常被抛出。

Am I doing something wrong? 难道我做错了什么? I am quite sure it has to do with Cascading options, since I only save the bar. 我很确定它与Cascading选项有关,因为我只保存吧。


What I have tried: 我尝试过的:

  • adding orphanRemoval = true on both @OneToMany - annotations, which did not work. 在@OneToMany上添加orphanRemoval = true - 注释,这些都不起作用。 And I think that's correct, because I don't delete neither Foo nor Bar, just their relation. 我认为这是正确的,因为我不删除 Foo和Bar,只删除他们的关系。

  • excluding CascadeType.REMOVE from the @OneToMany annotations, but same as orphanRemoval I think this is not for this case. 从@OneToMany注释中排除CascadeType.REMOVE,但与orphanRemoval相同我认为这不适用于这种情况。


Edit: I suspect there has to be something in my code or model that messes with my orphanRemoval, since there are now already 2 answers who say that it works (with orphanRemoval=true ). 编辑:我怀疑在我的代码或模型中必须有一些与我的孤儿电影混淆的东西,因为现在已经有2个答案说它有效(使用orphanRemoval=true )。

The original question has been answered, but if anybody knows what could cause my orphanRemoval not to work I would really appreciate your input. 最初的问题已得到解答,但如果有人知道什么可能导致我的孤儿无法工作,我会非常感谢您的意见。 Thanks 谢谢


Code: Foo, Bar, FooBar 代码: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
}

I tried this out from the example code. 我从示例代码中尝试了这个。 With a couple of 'sketchings in' this reproduced the fault. 通过几个'草图',这再现了错误。

The resolution did turn out to be as simple as adding the orphanRemoval = true you mentioned though. 事实证明,解决方案就像添加你提到的orphanRemoval = true一样简单。 On Foo.getFooBars() : Foo.getFooBars()

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

It seemed easiest to post that reproduction up to GitHub - hopefully there's a further subtle difference or something I missed in there. 将这种复制发布到GitHub似乎最容易 - 希望还有一个微妙的差异或我错过的东西。

This is based around Spring Boot and an H2 in-memory database so should work with no other environment - just try mvn clean test if in doubt. 这是基于Spring Boot和H2内存数据库所以应该没有其他环境 - 如果有疑问,只需尝试mvn clean test

The FooRepositoryTest class has the test case. FooRepositoryTest类有测试用例。 It has a verify for the removal of the linking FooBar , or it may just be easier to read the SQL that gets logged. 它有一个验证删除链接FooBar ,或者它可能只是更容易读取记录的SQL。


Edit 编辑

This is the screenshot mentioned in a comment below: 这是下面评论中提到的屏幕截图: deleteOrphans()断点

I've tested your scenario and did the following three modifications to make it work: 我已经测试了你的场景,并进行了以下三个修改以使其工作:

  1. Added orphanRemoval=true to both of the @OneToMany getFooBars() methods from Foo and Bar. 在Foo和Bar的两个@OneToMany getFooBars()方法中添加了orphanRemoval = true For your specific scenario adding it in Foo would be enough, but you probably want the same effect for when you remove a foo from a bar as well. 对于您的特定情况,在Foo中添加它就足够了,但是当您从条形图中移除foo时,您可能也希望获得相同的效果。
  2. Enclosed the foo.removeBar(bar) call inside a method annotated with Spring's @Transactional . 将一个foo.removeBar(bar)调用封装在一个用Spring的@Transactional注释的方法中。 You can put this method in a new @Service FooService class. 您可以将此方法放在新的@Service FooService类中。
    Reason: orphanRemoval requires an active transactional session to work. 原因:orphanRemoval需要活动的事务会话才能工作。
  3. Removed call to barRepository.save(bar) after calling foo.removeBar(bar) . 删除调用barRepository.save(巴)调用foo.removeBar(巴)之后。
    This is now redundant, because inside a transactional session changes are saved automatically. 现在这是多余的,因为在事务会话内部会自动保存更改。

Java Persistence 2.1. Java Persistence 2.1。 Chapter 3.2.3 第3.2.3节

Operation remove 操作删除

• If X is a new entity, it is ignored by the remove operation . •如果X是新实体,则删除操作将忽略它 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. 但是,如果从X到这些其他实体的关系使用cascade = REMOVE或cascade = ALL annotation元素值进行注释,则删除操作会级联到由X引用的实体。

• If X is a managed entity, the remove operation causes it to become removed. •如果X是托管实体,则删除操作会导致其被删除。 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. 如果从X到这些其他实体的关系使用cascade = REMOVE或cascade = ALL注释元素值进行注释,则删除操作将级联到由X引用的实体。

Check that you already use operation persist for you Entities Foo (or FooBar or Bar ). 检查您是否已经为实体Foo (或FooBarBar )使用操作persist

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM