Unidirectional one-to-many using a Join Column – Hibernate

Did some splunking into a Unidirectional one-to-many Hibernate mapping using a join column as opposed to a join table.

As an example I have two entities:

  1. An Issue (which on a very high level represents a problem of some sorts)
  2. An IssueManagement (which represents an attempt at resolving the Issue in some way)
  • An IssueManagement can NOT exist without an Issue and as such seems to me well suited to using a foreign key (join column) on issue_management referring back to issue.
  • Here is the UML and database entity diagram for the relationship:

Setup:

  • Here is the sql I used for HSQLDB:
    CREATE TABLE issue (id INT GENERATED BY DEFAULT AS IDENTITY IDENTITY  NOT NULL, CONSTRAINT PK_ISSUE PRIMARY KEY (id))
    
    CREATE TABLE issue_management (id INT GENERATED BY DEFAULT AS IDENTITY IDENTITY  NOT NULL, issue_id INT NOT NULL, CONSTRAINT PK_ISSUE_MANAGMENT PRIMARY KEY (id))
    ALTER TABLE issue_management ADD CONSTRAINT issue_management_to_issue FOREIGN KEY (issue_id) REFERENCES issue(id)
  • Here are the pojos with the Hibernate mapping annotations:
    • Issue
      @Entity
      @Table
      public class Issue {
      	@Id @GeneratedValue(strategy=GenerationType.IDENTITY)
      	private Long id;
      
      	@OneToMany(fetch=FetchType.EAGER, cascade={CascadeType.ALL})
      	@JoinColumn(name="issue_id", nullable=false)
      	List<IssueManagement> issueManagements= new ArrayList<IssueManagement>();
      
      	public Long getId() {
      		return id;
      	}
      	public void setId(Long id) {
      		this.id = id;
      	}
      	public List<IssueManagement> getIssueManagements() {
      		return issueManagements;
      	}
      	public void addIssueManagment(IssueManagment issueManagment) {
      		issueManagements.add(issueManagment);
      	}
      }
    • IssueManagement
      @Entity
      @Table
      public class IssueManagement {
      	@Id @GeneratedValue(strategy=GenerationType.IDENTITY)
      	private Long id;
      
      	public Long getId() {
      		return id;
      	}
      	public void setId(Long id) {
      		this.id = id;
      	}
      }
  • Basic test code (details on setting up HibernateUtil ommitted)
    .......
                SessionFactory session = HibernateUtil.getSessionFactory();
                Session sess = session.getCurrentSession();
                Transaction tx = sess.beginTransaction();
                Issue issue = new Issue();
                issue.addIssueManagement(new IssueManagement());
                sess.save(issue);
                tx.commit();
    .......

Observations:

nullable=false is NOT optional if you constrain the foreign key to not null.

    • If you don’t this you’ll get a null constraint violation:
      ERROR org.hibernate.util.JDBCExceptionReporter - Attempt to insert null into a non-nullable column: column: ISSUE_ID table: issue_managment in statement [insert into issueManagement (id, city, postal_code, province, street, unit) values (null, ?, ?, ?, ?, ?)]
      could not insert: [hibernate.example.IssueManagment]
    • Notice that the issue_id column isn’t included in the insert statement. Here’s a comparison of the sql statements hibernate executes without or with nullable=false
      WITHOUT
      
      Hibernate:
          insert
          into
              issue_management
              (id)
          values
              (null)
      
      WITH nullable=false
      
      Hibernate:
          insert
          into
              issue_management
              (id, issue_id)
          values
              (null, ?)
    • WHY isn’t issue_id in the first sql?– because hibernate does the insert for issueManagement in two steps!
      • Which brings me to the next Observation…..

Hibernate persists the IssueManagement entities using two SQL statements instead of one!

    • When you map a unidirectional one-to-many relationship using a join column in hibernate it will persist the newly created issueManagement in two separate sql statements.
      • An insert
        insert
            into
                issue_management
                (id, issue_id)
            values
                (null, ?)
      • An update
        update
                issue_managment
            set
                issue_id=?
            where
                id=?
    • Why the update??
      • I have no idea but it seems to be acknowledged in the community as a bug of some sort.
      • Chris Richardson says on Page 204 of his book Pojos in Action:
        "First, Hibernate executes a SQL INSERT statement that inserts the MenuItem into the MENU_ITEM table. Then, for no obvious reason, Hibernate executes a SQL UPDATE statement that sets the MENU_ITEM_INDEX and RESTAURANT_ID foreign keys to the parent's row."

The Hibernate documentation STRONGLY recommends not using this mapping??

    • In the hibernate documentationthey recommend not mapping a unidirectional one-to-many using a join column. Here’s the quote:
      A unidirectional one to many using a foreign key column in the owned entity is not that common
      and not really recommended.
      We strongly advise you to use a join table for this kind of association (as explained in the next section).
      This kind of association is described through a @JoinColumn
    • That comment is rather vague but I believe it is not really a technical thing but more a modelling decision.
      • Here’s what they say in a later version of the documentation that’s likely what they are warning about:
        This mapping loses certain semantics of normal Java collections:
              An instance of the contained entity class cannot belong to more than one instance of the collection.
              An instance of the contained entity class cannot appear at more than one value of the collection index.
      • Personally, seems ok to me. Certainly for my example I don’t see why I should go with a join table.

It's only fair to share...
Share on FacebookGoogle+Tweet about this on TwitterShare on LinkedIn

Leave a Reply