Monday, January 23, 2012

Hibernate: Mapping Many-to-Many Associations

Overview:
This article will look at different ways of how you can map many-to-many associations in Hibernate.

Approach1: Mapping as many-to-one associations without an association entity.
Approach2: Mapping as many-to-one associations with an association entity as many-to-one.
Approach3: Mapping as many-to-one associations using composite-elements.

Approach1:
Mapping as many-to-one associations without an association entity. This approach is one of the most common ways of mapping a many-to-many association, there will be NO separate entity class to model the association between a Player and a Club.
Association Diagram
__________
|    Club         |
-----------------
       |*
       |
____|*____
|    Player     |
----------------
Class Diagram
___________
|Player             |
-------------------
|id:long            |
|name:String    |
|clubs:Set         |
-------------------
__________________
|Club                             |
------------------------------
|id:long                          |
|name:String                  |
|contractedPlayers:Set   |
------------------------------
Following is the database structure,
CREATE TABLE CLUB (
  club_id INTEGER NOT NULL,
  name VARCHAR(50),
  PRIMARY KEY (id)
);
CREATE TABLE PLAYER (
  player_id INTEGER NOT NULL,
  name VARCHAR(50),
  PRIMARY KEY (id)
);
CREATE TABLE CONTRACT (
  club_id,
  player_id,
  PRIMARY KEY (club_id, player_id),
  FOREIGN KEY (club_id) REFERENCES CLUB(club_id),
  FOREIGN KEY (player_id) REFERENCES PLAYER(player_id),
);  
Following is the hibernate mapping involved,
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
  <class name="org.fazlan.beans.Club">
     <id name="id" column="club_id">
        <generator class="increment" />
     </id>
     <property name="name" />
     <set name="contractedPlayers" table="contract" inverse="true">
        <key column="club_id" />
        <many-to-many class="Player" column="player_id"/>
     </set>
  </class>
  <class name="org.fazlan.beans.Player">
     <id name="id" column="player_id">
        <generator class="increment" />
     </id>
     <property name="name" />
     <set name="clubs" table="contract">
        <key column="player_id" />
        <many-to-many class="Club" column="club_id" />
     </set>
  </class>
</hibernate-mapping>
However, with this approach, we cannot model data specific to the association with the contract(e.i: start-date and end-date of the contract). The next approach will look at how we can model such many-to-many associations.

Approach2:
Mapping as many-to-one associations with an association entity. With this way, we can easily model additional data related to the association. This is achieved by having an entity class to model the association as many-to-one.

Association Diagram
_______________
|    Club       |
-----------------
|
______|*_______
|    Contract   |
-----------------
|*
______|________
|    Player     |
-----------------

Class Diagram
_______________
|Player         |
-----------------
|id:long        |
|name:String    |
-----------------
_______________
|Club           |
-----------------
|id:long        |
|name:String    |
-----------------
______________________________
|Contract                      |
--------------------------------
|contract_id:long              |
|club:Club                     |
|player:Player                 |
|startDate:Date                |
|endDate:Date                  |
--------------------------------
Here, the association between a Player and a Club will be represented using an association entity, namely Contract. Following is the database structure,
CREATE TABLE CLUB (
  club_id INTEGER NOT NULL,
  name VARCHAR(50),
  PRIMARY KEY (id)
);
CREATE TABLE PLAYER (
  player_id INTEGER NOT NULL,
  name VARCHAR(50),
  PRIMARY KEY (id)
);
CREATE TABLE CONTRACT (
  contract_id INTEGER NOT NULL,
  club_id,
  player_id,
  start_date Date,
  end_date Date,
  PRIMARY KEY (contract_id),
  FOREIGN KEY (club_id) REFERENCES CLUB(club_id),
  FOREIGN KEY (player_id) REFERENCES PLAYER(player_id),
); 
Following is the Hibernate mapping involved,
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
  <class name="org.fazlan.beans.Club">
     <id name="id" column="club_id">
        <generator class="increment" />
     </id>
     <property name="name" />
  </class>
  <class name="org.fazlan.beans.Player">
     <id name="id" column="club_id">
        <generator class="increment" />
     </id>
    <property name="name" />
  </class>
  <class name="org.fazlan.beans.Contract">
     <id name="id" column="contract_id">
        <generator class="increment" />
     </id>
     <property name="startDate" column="start_date"/>
     <property name="endDate" column="end_date"/>
     <many-to-one name="club" column="club_id" not-null="true" />
     <many-to-one name="player" column="player_id" not-null="true" />
  </class>
</hibernate-mapping>
Alternatively, you can model the many-to-many relationship using an composite-element. The next section looks at this.

Approach3:
Mapping as many-to-one associations using composite-elements.
Association Diagram
_______________
|    Club       |
-----------------
    |
______|*_______
|    Contract   |
-----------------
    |*
______|________
|    Player     |
-----------------

Class Diagram
_______________
|Player         |
-----------------
|id:long        |
|name:String    |
-----------------
_______________
|Club           |
-----------------
|id:long        |
|name:String    |
|contracts:List |
-----------------
______________________________
|Contract                      |
--------------------------------
|id:long              |
|player:Player                 |
|startDate:Date                |
|endDate:Date                  |
--------------------------------
Following is the database structure,
CREATE TABLE CLUB (
  club_id INTEGER NOT NULL,
  name VARCHAR(50),
  PRIMARY KEY (id)
);
CREATE TABLE PLAYER (
  player_id INTEGER NOT NULL,
  name VARCHAR(50),
  PRIMARY KEY (id)
);
CREATE TABLE CONTRACT (
  contract_id INTEGER NOT NULL,
  club_id,
  player_id,
  start_date Date,
  end_date Date,
  PRIMARY KEY (contract_id),
  FOREIGN KEY (club_id) REFERENCES CLUB(club_id),
  FOREIGN KEY (player_id) REFERENCES PLAYER(player_id),
);
Following is the Hibernate mapping involved,
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
 "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
  <class name="org.fazlan.beans.Club">
     <id name="id" column="club_id">
        <generator class="increment" />
     </id>
     <property name="name" />
     <list name="contracts" table="contract">
        <key column="club_id" />
        <index-column column="contract_id" />        
        <composite-element class="org.fazlan.beans.Contract">
             <property name="startDate" column="start_date"/>
             <property name="endDate" column="end_date"/>
             <many-to-one name="player" column="player_id">
        </composite-element>
     </list>
  </class>
  <class name="org.fazlan.beans.Player">
     <id name="id" column="player_id">
        <generator class="increment" />
     </id>
     <property name="name" />
  </class>
</hibernate-mapping>
Summary:
This article looked at how you can map many-to-many associations in different ways depending on the data you need to capture.

No comments:

Post a Comment