My experiments with technology

Investigating Hibernate Associations – Many to Many

September 25, 2007 · 33 Comments

In my previous posts, we have looked at one-to-one and one-to-many hibernate associations. This one is going to focus on many-to-many hibernate association.

Let’s start with an unidirectional many-to-many association and later move on to bi-directional association. We will continue to use the earlier examples of Person and Phone. Here’s the hibernate.cfg.xml file used for this example:

<hibernate-configuration>

    <session-factory>

        <!-- Database connection settings -->
        <property name="connection.driver_class">
		com.mysql.jdbc.Driver
	</property>
        <property name="connection.url">
		jdbc:mysql://localhost:3306/<dbname>
	</property>
        <property name="connection.username"><dbuser></property>
        <property name="connection.password"><dbpassword></property>

	<property name="transaction.factory_class">
		org.hibernate.transaction.JDBCTransactionFactory
	</property>

        <!-- JDBC connection pool (use the built-in) -->
        <property name="connection.pool_size">1</property>

        <!-- SQL dialect -->
        <property name="dialect">
		org.hibernate.dialect.MySQL5InnoDBDialect
	</property>

        <!-- Enable Hibernate's automatic session context management -->
        <property name="current_session_context_class">thread</property>

        <!-- Disable the second-level cache  -->
        <property name="cache.provider_class">
		org.hibernate.cache.NoCacheProvider
	</property>

        <!-- Echo all executed SQL to stdout -->
        <property name="show_sql">true</property>

        <!-- Drop and re-create the database schema on startup -->
        <property name="hbm2ddl.auto">update</property>
        <property name="hibernate.max_fetch_depth">1</property>

        <mapping resource="com/tutorial/hibernate/simple/Person.hbm.xml"/>
        <mapping resource="com/tutorial/hibernate/simple/Phone.hbm.xml"/>

    </session-factory>

</hibernate-configuration>

Here’s the CREATE TABLE scripts for PERSON, PHONE and PERSON_PHONE:

CREATE TABLE person (ID            varchar(255) NOT NULL,
                     FIRSTNAME     varchar(255),
                     LASTNAME      varchar(255),
                     DOB           date);
CREATE TABLE phone (ID                varchar(255) NOT NULL,
                    CONTACTNUMBER     varchar(10),
                    PHONETYPE         varchar(255));
CREATE TABLE person_phone (PERSONID     varchar(255),
                           PHONEID      varchar(255));

The hibernate configuration files for Person and Phone classes are as follows:

For Person

<hibernate-mapping package="com.tutorial.hibernate.simple">

    <class name="Person" table="PERSON" dynamic-update="true"
	dynamic-insert="true" select-before-update="false">
        <id name="id">
            <generator class="assigned"/>
        </id>
        <property name="firstName"/>
        <property name="lastName"/>
        <property name="dob" type="date" />

	<set name="phones" table="PERSON_PHONE" cascade="all">
		<key column="PERSONID"/>
		<many-to-many column="PHONEID" class="Phone"/>
	</set>

    </class>

</hibernate-mapping>

For Phone:

<hibernate-mapping package="com.tutorial.hibernate.simple">

    <class name="Phone" table="PHONE" dynamic-update="true"
	dynamic-insert="true" select-before-update="false">
        <id name="id">
            <generator class="assigned"/>
        </id>
        <property name="number" column="contactnumber"/>
        <property name="type" column="phonetype"/>
    </class>

</hibernate-mapping>

The Person class will have the following attributes id, firstName, lastName, dob with their getters and setters. Add the following attribute and appropriate getters and setters for the phone collection.

private Set phones = new HashSet();
public Set getPhones() {
	return phones;
}
public void setPhones(Set phones) {
	this.phones = phones;
}
public void addPhone(Phone phone) {
	this.phones.add(phone);
}

The Phone class will have the following attributes id, number, type and their getters and setters.

