Mapping One-To-Many Associations with Hibernate and JPA Annotations

Of all the mappings you’re going to see in a Java application, a one to many mapping has to be the most common. Just think about it, an Order has many LineItems; a Bank has many BankAccounts; a Record has many Songs; an Exam has many Questions. The one to many association really has to be the most common multiplicity we see in our Java apps.

For this tutorial, I’m going to model the idea that a Team has many Players. So, given a team, you will be able to obtain a List of the Players that are associated with that team. Furthermore, given a player, you can find out with which team that Player is associated. I guess you could say that it’s a bi-directional relationship, as the Player is navigable through the Team object, and the Team is navigable through the Player.

A Team Has Many Players

Since both the Team and Player objects will map to their own, individual, database tables, both the Team and Player classes will have their own primary key to keep track of a record’s uniqueness. In both classes, we will maintain the primary key field using a primitive of type long which will be uncreatively named id.

As far as the interesting properties of the Team class goes, well, every team has a name of type String. So, a team might be named the “Soo Greyhounds,” or the “Pickering Atoms.” As far as the Player class goes, every player has a nickName of type String, so “Lefty” and “Pork Chop might play for the Pickering Atoms.

Phase 1 of the Player Class (No Association Yet)

package com.examscam.mappings;

public class Player {

 private long id;
 private String nickName;

 public long getId() {return id;}
 public void setId(long id) {this.id = id;}
 public String getNickName() {return nickName;}
 public void setNickName(String n) {this.nickName = n;}
}

Phase 1 of the Team Class (No Association Yet)

package com.examscam.mappings;

public class Team {

 private long id;
 private String name;

 public long getId() {return id;}
 public void setId(long id) {this.id = id;}
 public String getName() {return name;}
 public void setName(String name) {this.name = name;}
}

Coding an Association in Java

Phase 1 of the Team and the Player classes define the basic properties of each object, without doing anything to imply the relationship between the two. Of course, the association between the two is the fact that a Player is associated with a Team, and a Team is associated with many Players. These relationships will be expressed in our Java code through instance variables.

To express the fact that a Player is associated with a Team, we need to add an instance variable of type Team in the Player class.

Phase 2 of the Player Class (Java Associations)

package com.examscam.mappings;
public class Player {
 private long id;private String nickName;

 private Team team; /* A Player is on a Team */
 public Team getTeam() {return team;}
 public void setTeam(Team t) {this.team = t;}

 public long getId() {return id;}
 public void setId(long id) {this.id = id;}
 public String getNickName() {return nickName;}
 public void setNickName(String n) {this.nickName = n;}
}

Phase 2 of the Team Class (Java Associations)

To express the fact that a Team has many players, we add a variable of type java.util.List, named players, to the Team class.

package com.examscam.mappings;
import java.util.List;
public class Team {
 private long id;private String name;

 private List<Player> players; /*A Team Has Players*/
 public List<Player> getPlayers() {return players;}
 public void setPlayers(List<Player> p){players=p;}

 public long getId() {return id;}
 public void setId(long id) {this.id = id;}
 public String getName() {return name;}
 public void setName(String name) {this.name = name;}
}

Adding the Standard Hibernate Annotations

Of course, instance variables only describe an association between classes at a Java bytecode level. Simply adding instance variables in your Java classes doesn’t give Hibernate any information about how to manage the associations. To have Hibernate persist and manage any associations between classes, those Java classes need to be annotated.

Both the Team and the player classes need the standard JPA annotations that describe the fact that they are to be Hibernate managed entities. We also need annotations that describe the fact that the id field will be the primary key whose values will be auto-generated by the database. That means adding the @Entity, @Id and @GeneratedValue JPA tags at the appropriate places in the Player and Team classes.

Phase 3 of the Player Class (Basic Annotations)

package com.examscam.mappings;
import javax.persistence.*;
@Entity
public class Player {
 private long id;
 private String nickName;

 private Team team;
 public Team getTeam() {return team;}
 public void setTeam(Team t) {this.team = t;}

 @Id @GeneratedValue
 public long getId() {return id;}
 public void setId(long id) {this.id = id;}
 public String getNickName() {return nickName;}
 public void setNickName(String n) {nickName = n;}
}

Phase 3 of the Team Class (Basic Annotations)

package com.examscam.mappings;
import java.util.List;
import javax.persistence.*;
@Entity
public class Team {
 private long id;
 private String name;

 private List<Player> players;
 public List<Player> getPlayers() {return players;}
 public void setPlayers(List<Player> p){players=p;}

 @Id @GeneratedValue
 public long getId() {return id;}
 public void setId(long id) {this.id = id;}
 public String getName() {return name;}
 public void setName(String name) {this.name = name;}
}

@OneToMany and @ManyToOne Annotations

After the basic JPA annotations identifying the Team and Player classes as managed entities have been added, along with the annotations to decorate the id fields of the two classes, it’s time to add the @OneToMany and @ManyToOne JPA annotations. These annotations help Hibernate understand the purpose of the instance variables that implement the Java based associations between the Team and Player classes.

