My previous post covered one-to-one associations in Hibernate. This will cover one-to-many associations in Hibernate.
We will continue to the configuration and classes as defined in the previous post. Let me recap. Here’s the hibernate.cfg.xml
<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"><username></property>
<property name="connection.password"><password></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>
The Person.hbm.xml and Phone.hbm.xml is as below:
<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" />
</class>
</hibernate-mapping>
<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>
We are now step up to start working on developing one-to-many associations. Here’s how we will define the relationalship in tables as per following format:
_____________ __________ | | | | | PERSON | | PHONE | |_____________| |__________| | | 1 * | | | ID | ---- | ID | | FIRSTNAME | | NUMBER | | LASTNAME | | TYPE | | DOB | | PERSONID | |_____________| |__________|
Define the one-to-many association between person and phone in the following manner:
<set name="phones"> <key column="personid" not-null="true"/> <one-to-many class="Phone"/> </set>
Make the following additions in the Person.java file.
private Set phones = new HashSet();
public Set getPhones() {
return phones;
}
public void addPhone(Phone phone) {
this.phones.add(phone);
}
public void setPhones(Set phones) {
this.phones = phones;
}
Here’s the test program:
Configuration cfg = new Configuration();
cfg.configure();
SessionFactory sessFactory = cfg.buildSessionFactory();
try {
Session session = sessFactory.openSession();
Person p = new Person();
p.setId("21823");
p.setFirstName("T1om");
p.setLastName("Jones");
p.setDob(new java.util.Date());
Phone ph = new Phone();
ph.setId("123");
ph.setNumber("3033838");
ph.setType("BUS");
p.addPhone(ph);
Transaction tx = session.beginTransaction();
session.save(p);
tx.commit();
session = sessFactory.openSession();
Query q = session.createQuery("FROM Person as p" +
" WHERE p.id = 21823");
List l = q.list();
System.out.println("Result Size: " + l.size());
session.close();
} catch(Exception e) {
e.printStackTrace();
}
The output generated shows an exception:
Hibernate: insert into PERSON (firstName, lastName, dob, id) values (?, ?, ?, ?) Hibernate: update PHONE set personid=? where id=? org.hibernate.StaleStateException: Batch update returned unexpected row count from update [0]; actual row count: 0; expected: 1 at org.hibernate.jdbc.Expectations$BasicExpectation.checkBatched (Expectations.java:61)
The Person INSERT is generated appropriately. However no Phone INSERT script is generated. Hibernate directly moves on to updating the Phone table with personid information. Make the following change in the one-to-many association definition.
<set name="phones" cascade="all"> <key column="personid" not-null="true"/> <one-to-many class="Phone"/> </set>
Now run the sample program. Presto it works.
Hibernate: select phone_.id, phone_.contactnumber as contactn2_1_, phone_.phonetype as phonetype1_ from PHONE phone_ where phone_.id=? Hibernate: insert into PERSON (firstName, lastName, dob, id) values (?, ?, ?, ?) Hibernate: insert into PHONE (contactnumber, phonetype, personid, id) values (?, ?, ?, ?) Hibernate: update PHONE set personid=? where id=? Hibernate: select person0_.id as id0_, person0_.firstName as firstName0_, person0_.lastName as lastName0_, person0_.dob as dob0_ from PERSON person0_ where person0_.id=21823 Result Size: 1
The query on the Person is not retrieving the phones. Hence it is working as expected. In case we intend to retrieve the phones information along with the Person information make the following change in the query:
FROM Person as p LEFT OUTER JOIN p.phones WHERE p.id = 21823
The query generated is as follows:
select person0_.id as id0_0_, phones1_.id as id1_1_, person0_.firstName as firstName0_0_, person0_.lastName as lastName0_0_, person0_.dob as dob0_0_, phones1_.contactnumber as contactn2_1_1_, phones1_.phonetype as phonetype1_1_ from PERSON person0_ left outer join PHONE phones1_ on person0_.id=phones1_.personid where person0_.id=21823
There is one other aspect to look at. An unnecessary UPDATE script is getting created.
update PHONE set personid=? where id=?
There is a way to get rid of this UPDATE. Here’s how. Change the one-to-many definition in the Person.hbm.xml file as follows:
<set name="phones" cascade="all" inverse="true"> <key column="personid" not-null="true"/> <one-to-many class="Phone"/> </set>
Add the following definition in the Phone.hbm.xml file.
<many-to-one name="person" class="Person" not-null="false" column="personid"/>
Add the following code in the Phone.java file.
private Person person = null;
public Person getPerson() {
return person;
}
public void setPerson(Person person) {
this.person = person;
}
Run the sample program. The output is as below:
Hibernate: select phone_.id, phone_.contactnumber as contactn2_1_, phone_.phonetype as phonetype1_, phone_.personid as personid1_ from PHONE phone_ where phone_.id=? Hibernate: insert into PERSON (firstName, lastName, dob, id) values (?, ?, ?, ?) Hibernate: insert into PHONE (contactnumber, phonetype, id) values (?, ?, ?) Hibernate: select person0_.id as id0_, person0_.firstName as firstName0_, person0_.lastName as lastName0_, person0_.dob as dob0_ from PERSON person0_ where person0_.id=21823 Result Size: 1
We have got rid of the UPDATE script. However in the database we will notice that the personid field in the PHONE table is set to null. Make the following change in the addPhone method of Person.
public void addPhone(Phone phone) {
phone.setPerson(this);
this.phones.add(phone);
}
A final point, hibernate generates a SELECT query on Phone before proceeding with the inserts(Thanks Basker, getting my attention and making me look into this aspect). If you want to get rid of the SELECT query, change the id declaration in Phone.hbm.xml to the following:
<id name="id" unsaved-value="any">
<generator class="assigned" />
</id>
Note the unsaved-value=”any” added in the declaration. This ensures that the SELECT query does not get generated. The new hibernate output will be as follows:
Hibernate: insert into PERSON (firstName, lastName, dob, id) values (?, ?, ?, ?)
Hibernate: insert into PHONE (contactnumber, phonetype, personid, id) values (?, ?, ?, ?)
Hibernate: select person0_.id as id0_, person0_.firstName as firstName0_, person0_.lastName
as lastName0_, person0_.dob as dob0_ from PERSON person0_ where person0_.id=21823
Result Size: 1
That’s all for the moment. Next time I will move on to many-to-many associations.
13 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 |
[...] We will use the same hibernate configuration setting for Person and Phone as mentioned in my post related to one-to-many [...]
Basker // November 18, 2008 at 12:56 am |
Thanks for the infomation. It is very usefull for me in resolving my problem.
I have a clarification.
Before inserting into PERSON Table, why does hibernate issues the follwing sql to query on PHONE Table:
—————
Hibernate: select phone_.id, phone_.contactnumber as contactn2_1_,
phone_.phonetype as phonetype1_, phone_.personid as personid1_
from PHONE phone_ where phone_.id=?
———————-
Hibernate: insert into PERSON (firstName, lastName, dob, id)
values (?, ?, ?, ?)
Hibernate: insert into PHONE (contactnumber, phonetype, id)
values (?, ?, ?)
Regards
Basker
Mr. President // November 18, 2008 at 4:36 am |
Basker,
The response to your query is purely speculation from my end. I am assuming that Hibernate is double checking to ensure that the relationship object Phone in our case is not already available in the database(logically we could use a persistent object so that hibernate could know, but nothing prevents us from using a new object). Unfortunately there is no way to check how the underlying code generated by hibernate works. Probably you could post this question to the hibernate.org forum. Sorry that I could not provide you a proper response.
Mr. President // November 18, 2008 at 9:33 am |
Basker,
I did a bit of debugging and found the following. For persisting relationships, hibernate checks if the id is a null value or a not null value. In our case when are using the assigned generator, therefore it gets a not null value. In such a scenario it checks the id’s property value for attribute unsaved-value in Phone’s hbm.xml. By default, the value assigned is “undefined”, then it looks up for the object in the secondary cache and if not found, hits the database to validate if the entity instance exists or not, thus resulting in the SELECT sql. A quick way to bypass this is to define unsaved-value=”any” or unsaved-value=”none”. This prevents the SELECT query from getting generated. Hope this helps. I have not tested this in a production size applications, hence there might be some unwanted side effects.
Basker // November 20, 2008 at 1:36 am |
Thanks for the information. I tried your suggesion and it worked. Hibernate did not issue any sql before insert.
But one thing I found is unsaved-value=”none” DOES NOT work. hibernate tries to update the PHONE table and fails.
SO unsaved-value=”any” works and elimnates the select query on PHONE Table before insert in to PERSON Table.
Mr. President // November 21, 2008 at 4:44 am |
Basker,
I apologize for the oversight. Only unsaved-value=”any” works. Thanks for pointing out the error.
santh // December 17, 2008 at 9:37 am |
super
APoisson // February 4, 2009 at 2:59 pm |
Thank you very much!!! I didn’t know how to get rid of the SELECT before un INSERT. Thankssss!!
annie // May 6, 2009 at 8:45 am |
Hi!
I have a mapping similar to yours but I want to do a query like this:
Select all Persons where the Person’s phone number like ‘49%’
I know I could query on the Phone object but I want a list of Person objects that have a phone number that starts with ‘49′.
Thanks!
Mr. President // May 6, 2009 at 10:26 am |
Annie, I believe the query should be something like this:
Select p.name
from Person p
join p.phones ph
where ph.number like ‘49%’.
I do not have a ready code to test this, but this should provide you with the general approach.
seenu // May 6, 2009 at 10:43 am |
thanks Mr President!.
ur article helped to solve an update/delete on an one-to-many associated collection mapping.
Vimm // May 18, 2009 at 7:35 pm |
Thanks a bunch. I’m new to Hibernate and wondered why the child was being inserted and then updated, breaking the not-null constraint on my foreign key. This walkthrough was exactly what I needed to understand and fix the problem.
HibernateLinks « Blog zum Fragebogensystem // May 22, 2009 at 7:55 pm |
[...] http://technicalmumbojumbo.wordpress.com/2007/08/18/investigating-hibernate-associations-one-to-many... [...]