Here’s the sample test program for many to many relationship.

	Configuration cfg = new Configuration();
	cfg.configure();
	SessionFactory sessFactory = cfg.buildSessionFactory();
	try {
		Session session = sessFactory.openSession();

		Person p1 = new Person();
		p1.setId("1245");
		p1.setFirstName("Clark");
		p1.setLastName("Kent");
		p1.setDob(new java.util.Date());

		Person p2 = new Person();
		p2.setId("12456");
		p2.setFirstName("Bruce");
		p2.setLastName("Willis");
		p2.setDob(new java.util.Date());

		Phone phone1 = new Phone();
		phone1.setId("451");
		phone1.setNumber("1234567");
		phone1.setType("BUS");
		p1.addPhone(phone1);
		p2.addPhone(phone1);

		Phone phone2 = new Phone();
		phone2.setId("452");
		phone2.setNumber("7654321");
		phone2.setType("PER");
		p1.addPhone(phone2);
		p2.addPhone(phone2);

		Transaction tx = session.beginTransaction();
		session.save(p1);
		session.save(p2);
		tx.commit();

	} catch(Exception e) {
		e.printStackTrace();
	}

The many-to-many relationship definition in Person.hbm.xml, the set attribute is defined as follows:

<set name="phones" table="PERSON_PHONE" cascade="all">
	<key column="PERSONID"/>
	<many-to-many column="PHONEID" class="Phone"/>
</set>

The cascade=”all” ensures that the INSERT order is appropriate. I.e. the insertion order is in the following fashion PERSON, PHONE and PERSON_PHONE.

Here’s the SQL output generated:

Hibernate: select phone_.id, phone_.contactnumber as contactn2_2_,
	   phone_.phonetype as phonetype2_ from PHONE phone_
	   where phone_.id=?
Hibernate: select phone_.id, phone_.contactnumber as contactn2_2_,
	   phone_.phonetype as phonetype2_ from PHONE phone_
	   where phone_.id=?
Hibernate: insert into PERSON (firstName, lastName, dob, id)
	   values (?, ?, ?, ?)
Hibernate: insert into PHONE (contactnumber, phonetype, id)
	   values (?, ?, ?)
Hibernate: insert into PHONE (contactnumber, phonetype, id)
	   values (?, ?, ?)
Hibernate: insert into PERSON (firstName, lastName, dob, id)
	   values (?, ?, ?, ?)
Hibernate: insert into PERSON_PHONE (PERSONID, PHONEID) values (?, ?)
Hibernate: insert into PERSON_PHONE (PERSONID, PHONEID) values (?, ?)
Hibernate: insert into PERSON_PHONE (PERSONID, PHONEID) values (?, ?)
Hibernate: insert into PERSON_PHONE (PERSONID, PHONEID) values (?, ?)

Here’s how the data is inserted in the tables:

SELECT * FROM PERSON

ID      FIRSTNAME LASTNAME DOB
-----   --------- -------- -------
1245	Clark	  Kent	   2007-09-25
12456	Bruce	  Willis   2007-09-25

SELECT * FROM PHONE

ID      CONTACTNUMBER PHONETYPE
----    ------------- ---------
451	1234567	      BUS
452	7654321	      PER

SELECT * FROM PERSON_PHONE

PERSONID PHONEID
-------- -------
1245	 452
1245	 451
12456	 452
12456	 451

Moving on, let’s get a bi-directional many-to-many association going. Add the following in the Phone.hbm.xml

<set name="persons" inverse="true" table="PERSON_PHONE">
       	<key column="PHONEID"/>
       	<many-to-many column="PERSONID"
           class="Person"/>
</set>

Add the following code in the Phone class:

private Set persons = new HashSet();

public Set getPersons() {
	return persons;
}
public void setPersons(Set persons) {
	this.persons = persons;
}

Add the following test code in our sample code after the tx.commit() line.

	Transaction tx1 = session.beginTransaction();
	Person per = (Person)session.load(Person.class, "12456");
	System.out.println(per.getPhones().size());
	tx1.commit();

Now we have bi-directional relationship working. the attribute definition inverse=”true” informs Hibernate that Person class is responsible for maintaining the relationship.

In my next post I intend do a deep dive in understanding the cascade attribute. Until the next post.




Categories: Hibernate