Let’s first concentrate on the Player class that maintains a single instance variable of type Team to help identify the team with which a player is associated.

Since many players can be associated with a single team, the relationship is many-to-one, and as such, the getTeam() method in the Player class gets decorated with the @ManyToOne annotation.

Now, it is the responsibility of each Player to keep track of the single Team with which they are associated. In Java, this is achieved through an instance variable, but databases don’t have instance variables. Instead, databases use foreign keys in much the same way a Java program uses an instance variable. For a database, each row on the many side of a one to many relationship must maintain a foreign key that maps the row back to the associated row on the many side of the relationship.

So, each row in the Player table must define a column to map the Player back to the associated team. We’ll call that column, which will be defined in the player table, team_id.

It is worth mentioning that while the many side in a one to many relationship must define a field that maps back to the encapsulating one side, the reverse is not true. With the player and team tables, the player table (the many table) uses the team_id column to map back to the team table (the one table), but no column is needed in the team table to map to the player.

@JoinColumn and @ManyToOne Annotations

When using JPA annotations to map the many side of a relationship to the encapsulating one side, we not only use the @ManyToOne annotation, but we further decorate the class with the @JoinColumn annotation.

Again, the player database table must define a column to map rows back to the encapsulating team table, and this fact manifests itself through the name attribute of the very important @JoinColumn annotation. These decorations will appear immediately before the getTeam() method of the Player class:

@Entity
public class Player {
  @ManyToOne
  @JoinColumn(name="team_id")
  public Team getTeam() {return team;}
}

Of Databases and Java Programs

So, from the perspective of a Java program, the only thing that is needed to express a relationship between classes is the existence of an instance variable; but since databases don’t quite work the same way, we need to inform the Hibernate framework as to how it should manage the association at the database layer. This is where the @OneToMany and @JoinColumn JPA annotations come in.

@OneToMany JPA Annotation

The getTeam() method in the Player class was decorated with the @ManyToOne JPA annotation, so it’ll probably come as no surprise to find out that the Team side of this bi-directional relationship requires a @OneToMany annotation decorating the getPlayers() method.

public class Team {
@OneToMany(mappedBy="team", targetEntity=Player.class, fetch=FetchType.EAGER, cascade=CascadeType.ALL)
public List<Player> getPlayers() {return players;}
}

The @OneToMany annotation has a few important attributes that need to be included. First of all, you need to have the mappedBy attribute. And what is the mappedBy attribute? Well, this actually maps to the variable name the encapsulating class(Team) takes in the encapsulated entity Player). So, if you looked at the Player class, you’d see that there is an instance variable of type Team named team, in all lower case letters. This variable named team maps the Player back to their Team, and as such, becomes the value of the mappedBy attribute.

public class Player {
  private long id;
  private String nickName;

/* The Player class has an instance variable named team */

private Team team;
}

Along with the mappedBy attribute, the @OneToMany annotations needs a targetEntity attribute. Since any generic Object can be placed into a List, Vector, or other collection class that implements the many part of the association, it is important to explicitly state in the annotation what type of object class will be contained in the collection. This should be done even when the contained object type is defined using generics syntax. Since our team contains players, Player.class is the value of the targetEntity attribute of the Team’s @OneToMany annotation.

Final Phase of the Player Class

package com.examscam.mappings;

import javax.persistence.*;
@Entity
public class Player {
 private long id;
 private String nickName;
 private Team team;

 @ManyToOne @JoinColumn(name="team_id") 
 public Team getTeam() {return team;}
 public void setTeam(Team t) {this.team = t;}
 @Id
 @GeneratedValue
 public long getId() {return id;}
 public void setId(long id) {this.id = id;}
 public String getNickName() {return nickName;}
 public void setNickName(String n) {nickName = n;}
}

Final Phase of the Team Class

package com.examscam.mappings;

import java.util.List; import javax.persistence.*;
@Entity
public class Team {
 private long id;
 private String name;
 private List<Player> players; 

 @OneToMany(mappedBy="team", targetEntity=Player.class, fetch=FetchType.EAGER, cascade = CascadeType.ALL)
 public List<Player> getPlayers() {return players;}
 public void setPlayers(List<Player> p){players=p;}
 @Id
 @GeneratedValue
 public long getId() {return id;}
 public void setId(long id) {this.id = id;}
 public String getName() {return name;}
 public void setName(String name) {this.name = name;}
}

FetchType and CascadeType Revisited

Notice that the @OneToMany annotation uses the fetch and cascade attributes. While optional, it is always important to think about the potential negative impact of doing an eager fetch on associated classes. If loading associated classes will have a negative impact on performance, you might want to leave the fetch type to the default value of LAZY. Just be sure that with a lazy fetch setting that you don’t try and access associated classes outside of a transaction, as you will ended up getting a blank proxy, not the actual data, resulting in a very frustrating and often perplexing LazyInitialization runtime exception.

