Hibernate Association

Hibernate 1-to-many or many-to-many unidirectional association

Hibernate 1-to-many or many-to-many unidirectional association Java Entity Class

A Hibernate Java Entity Class

  • a hibernate Entity (Student) with a many-to-many relationship with a second Entity (Teacher)
  • a hibernate Entity (Student) with a 1-to-many relationship with a second Entity (Phone)
    public class Student {
    
        private Set<Teacher> teachers = new HashSet();       // Demonstrate many-to-many association
        private Set<String> phones = new HashSet();      // Demonstrate 1-to-many association
    
        public Set getTeachers() {
            return teachers;
        }
        public void setTeachers(Set<Teacher> teachers) {
            this.teachers = teachers;
        }
        public Set getPhones() {
            return phones;
        }
        public void setPhones(Set<String> phones) {
            this.phones = phones;
        }
    }

Hibernate Many-to-many Mapping

The corresponding Hibernate mapping file

project/src/main/resources/com/innotrekker/app/hbm/Teacher.hbm.xml
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
        "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
        "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">

<hibernate-mapping package="com.innotrekker.app.hbm">
    <class name="Student" table="students">
        ...
      <set name="teachers" table="student_teacher">
          <key column="student_id"/>
          <many-to-many column="teacher_id" class="Teacher"/>
      </set>
        ...
    </class>

</hibernate-mapping>
  • The students table are joined with the teachers table through an associate table student_teacher
    • student_teacher table contains the column student_id referencing the students table and teacher_id referencing the teachers table

Sample code to add an associated object in Hibernate (associate a teacher to a student)

Session session = HibernateUtil.getSessionFactory().getCurrentSession();
session.beginTransaction();

Student s = (Student) session.load(Student.class, aSId);
Teacher t = (Teacher) session.load(Teacher.class, aTId);
s.getStudents().add(t);
session.getTransaction().commit();
  • Hibernate will insert a new row to the associate table student_teacher to map a teacher to a student
  • Hibernate's automatic dirty checking detects data or association change
  • As long as an object is bind to a Hibernate session (in persistent state), all changes will be saved on commit without explicit calls to save() or update()

Hibernate 1-to-many association

<class name="Student" table="students">
    ...
    <set name="phones" table="st_phones">
        <key column="s_id"/>
        <element type="string" column="phone_number"/>
    </set>
    ...
</class>
  • element indicates the table is not an associate table. Instead it holds the data
    • st_phones is a table containing the phone information for all students
  • s_id is a column in the st_phones table referencing the students table
  • phone_number is a column in the st_phones table containing the phone number

To add a new Phone number to the st_phones table with association to a student

s.getPhones().add('4155551212');

Hibernate bidirectional association

The above section associates a student to a teacher. This section adds another association from a teacher to a student. This create a bi-directional relationship between a student and a teacher

Create a Java Entity Class (Teacher)

public class Teacher {

    private Set students = new HashSet();
    public Set getStudents() {
        return students;
    }
    public void setStudents(Set students) {
        this.students = students;
    }
}

To create a bi-directional association

<class name="Teacher" table="teachers">
  ...
  <set name="students" table="student_teacher" inverse="true">
     <key column="teacher_id"/>
     <many-to-many column="student_id" class="Student"/>
  </set>

Hibernate inverse flag

For bi-directional association,

s.getTeachers().add(t);
t.getStudents().add(s);

session.getTransaction().commit();
  • Saving a session will trigger both the Student Entity and Teacher Entity to add a new row in the student-teacher association table
  • Hence, we need to inform Hibernate whether the Student or Entity class is responsible for persisting the association information (instead of both side doing the same duplicated work)

In Teacher XML mapping

<class name="Teacher" table="teachers">
  ...
  <set name="students" table="student_teacher" inverse="true">
     <key column="teacher_id"/>
     <many-to-many column="student_id" class="Student"/>
  </set>
  • inverse="true" declares the opposite side, Student Entity, maintain and update the student-teacher association
  • The class with inverse="false" (Student) owns the association. When Student is saved, it will also insert a row to student_teacher table.
  • The class with inverse="true" (Teacher) do not persist the association to the DB
  • By default, inverse is false in Hibernate
Student student = (Student) session.load(Student.class, 1234556);
   Teacher t = (Teacher) session.load(Teacher.class, 285066);
   student.getTeachers().add(t);

   session.getTransaction().commit();
  • When "student" is saved, inverse="false" triggers a new row added to the association table (student_teacher)
  • if inverse="true", it will NOT trigger Student to save any association table information