33 responses so far ↓

  • Investigating Hibernate Associations - Cascading Styles « My experiments with technology - My oasis in the desert of Project Management // October 28, 2007 at 6:53 am | Reply

    [...] with the various cascading styles. We will use the many-to-many relationship defined in my previous post as [...]

  • sushant // July 8, 2008 at 1:33 pm | Reply

    I am new for hibernate and this is very helpful. And this is very easy to understand.

  • Takao Kimura // July 28, 2008 at 5:25 pm | Reply

    Great!! Thank´s…
    Finally something easy to understand

  • kukumber // September 17, 2008 at 2:14 pm | Reply

    Hi,
    I am following the same procedure for a sample app.But data’s are not inserting in the PERSON_PHONE table.but other tables data’s are inserting..

    Please hepl me out where i am doing mistake.

    thanks
    partha

  • Jose // September 17, 2008 at 3:10 pm | Reply

    Great post! Thanks for sharing.

    One question: If PERSON already has 3 PHONE entries in PERSON_PHONE and in code I retrieve that PERSON, I retrieve the PHONE collection and add 1 new phone to it, hibernate executes a DELETE on ALL PERSON_PHONE entries and then does 4 inserts. Is there a way to prevent this behavior and have hibernate only do 1 insert for the new phone?

    Thanks in advance for your help.

  • Jose // September 17, 2008 at 3:13 pm | Reply

    kukumber,

    Do you have the cascade option set to either save-update or all (as the example on the Person class mapping)?

  • kukumber // September 18, 2008 at 7:35 am | Reply

    YES,I am using cascade=all inverse=true

    Thanks
    partha

  • Dinakar // October 13, 2008 at 4:58 am | Reply

    Thanks a ton !!!…. this is the best all-under-one-roof example for many-to-many mapping …… It saved me soo much time….. keep posting such good articles. I also want to know how the mapping would chance if I used ArrayLists to hold objects instead of sets …

  • Mr. President // October 14, 2008 at 6:16 am | Reply

    Jose, sorry for the delay in response. I do not have this setup available with me. Will try out your issue some time soon.

  • Mr. President // October 14, 2008 at 6:17 am | Reply

    Dinakar, thanks for your comment. Honestly I have not tried using an ArrayList instead of a set, so I do not have any idea, but I guess it should work without any issues.

  • Dave // October 18, 2008 at 4:19 am | Reply

    Excellent post on hibernate relations. Very easy to follow. Most examples I have seen usually use more complicated classes which obscure the objective of the example.

  • Ankit singh rathore // November 4, 2008 at 6:12 am | Reply

    hi this is realy a very good info abt many to many relation thanks i never saw this type live example in so many sites
    realy thanks.

  • Mr. President // November 18, 2008 at 10:14 am | Reply

    This is a really delayed response. This is in response to a query from Jose. I am unable to replicate the scenario you mentioned. I have added the following piece of code before the exception catch block in my test code.

    Phone phone3 = new Phone();
    phone3.setId(“4521″);
    phone3.setNumber(“8888888″);
    phone3.setType(“PER”);
    p2.addPhone(phone3);

    tx = session.beginTransaction();
    session.save(p2);
    tx.commit();

    The output generated is:

    Hibernate: select phone_.id, phone_.contactnumber as contactn2_2_, phone_.phonetype as phonetype2_ from PHONE phone_ where phone_.id=?
    Hibernate: select phone_.id, phone_.contactnumber as contactn2_2_, phone_.phonetype as phonetype2_ from PHONE phone_ where phone_.id=?
    Hibernate: insert into PERSON (firstName, lastName, dob, id) values (?, ?, ?, ?)
    Hibernate: insert into PHONE (contactnumber, phonetype, id) values (?, ?, ?)
    Hibernate: insert into PHONE (contactnumber, phonetype, id) values (?, ?, ?)
    Hibernate: insert into PERSON (firstName, lastName, dob, id) values (?, ?, ?, ?)
    Hibernate: insert into PERSON_PHONE (PERSONID, PHONEID) values (?, ?)
    Hibernate: insert into PERSON_PHONE (PERSONID, PHONEID) values (?, ?)
    Hibernate: insert into PERSON_PHONE (PERSONID, PHONEID) values (?, ?)
    Hibernate: insert into PERSON_PHONE (PERSONID, PHONEID) values (?, ?)
    Hibernate: select phone_.id, phone_.contactnumber as contactn2_2_, phone_.phonetype as phonetype2_ from PHONE phone_ where phone_.id=?
    Hibernate: insert into PHONE (contactnumber, phonetype, id) values (?, ?, ?)
    Hibernate: insert into PERSON_PHONE (PERSONID, PHONEID) values (?, ?)

    Jose, is this an appropriate test case or am I doing something wrong?

  • Srikanth // November 26, 2008 at 7:59 pm | Reply

    Hi friends,

    I already read four books on hibernate but gained very little, confused, frustrated and lost the hope on hibernate.

    But this example gave me lot of hope and confidence.

    My sincere appriciations and thanks to author..

    I am thankfull if he continues his valuable examples like this on every topic in hibernate.

  • duongtrung // December 24, 2008 at 5:36 am | Reply

    It’s what i need now!
    Thanks so much. ^-^

  • moiaz // January 20, 2009 at 9:34 am | Reply

    xcellent article dude…
    keep posting such articles..
    we need guys like u

    cheers !!!
    mOIAz

  • renuka // February 4, 2009 at 11:40 am | Reply

    Thanks a lot….. Very usefull article… Keep dng same.

  • SJohn // February 9, 2009 at 7:10 pm | Reply

    Hi,
    I have tables agreement, distributor and agreementdistributors.
    The records are not getting saved in agreementdistributors.

    Please help!!!!

    My agreement.hbm.xml looks like below:

    and my Distributor.hbm.xml looks like below:

  • Mr. President // February 10, 2009 at 3:59 am | Reply

    Unfortunately SJohn, your xml files did not get copied in your comment. You will have to replace xml tags with their escape character representation for e.g. < becomes <

    A quick suggestion would be to compare my code and yours and identify differences. That should assist you in solving the issue.

  • Mr. President // February 10, 2009 at 4:16 am | Reply

    SJohn,

    You could use the following URL to convert your XMLs
    http://rishida.net/scripts/uniview/conversion.php

  • Maaz Hurzuk // April 2, 2009 at 5:25 am | Reply

    Thank you very much. Very helpful example.

  • Vamsi Krishna // May 27, 2009 at 8:54 am | Reply

    Hi,
    This is very useful for the people who are new for hibernate. Thanks for such post. I have a smiliar many to many relation. Now the new requirment is that the relation between person and phone should not be deleted but only be marked as deleted by adding extra field to Person_phone. Currently i am saving all the three objects person,phone and person_phone manually. Is there any better solution then what i do by using cascade.
    Please help.

  • Maniyas // July 23, 2009 at 6:43 pm | Reply

    Can we add an additional column in join table PERSON_PHONE table? If yes, how hibernate handles it? Can anyone share the code?

  • gnujack // August 3, 2009 at 3:14 pm | Reply

    Thanks for this clear walkthrough. I’m having a really annoying problem though: if i set my dialect to MySQL5InnoDBDialect then i get errors when trying to automatically build my tables. I’m using this little program to do that:

    Configuration config = new Configuration().configure();
    new SchemaUpdate(config).execute(true, true);

    It works however, when I set it to MySQL5Dialect. BUT my other problem is then with a many-to-many mapping (between objects: user and permission), if I include the cascade=”all” and inverse=”true” in their mappings, then the many-to-many table user_permissions never gets populated! I don’t really understand what their use is anyway… as without them, my database tables all work as expected (i get an entry in user_permissions whenever i add to a permission object’s set of users, OR if i add to a user object’s set of permissions).

    BUT (big, and only real problem:) after changing a user’s permission, and successfully saving that change to the DB, when i try to fetch that information, half the time it doesnt execute a query, it just uses the old (stale) information. Any idea why this might be?

    Many Thanks

  • ggg // September 25, 2009 at 2:31 pm | Reply

    awesome :)

  • shriram.m // October 1, 2009 at 5:54 am | Reply

    thanks for the clear explanation. Please tell me how to retrieve the values using many to many mapping.(by giving more than one values as input)

    • Mr. President // October 3, 2009 at 2:26 pm | Reply

      Shriram,

      Many thanks for your comment. I am unable to understand your requirement. Could you please elaborate on what you are trying to achieve?

  • shriram // October 3, 2009 at 5:26 pm | Reply

    i am having player class and team class. i want to select the teams(many) which the player is included(one)
    many- one ==>giving the input of players and getting the output of a team(or teams).
    i want an simple example. please provide so that it will be helpful for me to learn the hibernate thoroughly.
    thanks in advance
    regards
    shriram.m

    • Mr. President // October 4, 2009 at 5:00 pm | Reply

      Shriram,

      I think what you are looking for is an example of one-to-many relationship implementation in Hibernate. My earlier post should provide you the answer.

  • Shriram // October 23, 2009 at 4:57 am | Reply

    Actually I want to RETRIEVE a single value(one) by giving the input as set(many values). I can retrieve the one to many values by your example. so please give me an example for many to many or many to one retrieval.

Leave a Comment