Furthermore, it is important to think about how saves to a containing object, such as Team, will effect the contained objects, in this case, the Players. If you want changes to the owning entity to cascade to the owned entity when a change occurs, you should set your CascadeType to ALL. Other available options for the CascadeType include: MERGE, PERSIST, REFRESH and REMOVE.

Testing the Implementation

Let’s test our one-to-many JPA annotated classes:

To test the @OneToMany and @ManyToOne mappings, you must first add both the Team and Player classes to the Hibernate configuration.

AnnotationConfiguration config = new AnnotationConfiguration();
config.addAnnotatedClass(Player.class);
config.addAnnotatedClass(Team.class);
config.configure();

After making sure the Player and Team classes are added to the configuration, you must make sure the corresponding tables actually exist in the database. I like to get Hibernate to do this for me by using my HibernateUtil.recreateDatabase() method, but you can also do it by running the following SQL script:

CREATE TABLE  'examscam'.'player'
('id' bigint(20) NOT NULL auto_increment,
'nickName' varchar(255) default NULL,
'team_id' bigint(20) default NULL,
PRIMARY KEY  ('id'),
KEY 'FK8EA387017289A91D' ('team_id'));

CREATE TABLE  'examscam'.'team'
 ( 'id' bigint(20) NOT NULL auto_increment,
 'name' varchar(255) default NULL,
 PRIMARY KEY  ('id'));

Testing the Team and Players

Because both the Team and Player classes are marked with the @Entity tags, any time a Team or Player class is created, it is a good practice to have the entity touch the Hibernate Session for its persistence to be managed.

Session session = HibernateUtil.beginTransaction();
Team team = new Team();
Player p1 = new Player();
Player p2 = new Player();
session.save(team); session.save(p1); session.save(p2);

After ‘touching’ the Hibernate Session, any subsequent changes, updates or associations that are made to the entity are managed by Hibernate and persisted to the database.

team.setName("Pickering Atoms");
p1.setNickName("Lefty");
p1.setTeam(team);
p2.setNickName("Blinky");
p2.setTeam(team);
HibernateUtil.commitTransaction();

After committing the transaction to the database, there are two players, Lefty and Blinky, who are both associated with the Pickering Atoms, giving them both a team_id of 1.

Full Team Class with the Testable main

package com.examscam.mappings;
import java.util.*; import javax.persistence.*;
import org.hibernate.Session; import com.examscam.HibernateUtil;
@Entity
public class Team {
 private long id;
 private String name;
 private List<Player> players; 

 @OneToMany(mappedBy="team", targetEntity=Player.class, fetch=FetchType.EAGER, cascade = CascadeType.ALL)
 public List<Player> getPlayers() {return players;}
 public void setPlayers(List<Player> p){players=p;}
 @Id
 @GeneratedValue
 public long getId() {return id;}
 public void setId(long id) {this.id = id;}
 public String getName() {return name;}
 public void setName(String name) {this.name = name;}
}
 public static void main(String args[]){
    HibernateUtil.recreateDatabase();
    Session session=HibernateUtil.beginTransaction();

    Team team = new Team();
    Player p1 = new Player();
    Player p2 = new Player();

    session.save(team); session.save(p1); session.save(p2);

    team.setName("Pickering Atoms");
    p1.setNickName("Lefty");
    p1.setTeam(team);
    p2.setNickName("Blinky");
    p2.setTeam(team);
    HibernateUtil.commitTransaction();
  }
}

Hibernate and SQL Execution

When committing the transaction to the database, Hibernate logs the following very interesting SQL statements, reflecting the flow of object creation and updates to the instance variables of the Player and Team classes as the code progresses.

Session session = HibernateUtil.beginTransaction();
Team team = new Team();
Player p1 = new Player();
Player p2 = new Player();
session.save(team);
session.save(p1);
session.save(p2);
team.setName("Pickering Atoms");
p1.setNickName("Lefty");
p1.setTeam(team);
p2.setNickName("Blinky");
p2.setTeam(team);
HibernateUtil.commitTransaction();

H: insert into Team (name) values (?) H: insert into Player (nickName, team_id) values (?, ?) H: insert into Player (nickName, team_id) values (?, ?) H: update Team set name=? where id=? H: update Player set nickName=?, team_id=? where id=? H: update Player set nickName=?, team_id=? where id=?

Looking at the results in the database, you can see that Lefty and Blinky are both players on the Pickering Atoms team. The single Team, the Pickering Atoms, has many players, namely Lefty and Blinky – making this a one to many, and inversely, a many to one, relationship mapping.

Mr Cameron McKenzie book ‘Hibernate made easy’, chapter 18

Advertisements

About lorddisk

WebCenter Content, WebCenter Portal, WebCenter Sites,Weblogic, Identity and Access Management (IAM),SSO,OAM,OIM,OAAM,OUD, OPAM,OID, OVD ,Oracle API Gateway ,OBIEE,OEDQ, Oracle ADF, Oracle SOA,J2EE, CackePHP ,PHP,J2SE,J2EE,Spring,Hibernate,JQuery,CSS,Java Script ,Joomla,Drupal,Worpress
This entry was posted in java. Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s