Home > Software design >  Hibernate relationship confusion with uni and bidirectional
Hibernate relationship confusion with uni and bidirectional

Time:06-21

I have Spring Data JPA experience, but new for Hibernate and after looking at several tutorials and articles, now I am too confused with some points. Could you please clarify me about the following issues?

1. Regarding to OneToMany relationship, it is suggested to define this relationship in the table e.g. Country table instead of the related table e.g. Employee table with country_id map key. However, there are different kind of implementations for this and other relationships.

What does Uni or Bidirectional relationship mean in Hibernate?

Could you please give an example or suggest me a good example (except from documentation that also cunfused me) in order to understand all the relationships used in Hibernate?

2. For the ManyToMany relationship, some example use List<T> while some others use Set<T>, Collection<T>. So, what is the proper way to define multiple relationship entity?

3. There are also some unnecessary implementation e.g. @ManyToOne(fetch = FetchType.LAZY) or @JoinColumn(name = "book_id") because these are default implementation. So, as far as I see, there is no need to imply these default settings as they have no impact. Is that true?

CodePudding user response:

There are many options because there are many different use cases.

  1. Choosing between Bidirectional and uni has an impact on the Java Object mapping the entities, how the association is mapped on the database and which query Hibernate will use to load entities (or convert HQL queries). I'm not going into the details of everything - the documentation does already a great job at it - but if we take the following bidirectional association:

    @Entity(name = "Person")
    @Table(name="PERSON_TABLE")
    class Person {
    
      @Id    
      private Long id;
    
      @OneToMany(mappedBy = "person")
      private List<Phone> phones = new ArrayList<>();
    
    }
    
    @Entity
    @Table(name="PHONE_TABLE")
    class Phone {
    
      @Id    
      private Long id;
    
      @ManyToOne
      private Person person;
    
    }
    

    Hibernate will map it adding a column person_id on the table PHONE_TABLE.

    If instead we map it as a unidirectional one-to-many:

    @Entity(name = "Person")
    @Table(name="PERSON_TABLE")
    class Person {
    
      @Id    
      private Long id;
    
      @OneToMany(mappedBy = "person")
      private List<Phone> phones = new ArrayList<>();
    
    }
    
    @Entity
    @Table(name="PHONE_TABLE")
    class Phone {
    
      @Id    
      private Long id;
    
      // No many-to-one in this class
    
    }
    

    Hibernate creates an extra table Person_Phone (a bridge between PHONE_TABLE and PERSON_TABLE), that contains two columns person_id and phone_id.

  2. It depends what type of content you expect the association to contain and how you want it returned: does it have duplicates? Is it ordered? Do you want the result as they appear on the db or do you want to use a comparable on the collection? Which mapping are you using? List is usually a good generic option, but you might have better performances using another implementation in some cases, like when you many-to-many associations or when you want to load multiple associations eagerly. This question on stack overflow explains why one cannot load multiple lists eagerly and the documentation list all the available collections (there are many more articles on-line). Here's an excerpt from the docs:

    unidirectional bags are not as efficient when it comes to modifying the collection structure (removing or reshuffling elements).

    Because the parent-side cannot uniquely identify each individual child, Hibernate deletes all link table rows associated with the parent entity and re-adds the remaining ones that are found in the current collection state.

  3. @JoinColumn is optional, and it's usually necessary when users want to change some of the defaults. For example, when you want to change the column name mapping the association:

    @Entity
    class Book {
    
       // Default column name would be `author_id` but we changed it to `a_id`,
       // if you are fine with author_id, than `@JoinColumn` is not necessary
       @ManyToOne
       @JoinColumn(name = "a_id")
       Author author;
    }
    

    In regard to @ManyToOne(fetch = FetchType.LAZY), Hibernate cannot always fetch many-to-one associations lazily. So the default is EAGER. When Hibernate load an association lazily, it creates a proxy object of the expected class. The problem is that you could have code like this:

    Book book = session.find(Book.class, 5);
    if ( book.getAuthor() == null ) {
     // do something ...
    }
    

    If Hibernate creates a proxy every time, getAuthor() will never return null. So if hibernate can guess that the author exists without querying the associated table, it will load it lazily (for example, when one specifies that the association always exists), otherwise it will just query the associated table and create the object eagerly.

  • Related