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
This is the screenshot mentioned in a comment below:
I've tested your scenario and did the following three modifications to make it work:
Added orphanRemoval=true to both of the #OneToMany getFooBars() methods from Foo and Bar. 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.
Enclosed the foo.removeBar(bar) call inside a method annotated with Spring's #Transactional. You can put this method in a new #Service FooService class. Reason: orphanRemoval requires an active transactional session to work.
Removed call to barRepository.save(bar) after calling foo.removeBar(bar).
This is now redundant, because inside a transactional session changes are saved automatically.
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).
Related
I have two entities with one-to-one association. And some services that works with them in parallel. I need to delete one, but sometimes i have DataIntegrityViolationException, that as i understand means that i can't delete some entity while other has foreign key on it.
Here is my entities:
public class Foo{
#Id
#GeneratedValue
private Long id;
}
and:
public class Bar{
#Id
#GeneratedValue
private Long id;
#ManyToOne
private Foo foo;
}
Method that doesn't work (all repos extends from JpaRepository):
#Transactional
public void deleteFoo(Long fooId) {
barRepository.deleteAllByFooId(fooId);
fooRepository.deleteById(fooId);
}
And some method that works in parallel and breaks everything:
#Transactional
public void method(Long fooId) {
...
Foo foo = fooRepository.findById(fooId);
barRepository.save(new Bar(foo));
...
}
So i have ConstraintViolationException, as i understand because im trying to delete Foo but i have that new Bar(foo) in method that wasn't deleted by barRepository.deleteAllByFooId(fooId) in deleteFoo .
I need some approach like "delete method should wait until all current transactions finishes and then run only one transaction".
Can't use KeyLockManager because real structure of project already enough complicated and if i use it it should be same lock in many classes.
The problem is that you try to delete entity that being used in transaction. To close transaction you could use repository's save() method.
Also you should look at cascades and orphan removal. Use like this:
In Bar entity:
#ManyToOne(cascade = CascadeType.REMOVE, orphanRemoval = true)
private Foo foo;
And delete like so:
barRepository.save(bar);
or so:
I have the following scenario:
#Entity public class Foo {
#Id private Long id;
#OneToMany(mappedBy = "foo")
#MyCustomConstraint
private Set<Bar> bars;
}
#Entity public class Bar {
// ...
#ManyToOne(optional = false)
Foo foo;
}
I have autogenerated code (i.e. can't modify it) that creates a new Bar and adds it to an existing Foo by calling bar.setFoo(foo). setFoo makes sure the bar is added to foo's collection too. Then the autogenerated code calls persist(bar). At this point I need the custom constraint validator on foo.bars to be run (the newly added bar might violate it), but it isn't.
My questions:
Is this by design or am I doing something wrong?
What can I do to make it work?
Edit:
Some more information about the custom constraint - although I don't think that's particularly relevant to the question.
It's just a javax.validation custom constraint:
#javax.annotation.Constraint(validatedBy = MyCustomConstraintValidator.class)
#AllTheOtherAnnotationStuff
public #interface MyCustomAnnotation {
}
public class MyCustomConstraintValidator implements ConstraintValidator<MyCustomConstraint, Set /*<Bar>*/ .class> {
void initialize(MyCustomConstraint a) {}
boolean isValid(Set /*<Bar>*/ s, ConstraintValidatorContext ctx) { ... }
}
If I call entityManager.persist(foo), the constraint is validated. If I call entityManager.persist(bar), it is not, even though the bar was newly added to its Foo's collection.
The problem is that when you try to persist bar, you don't persist its foo automatically. You have to explicitly specify in the #ManyToOne annotation that you want to cascade the persist operation or you have to persist foo manually.
#ManyToOne(optional = false, cascade = CascadeType.PERSIST)
Foo foo;
I have the following entities:
#Entity
public class Foo {
#ManyToOne(optional = false) // I've tried #OneToOne also, same result
#JoinColumn(name = "bar_id")
private Bar bar;
// this is a business key, though not mapped as unique for legacy reasons
#Column(nullable = false)
private long fooNo;
// getters/setters + other properties
}
#Entity
public class Bar {
#OneToOne(optional = true, mappedBy = "bar", cascade = CascadeType.ALL)
private Foo foo;
// getters/setters + other properties
}
NOTE: This is the correct mapping: #ManyToOne and #OneToOne (it wasn't designed by me). I have tried #OneToOne on both sides also, with the same result.
Basically, I can have a Bar without a Foo object, but everytime I have a Foo, there has to be a Bar associated with it. Foo is considered the parent object (which is why its the owner of the association), but Bar can stand alone in certain cases.
I then load a Foo object like this:
SELECT f FROM Foo f WHERE f.fooNo = :fooNo
foo.getBar() correctly fetches the appropriate Bar, as expected. However, foo.getBar().getFoo() is null. It seems the other side of this relationship is not correctly initialized by JPA/hibernate. Any ideas why this is happening and how I can fix it?
I use Hibernate 3.2.1 as my JPA implementation, which we are using through EJB3 beans (though that is probably irrelevant).
Are you sure about your assotiation? ManyToOne usually implies oneToMany on other.
Merged with Referential integrity with One to One using hibernate.
I have following Entity -
#Entity
public class Foo {
#Id
private Long id;
#OneToOne(cascade = CascadeType.ALL, mappedBy = "foo")
private Bar bar;
// getters/setters omitted
}
#Entity
public class Bar{
#id
private Long id;
#OneToOne
#JoinColumn(name = "foo_id", nullable = false)
private Foo foo;
// getters/setters omitted
}
I kept the relation like this because I want to keep the Id of Foo in Bar table so I can have delete cascade constraints through SQL at DB end
Now this causes another issues -
If I change the reference of Bar in Foo then hibernate doesn't delete the existing Bar but adds the another entry.
I need to delete existing Bar explicitly before assigning new one for update.
What I would like to know is - can I achieve the same DB layout with Foo as a owning side, so If I assign the new Bar I'll just assign it and Hibernate will internally delete the existing not required entry.
I have the following annotated Hibernate entity classes:
#Entity
public class Cat {
#Column(name = "ID") #GeneratedValue(strategy = GenerationType.AUTO) #Id
private Long id;
#OneToMany(mappedBy = "cat", cascade = CascadeType.ALL, fetch = FetchType.EAGER)
private Set<Kitten> kittens = new HashSet<Kitten>();
public void setId(Long id) { this.id = id; }
public Long getId() { return id; }
public void setKittens(Set<Kitten> kittens) { this.kittens = kittens; }
public Set<Kitten> getKittens() { return kittens; }
}
#Entity
public class Kitten {
#Column(name = "ID") #GeneratedValue(strategy = GenerationType.AUTO) #Id
private Long id;
#ManyToOne(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
private Cat cat;
public void setId(Long id) { this.id = id; }
public Long getId() { return id; }
public void setCat(Cat cat) { this.cat = cat; }
public Cat getCat() { return cat; }
}
My intention here is a bidirectional one-to-many/many-to-one relationship between Cat and Kitten, with Kitten being the "owning side".
What I want to happen is when I create a new Cat, followed by a new Kitten referencing the Cat, the Set of kittens on my Cat should contain the new Kitten. However, this does not happen in the following test:
#Test
public void testAssociations()
{
Session session = HibernateUtil.getSessionFactory().getCurrentSession();
Transaction tx = session.beginTransaction();
Cat cat = new Cat();
session.save(cat);
Kitten kitten = new Kitten();
kitten.setCat(cat);
session.save(kitten);
tx.commit();
assertNotNull(kitten.getCat());
assertEquals(cat.getId(), kitten.getCat().getId());
assertTrue(cat.getKittens().size() == 1); // <-- ASSERTION FAILS
assertEquals(kitten, new ArrayList<Kitten>(cat.getKittens()).get(0));
}
Even after re-querying the Cat, the Set is still empty:
// added before tx.commit() and assertions
cat = (Cat)session.get(Cat.class, cat.getId());
Am I expecting too much from Hibernate here? Or is the burden on me to manage the Collection myself? The (Annotations) documentation doesn't make any indication that I need to create convenience addTo*/removeFrom* methods on my parent object.
Can someone please enlighten me on what my expectations should be from Hibernate with this relationship? Or if nothing else, point me to the correct Hibernate documentation that tells me what I should be expecting to happen here.
What do I need to do to make the parent Collection automatically contain the child Entity?
It won't automatically add it. You have to add it yourself.
I wouldn't directly call Kitten.setCat() either. The typical pattern for this is to put a method in Cat like:
public void addKitten(Kitten kitten) {
if (kittens == null) {
kittens = new HashSet<Kitten>();
}
kittens.add(kitten);
kitten.setCat(this);
}
and then simply call:
cat.addKitten(kitten);
When working with bi-directional associations, you have to handle both sides of the "link" and it is very common to use defensive link management methods for that, as suggested by #cletus. From Hibernate Core documentation:
1.2.6. Working bi-directional links
First, keep in mind that Hibernate
does not affect normal Java semantics.
How did we create a link between a
Person and an Event in the
unidirectional example? You add an
instance of Event to the collection of
event references, of an instance of
Person. If you want to make this link
bi-directional, you have to do the
same on the other side by adding a
Person reference to the collection in
an Event. This process of "setting
the link on both sides" is absolutely
necessary with bi-directional links.
Many developers program defensively
and create link management methods to
correctly set both sides (for example,
in Person):
protected Set getEvents() {
return events;
}
protected void setEvents(Set events) {
this.events = events;
}
public void addToEvent(Event event) {
this.getEvents().add(event);
event.getParticipants().add(this);
}
public void removeFromEvent(Event event) {
this.getEvents().remove(event);
event.getParticipants().remove(this);
}
The get and set methods for the
collection are now protected. This
allows classes in the same package and
subclasses to still access the
methods, but prevents everybody else
from altering the collections
directly. Repeat the steps for the
collection on the other side.
More References
1.2.6. Working bi-directional links (this one is the more obvious)
6.3.2. Bidirectional associations
Chapter 21, Example: Parent/Child
Java Persistence with Hibernate