I have a Many-to-Many
relationship between the class Foo
and Bar
. 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
I created a Foo, and created some bars (saved to DB). When I then add one of the bars to the foo using
foo.addBar(bar); // adds it bidirectionally
barRepository.save(bar); // JpaRepository
then the DB-entry for FooBar is created - as expected.
But when I want to remove that same bar again from the foo, using
foo.removeBar(bar); // removes it bidirectionally
barRepository.save(bar); // JpaRepository
then the earlier created FooBar-entry is NOT deleted from the DB. With debugging I saw that the 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.
What I have tried:
adding orphanRemoval = true
on both @OneToMany - annotations, which did not work. And I think that's correct, because I don't delete neither Foo nor Bar, just their relation.
excluding CascadeType.REMOVE from the @OneToMany annotations, but same as orphanRemoval I think this is not for this case.
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
).
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
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. On 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.
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.
The FooRepositoryTest
class has the test case. It has a verify for the removal of the linking FooBar
, or it may just be easier to read the SQL that gets logged.
Edit
I've tested your scenario and did the following three modifications to make it work:
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.
Check that you already use operation persist
for you Entities Foo
(or FooBar
or Bar
).
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.