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.
Related
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).
I have two entities that have a relation OneToOne, like this:
#Entity
#Table(name = "FOO")
Foo{
#OneToOne(fetch = FetchType.LAZY, mappedBy = "foo")
#Cascade({org.hibernate.annotations.CascadeType.ALL})
#JoinColumn(name = "BAR_ID")
private Bar bar;
// getters and setters
}
#Entity
#Configurable
#Table(name = "BAR")
Bar{
#OneToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "FOO_ID")
#Cascade({org.hibernate.annotations.CascadeType.ALL})
private Foo foo;
// getters and setters
}
In the Service layer, I make the connection by setting Bar in Foo:
Bar.setFoo(foo);
barDAO.saveOrUpdate(bar);
Wich saves the foo id in the Bar table. But the opposite doesn't happen. Is it possible for hibernate to save both ids making only one set? I thought this would be working already
You need to understand the relationships well first. As much I see here you might trying to have a bidirectional OneToOne relationship between Foo and Bar.
#Entity
#Table(name = "FOO")
Foo {
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "BAR_ID")
private Bar bar;
// getters and setters
}
#Entity
#Table(name = "BAR")
Bar{
#OneToOne(mappedBy = "bar")
private Foo foo;
// getters and setters
}
In the bidirectional association two sides of association exits –
owning and inverse. For one to one bidirectional relationships, the
owning side corresponds to the side that contains the appropriate
foreign key.
Here the owning side is the Foo and BAR_ID would be that foreign key. Having join column in both end doesn't make sense. And Relation will be cascaded from the Foo to Bar. And the inverse side is Bar here that needs to be annotated with mapped by value of the owning side reference.
Now if you set Bar object in Foo it will persist the Bar object along with the mapping with Foo. Doing the reverse thing doesn't make sense. isn't it ?
You are missing the opposite side of the relation.
If you say bar.setFoo(foo) then after you have to say foo.setBar(bar) or of course you can do this within the setFoo method as well.
Cascading means that it will trigger the operation on the relation, however in your case, the relation was unfinished as one side was missing.
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;
Inside a service class, I have a method that is called from a #Transactional method. I have verified that I have a transaction active at the point this code is called. I realize that I don't have a DA layer when I should, but I am working with a legacy application that makes doing things the 'right' way more of a hassle than it's worth at this point.
The mappings look like this:
public class Foo {
private String id;
private Bar bar;
#Id
#Column(name = "FOO_ID", unique = true, nullable = false, length = 16)
#GeneratedValue(generator = "blahIdSeq")
#GenericGenerator(name = "blahIdSeq",
strategy = "org.blah.CustomIdGenerator")
public String getId() {return id;}
#JoinColumn(name = "FOO_ID")
#OneToOne(fetch = FetchType.LAZY, optional = false)
public Bar getBar() { return bar; }
// SETTERS INCLUDED
}
public class Bar {
private String id;
private Foo foo;
#Id
#Column(name = "FOO_ID")
#GeneratedValue(generator = "someSeq")
#GenericGenerator(name = "someSeq",
strategy = "foreign",
parameters = {
#Parameter(name = "property", value = "foo")
})
public String getId() { return id; }
#OneToOne(fetch = FetchType.LAZY)
#PrimaryKeyJoinColumn(name = "FOO_ID")
public Foo getFoo() { return foo; }
// SETTERS INCLUDED
}
The method looks something like this:
public String createFoo(Foo foo) {
Session ses = getSessionFactory().getCurrentSession();
Bar bar = new Bar();
bar.setFoo(foo);
foo.setBar(bar);
ses.save(foo);
ses.save(bar);
System.out.println(foo.getId()); // yields the ID value set by generator
System.out.println(bar.getId()); // yields same ID value as above
ses.flush();
ses.refresh(foo);
}
Now, with org.hibernate.SQL logging set to DEBUG, I can see that the insert statements for both Foo and Bar are created, but the refresh after the flush is called throws a org.hibernate.UnresolvableObjectException: No row with the given identifier exists exception.
What could cause this? The database used is Oracle 11gR2.
UPDATE
I have narrowed my issue down to sessions. It seems that calling the currentSession.flush() is not writing the data to the database as expected for the refresh. If I comment out the rest of the method, it will commit at the end and everything will be in the database.
Doing the flush/refresh will not return the hydrated object, however, so I cannot use the database-populated values (set by column defaults) later on in my transaction. I also cannot split the transaction into multiple ones because I need to be able to rollback at any point in the method.
Any ideas as to why the flush is not giving me accessible data in the database?
ANOTHER UPDATE
I have moved a lot of code around just to try and isolate the issue, and I'm still having problems. I also got rid of the relationship between the two entities to try and handle everything manually, just to see if that would fix the problem. Considering all the comments from Steve, here's what I have now:
public class Foo {
private String id;
private Bar bar;
#Id
#Column(name = "FOO_ID", unique = true, nullable = false, length = 16)
#GeneratedValue(generator = "blahIdSeq")
#GenericGenerator(name = "blahIdSeq",
strategy = "org.blah.CustomIdGenerator")
public String getId() {return id;}
// SETTERS INCLUDED
}
public class Bar {
private String id;
private Foo foo;
#Id
#Column(name = "FOO_ID")
public String getId() { return id; }
// SETTERS INCLUDED
}
#Service('fooService')
#Transactional(readOnly = true)
class FooService {
#Autowired
SessionFactory sessionFactory // populated using Spring config:
// org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean
#Transactional(readOnly = false)
public void doSomeStuff(Foo fooToSave) {
sessionFactory.getCurrentSession().saveOrUpdate(fooToSave);
Bar bar = new Bar(fooToSave); // this populates the Bar.Id field
sessionFactory.getCurrentSession().saveOrUpdate(bar);
sessionFactory.getCurrentSession().flush();
sessionFactory.getCurrentSession().refresh(fooToSave); // exception thrown here
}
}
YET ANOTHER UPDATE
After quite a bit of playing around in Oracle-land to make sure that the SQL was running on the same session and the like, I've found the issue. Even though Hibernate is logging that the SQL bind variables are being set, they actually are not. Using Oracle 11gR2's V$SQL_BIND_CAPTURE, I was able to see using the SQL ID that was executed last (verified to be the insert statement) had 24 bind variables and not one of them ever had a value bound to it. Still not sure what's causing the values to be blank, but I am quite a bit closer to finding my answer. It has to be a problem with my mappings, which I cannot put here in entirety.
Being bind variables, I'm guessing that Oracle doesn't throw a fit about not being able to insert. JDBC typically just returns the number of rows inserted for an INSERT statement for verification, but I'm not sure exactly how the Hibernate abstraction handles this stuff. I am currently using Hibernate 3.6.10 -- upgraded from 3.6.5 to see if it might fix the issue. It didn't. :P
I'VE BEEN MISLEAD
Ignore that "YET ANOTHER UPDATE" section, above. The bind variables seem like they don't show up in the V$SQL_BIND_CAPTURE view until the transaction has been committed. Back to the drawing board.
ANOTHER REVISION - I SWEAR I'M GONNA GET BANNED
I decided to go back to basics. What have I changed since it was in a working state? Mostly mappings. A few service layer items were also changed, but it was mostly moving our Hibernate mappings from XML to annotations. So I took the same service method I've been playing with, commented out all the other stuff, and tried doing the very same thing as what I'm trying to do with Foo using another persistent object type. Guess what? That works. The only link that could be causing my heartache at this point is the mapping I have for Foo. I doubt my employer would like me to just throw full source up on SO, so I'll probably have to just figure this one out on my own. I will post the answer in some capacity when I finally figure it out.
SUCCESS! BUT I'M NOT SURE WHY...
Here's the code that was giving me trouble. Keep in mind that BAZ is a linking table that has a composite ID made up with an #Embeddable (just called "key" for this example), consisting of FOO_ID referencing a row in the FOO table and a STATE_ID referencing another table.
public class Foo {
// OTHER FIELDS INCLUDING IDs AND SUCH
private Baz bazOfDoom;
private Baz bazOfLight;
private Set<Baz> allTheBaz;
#OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.REFRESH)
#JoinColumns({
#JoinColumn(name = "FOO_ID", referencedColumnName = "FOO_ID", insertable = false, updatable = false, nullable = false)
#JoinColumn(name = "DOOM_ID", referencedColumnName = "STATE_ID", insertable = false, updatable = false, nullable = false)
})
public Baz getBazOfDoom() { return bazOfDoom; }
#OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.REFRESH)
#JoinColumns({
#JoinColumn(name = "FOO_ID", referencedColumnName = "FOO_ID", insertable = false, updatable = false, nullable = false)
#JoinColumn(name = "LIGHT_ID", referencedColumnName = "STATE_ID", insertable = false, updatable = false, nullable = false)
})
public Baz getBazOfLight() { return bazOfLight; }
#OneToMany(mappedBy = "key.foo", fetch = FetchType.LAZY, cascade = CascadeType.REFRESH)
public Set<Baz> getAllTheBaz() { return allTheBaz; }
}
I removed the cascades and it worked. I don't know why. Whoever can explain that will get the "correct answer" mark from me. :)
It seems that your object doesn't own an identifer for your object after saving it to database, leading thus to your exception when calling refresh().
Indeed, assume your database tables own primary key defined as auto-increment.So, when you save your first Foo object, primary key column is valued as: 1.
However, Hibernate has to be aware of this newly generated identifier after calling save() method !
The best way to do this is to expect Hibernate to reaffect the good identifier as soon as the object is saved into database.
Thus, you might miss this line within your entity classes in order to provide identifier automatically when object is saved in database:
#GeneratedValue(strategy = GenerationType.AUTO)
Of course, you don't have to autogenerate them and rather can manually precise it.
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.