Question

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
Was it helpful?

Solution

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?

OTHER TIPS

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 :)

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

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.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top