Hibernate is a framework to make storing and retrieving persistent objects and their association to one another simpler. While this is generally true, there are aspects of Hibernate configuration and mapping that can be challenging to learn and understand. Perhaps, one of the toughest to teach people is that of the inverse setting on a bi-directional relationship mapping in a one-to-many or many-to-many association. Without an understanding of the objects, the association, and SQL needed to save them, people new to Hibernate often struggle with this setting.
In fact, the inverse attribute on an association mapping can be easily understood with a simple example. So to start, assume there are two entities: BallPlayer and Team. As anyone who has played team sports knows, a team has many players, but a player can be on only one team at anytime. Therefore, the relationship between BallPlayer and Team is a one-to-many relationship, and in this example it is bi-directional. That is, a BallPlayer knows the team he plays for, and the Team knows the players on it. The UML showing the BallPlayer and Team attributes and the ERD diagrams for Player and Team are shown below.
The Hibernate mapping files for both BallPlayer and Team are straightforward.
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"> <hibernate-mapping package="com.intertech.domain">
<class name="BallPlayer" table="Player" abstract="true">
<id name="id" access="field">
<generator class="sequence">
<param name="sequence">common_seq</param>
</generator>
</id>
<property name="name" />
<property name="dob" column="date_of_birth" />
<property name="uniformNumber" column="uniform_number" />
<many-to-one name="team" column="team_id" cascade="all"
class="com.intertech.domain.Team" not-null="true" />
</class>
</hibernate-mapping>
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="com.intertech.domain">
<class name="Team">
<id name="id" access="field">
<generator class="sequence">
<param name="sequence">common_seq</param>
</generator>
</id>
<property name="nickname" />
<property name="founded" />
<set name="players" inverse="true" cascade="all">
<key column="team_id" />
<one-to-many class="com.intertech.domain.BallPlayer" />
</set>
</class>
</hibernate-mapping>
Note the inverse="true" on the <set> element in the Team mapping. When used, the inverse attribute must be set on the collection (in this case the <set> on Team) side of the association rather than the side containing the <many-to-one> element (the BallPlayer in this case). In a many-to-many relationship, the inverse attribute can be placed on the collection element on either side.
OK, but what is this inverse attribute all about? Well, to understand it, examine some test code that exercises the BallPlayer to Team relationship.
Calendar dob = Calendar.getInstance();
dob.set(1942, Calendar.DECEMBER, 13);
BallPlayer fergie = new BallPlayer();
fergie.setName("Ferguson Jenkins");
fergie.setDob(dob);
fergie.setUniformNumber((short) 31);
SessionFactory sessionFactory = new Configuration().configure().buildSessionFactory();
Session session = sessionFactory.openSession();
Transaction transaction = session.beginTransaction();
Team cubs = (Team) session.get(Team.class, 1L);
cubs.addPlayer(fergie);
fergie.setTeam(cubs);
transaction.commit();
session.close();
In this Hibernate code, a new BallPlayer (fergie) is created and associated to an already persistent Team (cubs) object. Once the BallPlayer and Team are associated to one another and since Team is already persistent, cascading persistence causes the BallPlayer (fergie) and relationship to the Team (cubs) to be saved.
The SQL issued to the database (shown with show_sql and use_sql_comments all set to true in the Hibernate configuration) when the test code above executes is shown below.
Hibernate: /* load com.intertech.domain.Team */ select team0_.id as id1_0_, team0_.nickname as nickname1_0_, team0_.founded as founded1_0_ from Team team0_ where team0_.id=?
Hibernate: /* load one-to-many com.intertech.domain.Team.players */ select players0_.team_id as team5_1_, players0_.id as id1_, players0_.id as id0_0_, players0_.name as name0_0_, players0_.date_of_birth as date3_0_0_, players0_.uniform_number as uniform4_0_0_, players0_.team_id as team5_0_0_ from Player players0_ where players0_.team_id=?
Hibernate: call next value for common_seq
Hibernate: /* insert com.intertech.domain.BallPlayer */ insert into Player (name, date_of_birth, uniform_number, team_id, id) values (?, ?, ?, ?, ?)
However, when the inverse="true" is removed from the Team mapping file, look how the SQL changes.
Hibernate: /* load com.intertech.domain.Team */ select team0_.id as id1_0_, team0_.nickname as nickname1_0_, team0_.founded as founded1_0_ from Team team0_ where team0_.id=?
Hibernate: call next value for common_seq
Hibernate: /* insert com.intertech.domain.BallPlayer */ insert into Player (name, date_of_birth, uniform_number, team_id, id) values (?, ?, ?, ?, ?)
Hibernate: /* create one-to-many row com.intertech.domain.Team.players */ update Player set team_id=? where id=?
It might be hard to see at first, but one extra SQL statement got executed. Notice the extra SQL update statement that is called (the last line in italics)? What gives? Why the extra SQL? This is a result of the absences of the inverse attribute on the relationship mapping.
From a database perspective, since only the Player table holds the reference (foreign key) to the Team, only one insert is needed for the relationship to be persisted – an insert into the Player table. However, Hibernate does not detect this fact by default. In the persistence context, all Hibernate knows is that both persistent objects (the BallPlayer called fergie and the Team called cubs) have been created or modified and need to be persisted. Therefore, at the next point of synchronization (transaction commit in this case) with the database, Hibernate attempts to persist both. If inverse attribute is not set to true, Hibernate looks at Team and notices the new BallPlayer.
It inserts the new BallPlayer, and association from cubs-to-fergie. However, it then looks at the new BallPlayer and sees the relationship fergie-to-cubs. Not told that the relationship is the inverse of one it has already taken care of, it thinks an update might be necessary to save this new relationship. Thus the extra "update Player set team_id=? where id=?" SQL is issued.
So, the inverse=true attribute informs Hibernate of the existence of a bidirectional relationship which allows it to ignore the relationship from the Team to BallPlayer direction.
This raises an important issue in your management of Java objects and their association to each other. With inverse="true", Hibernate essentially ignores the association of Team to BallPlayer. Therefore, if you add a BallPlayer to Team, but do not handle the reverse association in your coding, the association is ignored!
// - forget to do this - aPlayer.setTeam(aTeam);
aTeam.addPlayer(aPlayer);
session.save(aTeam);
session.save(aPlayer);
Hibernate manages the persistence of Java objects and their associations to each other into the database, but only if they are physically there! For this reason, good Hibernate developers write methods to update both sides of an entity relationship when either side is updated.
public void addPlayer(BallPlayer aPlayer){
players.add(aPlayer);
aPlayer.setTeam(this);
}
If you still find the invert attribute and associations confusing, I encourage you to download the code for this blog post here and see if it makes more sense after you play with the setting in this simple example.
To learn more about Hibernate, consider taking Intertech's Complete Hibernate class.