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.
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 |
[...] 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 |
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 |
Great!! Thank´s…
Finally something easy to understand
kukumber // September 17, 2008 at 2:14 pm |
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 |
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 |
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 |
YES,I am using cascade=all inverse=true
Thanks
partha
Dinakar // October 13, 2008 at 4:58 am |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
It’s what i need now!
Thanks so much. ^-^
moiaz // January 20, 2009 at 9:34 am |
xcellent article dude…
keep posting such articles..
we need guys like u
cheers !!!
mOIAz
renuka // February 4, 2009 at 11:40 am |
Thanks a lot….. Very usefull article… Keep dng same.
SJohn // February 9, 2009 at 7:10 pm |
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 |
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 |
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 |
Thank you very much. Very helpful example.
Vamsi Krishna // May 27, 2009 at 8:54 am |
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.
Mr. President // May 28, 2009 at 11:02 am |
Vamsi,
I believe this is what you are looking for.
Maniyas // July 23, 2009 at 6:43 pm |
Can we add an additional column in join table PERSON_PHONE table? If yes, how hibernate handles it? Can anyone share the code?
Mr. President // July 24, 2009 at 3:59 am |
An interesting question. I have not tried it myself. Here are two links which might provide pointers. Hope this helps. URL1, URL2
gnujack // August 3, 2009 at 3:14 pm |
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 |
awesome
shriram.m // October 1, 2009 at 5:54 am |
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 |
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 |
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 |
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 |
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.
Mr. President // October 28, 2009 at 1:32 pm |
I have personally not posted any article on many-to-one relationship. However try this tutorial. Might prove useful.