My experiments with technology

Investigating Hibernate Associations – One to Many

August 18, 2007 · 13 Comments

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.

Categories: Hibernate

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 | Reply

    [...] 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 | Reply

    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 | Reply

    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 | Reply

    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 | Reply

    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 | Reply

    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 | Reply

    super

  • APoisson // February 4, 2009 at 2:59 pm | Reply

    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 | Reply

    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 | Reply

    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 | Reply

    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 | Reply

    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 | Reply

    [...] http://technicalmumbojumbo.wordpress.com/2007/08/18/investigating-hibernate-associations-one-to-many... [...]

Leave a Comment