5

In a one-to-many relationship usualy the field annotated with @ManyToOne is the owner - the other side has 'mappedBy' attribute. However if I skip the 'mappedBy' and annotate both sides with @JoinColumn (same column) I can update both sides - changes are propageted to db.

I do not have two unidirectional relations instead of one bidirectional - there's just one join column.

What problems can I encounter by not having one side chosen as relations owner?

My entity looks similar to the following:

@Entity
public class B {
   @Id
   @GeneratedValue(strategy = GenerationType.IDENTITY)
   private Long id;


   @ManyToOne
   @JoinColumn(name = "parent_id")
   @Cascade({org.hibernate.annotations.CascadeType.SAVE_UPDATE})
   private B parent;

   @OneToMany()
   @JoinColumn(name = "parent_id")
   @Cascade({org.hibernate.annotations.CascadeType.SAVE_UPDATE})
   private List<B> children = new ArrayList<B>();
...
}

It doesn't seem to have any impact on performance (at least inserts look ok) Here is a simple test and log output:

Session session = HibernateUtil.getSessionFactory().openSession();
    session.beginTransaction();
    B a = new B("a");
    B b = new B("b");
    B c = new B("c");
    B d = new B("d");
    B e = new B("e");
    session.save(a);
    session.save(b);
    session.save(c);
    session.save(d);
    session.save(e);
    session.getTransaction().commit();
    System.out.println("all objects saved");
    session.beginTransaction();
    a.getChildren().add(b);
    a.getChildren().add(c);
    session.save(a);
    session.getTransaction().commit();
    System.out.println("b and c added as children");
    session.beginTransaction();
    a.getChildren().add(d);
    a.getChildren().add(e);
    session.getTransaction().commit();
    System.out.println("e and f added as children");
    session.close();

Hibernate: insert into B (fieldInB, parent_id) values (?, ?)
Hibernate: insert into B (fieldInB, parent_id) values (?, ?)
Hibernate: insert into B (fieldInB, parent_id) values (?, ?)
Hibernate: insert into B (fieldInB, parent_id) values (?, ?)
Hibernate: insert into B (fieldInB, parent_id) values (?, ?)
all objects saved
Hibernate: update B set parent_id=? where id=?
Hibernate: update B set parent_id=? where id=?
b and c added as children
Hibernate: update B set parent_id=? where id=?
Hibernate: update B set parent_id=? where id=?
e and f added as children

4 Answers4

3

You don't see the extra SQL statements because you do not properly set both sides of the association. For example, you say:

a.getChildren().add( b );

and assume that a and b are now associated. But what happens when you call b.getParent() here? Say this code "adds b as a child to a" and then returns this new child b and the caller wants to navigate b.getParent()?

Rather what you should be doing is:

a.getChildren().add( b );
b.setParent( a );

now your idiomatic Java code continues to work (b.getParent() behaves properly). Here, now, is where you will now see the multiple SQL statements. This is why you need to chose one side as an "owner".

Also, although totally bunk, consider:

a.getChildren().add( b );
b.setParent( c );

what gets written to the database here? Both sides effectively name a different parent for b. So which do we believe?

Steve Ebersole
  • 9,339
  • 2
  • 48
  • 46
0

I think this article might help, specifically what I have mentioned below:

What is “the thing” that makes Unidirectional not equal to the Bidirectional? When we had a Unidirectional relationship only the Customer class has a reference to the User class; you can only get a User by Customer.getUser() you can not do the other way. When we edit our User class we enabled the User to get the customer User.getCustomer().

You can check this question on SO too.

And this question looks quite helpful too.

The main differenece is that bidirectional relationship provides navigational access in both directions, so that you can access the other side without explicit queries. Also it allows you to apply cascading options to both directions.

There are lot more questions on SO on this topic. Those will be really helpful for you :)

Community
  • 1
  • 1
shazinltc
  • 3,616
  • 7
  • 34
  • 49
  • I get the difference between unidirectional and bidirectional. In the above case I have a bidirectional relationship - that's what I want - I mentioned the unidirectional relationships because that's what would happen if there wasn't the same column ( or no column at all) specified in the mappings. My problem is: what happens in a bidirectional relationship if one doesn't specify which side is the owner. – user1720310 Oct 04 '12 at 15:04
0

this will just be inefficient because saving B will issue

  • update object B
  • update all childrens parent reference to object B
  • cascade to all children of object B which update their parent reference

instead of

  • update object B
  • cascade to all children of object B which update their parent reference
Firo
  • 30,626
  • 4
  • 55
  • 94
  • I thought it may cause performance problems but it doesn't seem to be the case. – user1720310 Oct 04 '12 at 15:19
  • I conducted a simple test - see my question edit - and it doesn't seem to be the case - the number of updates is what I would expect – user1720310 Oct 04 '12 at 15:25
  • your test does not set the sides correctly, see Steve's answer. It is as i said. – Firo Oct 04 '12 at 18:08
  • yeah, I retested it with references correctly set on both sides and the outcome was just as you said in your answer I cannot upvote your answer though - I have not enough reputation to do it – user1720310 Oct 04 '12 at 18:20
0

Usually a different entity type is on each side (of one/many), but in this case a single entity type is on both sides. So when you say you are updating both sides, you are really only updating one side because there is only one entity (here, B)... Since this is an entity that is joined to itself, Hibernate knows what it's mapped by.

As for what problems you can encounter by not having one side chosen as relations owner: I tried this out just now in my own code (removing the "mappedBy" in the @OneToMany side of one of my entities), and at runtime got "java.sql.BatchUpdateException: failed batch". In short, you will get strange SQL exceptions from Hibernate not being able to properly map your classes.

Jason
  • 7,356
  • 4
  • 41
  • 48
  • It doesn't look to me as if hibernate picks one side as owner here. If I do someB.setParent(anotherB); and save someB - changes are visible in db. If I do someB.getChildren().add(someOtherB); and save someB - changes are also visible in db. (setParent(..) is a plain setter - I do not even set a reference to someB in anotherB's children list) – user1720310 Oct 04 '12 at 15:30
  • Hibernate does pick one side as the owner: B. Apparently for a self-join, this is ok. When I tried it for a non-self-join, it was not ok. – Jason Oct 04 '12 at 16:50
  • This is absolutely, unequivocally not true. Hibernate does not "pick" either side as an owner. In the absence of the user saying, Hibernate just assumes both sides are owners. – Steve Ebersole Oct 04 '12 at 18:10
  • Maybe saying it is picking owning sides is not true. What is true is the hibernate bombed out for me when the only thing I changed was removing the mappedby field. – Jason Oct 05 '12 at 23:02