For a one-to-many association set inverse="true" on the many-side, and for many-to-many association, select either side.

Define many-to-one association with @ManyToOne Hibernate Annotation

Using a foreign reference key in a table (item) to join the other table

@Entity
public class Item implements Serializable {

    @ManyToOne( cascade = {CascadeType.PERSIST, CascadeType.MERGE} )
    @JoinColumn(name="order_id", nullable=false)
    public Order getOrder() { ... }

    ...

}
  • @JoinColumn defines the column name in Item used to join with the associated objects

Join table by specify the columns used to join tables

@Entity
class Item {
   @ManyToOne
   @JoinColumns({
      @JoinColumn(name="o_name", referencedColumnName="orderName"),
      @JoinColumn(name="o_code", referencedColumnName="orderCode")
   })
   Order order;
}

Using an association table to join 2 tables

@Entity
public class Item implements Serializable {

    @ManyToOne( cascade = {CascadeType.PERSIST, CascadeType.MERGE} )
    @JoinTable(name="order_item",
        joinColumns = @JoinColumn(name="order_id"),
        inverseJoinColumns = @JoinColumn(name="item_id")
    )
    public Order getOrder() { ... }

    ...

}
  • joinColumns is the column name to join the associated entity
  • inverseJoinColumns is the column name to join this entity

Use a unique key to join the table

<property name="orderReferenceId" unique="true" type="string" column="SERIAL_NUMBER"/>
<many-to-one name="order" property-ref="orderReferenceId" column="o_reference_id"/>

Define one-to-many association with @OneToMany Hibernate Annotation

Use the many side (one-to-many) to manage the relation

@Entity
public class Customer implements Serializable {
    @OneToMany(cascade=ALL, mappedBy="customer")
    public Set<Order> getOrders() { return orders; }
    ...
}

public class Order implements Serializable {
    @ManyToOne
    @JoinColumn(name="c_id", nullable=false)
    public Customer getCustomer() { return customer; }
    ...
}
  • mappedBy is equivalent to the "inverse=true" flag
    • The current object Customer will not maintain (or own) the association relationship

Use the "one" side (one-to-many) to manage the relation (Order manage the relationship)

@Entity
public class Order {
    @OneToMany
    @JoinColumn(name="order_id")
    public Set<Item> getItems() { ... }
    ...
}

@Entity
public class Item {
    @ManyToOne
    @JoinColumn(name="order_id", insertable=false, updatable=false)
    public Order getOrder() { ... }
}
  • insertable means whether the column will be part of the insert statement
  • updatable means whether the column will be part of the update statement

Define one-to-one association with @OneToOne Hibernate Annotation

One-to-one association sharing the same primary key value

@Entity

public class Listing {
    ...
    @OneToOne(cascade = CascadeType.ALL)
    @MapsId
    public ListingAddress getListingAddress() {...}
    ...
}
  • Listing and ListingAddress use the same value in their primary key
  • ListingAddress can be lazily loaded if Hibernate knows the association is always present. To indicate that, use @OneToOne(optional=false).

Define the join columns in one-to-one mapping

@OneToOne
@JoinColumns({
      @JoinColumn(name="...", referencedColumnName="..."),
      @JoinColumn(name="...", referencedColumnName="...")
})

Define Many-to-Many association with @ManyToMany Hibernate Annotation

@Entity
public class Teacher implements Serializable {
    @ManyToMany(
        targetEntity=Student.class,
        cascade={CascadeType.PERSIST, CascadeType.MERGE}
    )
    @JoinTable(
        name="student_teacher",
        joinColumns=@JoinColumn(name="teacher_id"),
        inverseJoinColumns=@JoinColumn(name="student_id")
    )
    public Collection getStudents() {
        ...
    }
    ...
}

@Entity
public class Student implements Serializable {
    @ManyToMany(
        cascade = {CascadeType.PERSIST, CascadeType.MERGE},
        mappedBy = "students",
        targetEntity = Teacher.class
    )
    public Collection getTeachers() {
        ...
    }
}

Hibernate Map

Define a one-to-many association stored in a Java Map

@Entity
public class Order {
   @Id @GeneratedValue public Integer getId() { return id; }
   public void setId(Integer id) { this.id = id; }
   private Integer id;

   @OneToMany(mappedBy="order")
   @MapKey(name="itemNumber")
   public Map<String,Item> getItems() { ... }
   public void setItems(Map<String,Item> item) { ... }
   private Map<String,Item> items;
}

