Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is it necessary to distinguish one side of a bidirectional relationship in orm as an owning side?

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
like image 739
user1720310 Avatar asked Oct 22 '22 07:10

user1720310


1 Answers

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?

like image 97
Steve Ebersole Avatar answered Nov 10 '22 18:11

Steve Ebersole