Expressing Many-to-Many Relationships in Hibernate

As humans, we tend to think of the world in objects. For instance, my car has four tires and my body has a pair of arms and legs. Since everything around us is thought of as objects, then why would we want to write code that stores data relationally? The short answer is; we are forced to based on the way traditional databases store data.

Let’s say for instance we have a group of Students that wish to enroll in several University Courses. Storing the Students and Courses in a relational database is trivial, however, how do we express Students that are enrolled in Courses? This is a typical many-to-many relationship where any number of students can be enrolled in any number of courses.

In the world of databases, a single table is created for Students and another for Courses. Then, a third table is created that holds the students that are enrolled in each course. The Enrolled table is called a cross-reference table.

This poses a problem for object oriented languages such as Java, C#, and Ruby because, while we don’t mind creating separate Student and Course objects, creating a third Enrolled object is poor OO design and will create unnecessary complexities in our code.

The Code

This is a more programmer-friendly way to store Students, Courses, and the Enrollment of Students in Courses.

public class Student {

  private long sid;
  private String firstName;
  private String lastName;
  private double gpa;

  Student() {} //Default constructor

  public Student(String firstName, String lastName, double gpa) {
    this.firstName = firstName;
    this.lastName = lastName;
    this.gpa = gpa;
  }

  //Getters and Setters are mandatory for every class
  //that gets mapped using Hibernate
  public void setSid(long sid) {
    this.sid = sid;
  }

  public long getSid() {
    return this.sid;
  }

  public String getFirstName() {
    return firstName;
  }

  public void setFirstName(String firstName) {
    this.firstName = firstName;
  }

  public String getLastName() {
    return lastName;
  }

  public void setLastName(String lastName) {
    this.lastName = lastName;
  }

  public double getGpa() {
    return this.gpa;
  }

  public void setGpa(double gpa) {
    this.gpa = gpa;
  }
}
Course.java
public class Course {
  
  long cid;
  String name;
  Set students = new HashSet();

  public Course() {}

  public Course(String name) {
    this.name = name;
  }

  public long getCid() {
    return this.cid;
  }

  public void setCid(long cid) {
    this.cid = cid;
  }

  public String getName() {
    return name;
  }

  public void setName(String name) {
    this.name = name;
  }

  public Set getStudents() {
    return students;
  }

  public void setStudents(Set students) {
    this.students = students;
  }

  public void addStudent(Student student) {
    this.students.add(student);
  }
}

Now, when we need to perform an action on a Course all of the Student objects associated with that Course are passed along with it. This is a much better way of handling the issue of enrollment, but how does this data get stored back to the database? After all, it’s great that we can write code at the level of objects, but at the end of the day, all application data still needs to be stored back in a relational model.

Enter Hibernate

By using an Object-Relational Mapping (ORM) tool such as Hibernate, programmers can continue to write code in terms of objects and the ORM tool will worry about how to store the data in a relational model. In Hibernate, this can be achieved by creating mapping files that teach Hibernate how to store the data in an object.

Using Mapping Files

Continuing our Students->Courses example, let’s create the mapping files necessary to teach Hibernate how to store each object’s data.

<hibernate-mapping>

  <class name="example.hibernate.Student" table="students">

    <id name="sid" column="sid">
      <generator />
    </id>

    <property name="firstName" column="first_name" />
    <property name="lastName" column="last_name"/>
    <property name="gpa" column="gpa" />

  </class> 

</hibernate-mapping>
Course.hbm.xml
<hibernate-mapping>

  <class name="example.hibernate.Course" table="courses">

    <id name="cid" column="cid">
      <generator />
    </id>

    <property name="name" column="course_name" />

    <set name="students" table="list" lazy="true">
      <key column="cid"/>
      <many-to-many column="sid"/>
    </set>

  </class>

</hibernate-mapping>

See how the many-to-many relationship in the Course mapping file is expressed? This tells hibernate that a relationship exists between Courses and Students and also gives specifics on what the relationship is and how (and where) to store the relationship in the database.

Now, we can treat our objects as objects and teach our ORM tool how to persist the data back to the database.

Did you notice that I didn’t even start talking about Hibernate until the latter half of this article? ORM tools allow us to write code and not worry about how to store our application’s data back to the database.

Image From: Hibernate

Creative Commons License

What do you think?