@Entity
public class Item {
   @Id @GeneratedValue public Integer getId() { return id; }
   public void setId(Integer id) { this.id = id; }
   private Integer id;

   public String getItemNumber() { ... }
   public void setItemNumber(String number) { ... }
   private String itemNumber;

   @ManyToOne
   public Order getOrder() { ... }
   public void setOrder(Order order) { ... }
   private Order order;
}

Using @MapKeyColumn for the mapping

@OneToMany(mappedBy="order")
@MapKeyColumn(name="item_number")
public Map<String,Item> getItems() { ... }
  • @MapKeyColumn define the column used for the Map

Collection of Simple Type

Collect the associated data and store to a collection class

@Entity
public class User {
   @ElementCollection
   @CollectionTable(name="alias", joinColumns=@JoinColumn(name="item_id"))
   @Column(name="alias")
   public Set<String> getAlias() { ... }
}

Hibernate Cascade

cascade defines what operations can be automatically propagated to the associated entities

  • For example, when a Hibernate persistent entity object is deleted, the CascadeType.REMOVE will also remove all the associated objects automatically
    @Entity
    public class Student implements Serializable {
        @ManyToMany(
            cascade = {CascadeType.PERSIST, CascadeType.MERGE},
Cascade Option Description
CascadeType.PERSIST Cascades the persist operation to the associated entities. By default this is off, saving the parent will not save the associated objects
CascadeType.MERGE Cascades the merge operation to the associated entities
CascadeType.REMOVE Cascades the remove operation - delete()
CascadeType.REFRESH Cascades the refresh operation - refresh()
CascadeType.DETACH Cascades the detach operation - detach()
CascadeType.ALL all of the above

Other Hibernate annotations

Hibernate Order by

Order by a Entity's property. Returned Hibernate query result list will be sorted by price

@OneToMany(mappedBy="order")
@OrderBy("price")
public List<Items> getItems() { ... }

Ordery by using DB column name

@OrderColumn(name="o_price")

Delete an entity will trigger the associated objects to be deleted also

@OnDelete(action=OnDeleteAction.CASCADE)
public Order getOrder() { ... }

Ignore exception if the associated entity is not found

@NotFound(action=NotFoundAction.IGNORE)
public Set<Item> getItems() { ... }

Define the foreign key name used for the DB Schema

@ForeignKey(name="FK_PARENT")
public Order getOrder() { ... }

Specify a column that is unique and not null

@NaturalId
private String student_id;

Sort the Hibernate list with a custom comparator

@Sort(type = SortType.COMPARATOR, comparator = MyComparator.class)

General Fetching Strategy

Fetching strategy determines when associated objects are being fetched from the DB

Fetching Strategy Description
Join fetching Retrieve all data in one SELECT
Select fetching, Subselect fetching A second SELECT is used to retrieve the associated entity or collection
Batch fetching A single SELECT by specifying a list of primary or foreign keys

More Options in Hibernate Fetching Strategy

Hibernate also add more custom fetching strategy as belows

Fetching Strategy Description
Immediate fetching Association, collection or attribute is fetched immediately
Lazy collection A collection is fetched when it is accessed
Extra-lazy collection fetching Individual collection is accessed as needed
Proxy fetching A single valued association is fetched when a method other than ID is accessed
Lazy attribute A single valued association is fetched when it is accessed

By default, Hibernate uses lazy select fetching for collections and lazy proxy fetching for single-valued associations

To enable Join Fetching instead of the default

Using Hibernate XML mapping

<set name="item"
            fetch="join">
    <key column="order_id"/>
    <one-to-many class="Item"/>
</set

Using Session API

Order o = (Order) session.createCriteria(Order.class)
                .setFetchMode("items", FetchMode.JOIN)
                .uniqueResult();

Changing Fetching Strategy with Hibernate Annotation

Change to eager loading using Hibernate Fetching Profile

@Entity
@FetchProfile(name = "order-with-items", fetchOverrides = {
   @FetchProfile.FetchOverride(entity = Order.class, association = "items", mode = FetchMode.JOIN)
})
public class Order {
   @OneToMany
   private Set<Item> items;

}
session.enableFetchProfile( "order-with-items" );
Order Order = (Order) session.get( Order.class, orderId );

Lazy Loading on Properties

All properties are loaded eagerly. To skip some property

<class name="Order">
...
    <property name="detail" lazy="true"/>
...
</class>