Investigating Hibernate Associations – One to Many

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.

Advertisements

24 thoughts on “Investigating Hibernate Associations – One to Many

  1. 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

  2. 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.

  3. 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. 😉

  4. 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.

  5. 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!

  6. 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.

  7. thanks Mr President!.
    ur article helped to solve an update/delete on an one-to-many associated collection mapping.

  8. 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.

  9. its really good information to me, from last 2-3 days I was getting the stalestate exception and I found this article intersting and able to solve the problem now, the solutin is that when you use onetomany realtionship your child entity automatically gets updated , while deleting or inserting or update , and it runs a update query with null value so it was giving error , so i disabled now update query using inverse=true and its working fine. thanks dude:)

  10. Hi,
    I have a user table, and through we are going to create the workgroup , in this many users can belongs. And same user can belongs to many workgroup. Please suggest me which mapping should be use full. I tried the one to many but user table is updated by the workgroup. So i am facing the problem

    1. Sandeep,

      From your problem description, I understand that a user can belong to multiple workgroups and a workgroup can have multiple users. Essentially the relationship between user and workgroup is a many-to-many relationship. I have written a post on setting up a many-to-many association using hibernate. It should provide the appropriate pointers.

  11. I can get everything to work fine by following your example. One question I do have, however, is that once you change the query to…

    FROM Person as p LEFT OUTER JOIN p.phones WHERE p.id = 21823

    how are you able to cast the object in the list to List rather than List and hence call that object’s methods? When I try to do that it says I’m not able to cast, which seems slightly pointless as all I’m therefore able to do is check the size of the list.

  12. I’m sorry it seems to have take out my identifiers in that last post; that should be ListPhone and ListObject in that order.

    1. First, thanks Mike Baddeley for your kind words. It has been some time that I have worked on hibernate, however I do not think they have provided support for generics. Here’s how someone else has addressed the problem. Refer the URL. Go to the section titled “Creating the FilmHelper.java Helper Class” within that look for point 5 under Enumerating Film Titles and Retrieving Actors Using an HQL Query subsection. Hope this helps.

  13. You’ve got to be very very careful setting unsaved-value=”any” on an assigned identifier.

    If you ever have transient objects, it’s going to bite you in the bum. Hibernate needs to know whether an object is already in the database when you call session.save. If you’re quite sure you’re never going to use transience or detached objects, or moving objects from one sessions to another, you should be okay.

    Otherwise, let Hibernate to the select. It’s not doing it to be difficult 🙂

  14. Thank you for all this information, that is really usefull. However, I would like to know something concerning constraints that are automaticly set by hibernate when creating tables. I hope that I’m not too much Off topic, but I haven’t found any article that explains something in particular. How do you manipulate/modify your constraints? I’ve got now on a table “ON UPDATE NO ACTION and on DELETE NO ACTION” but I’ve never specified somewhere that I want this to happen. Any ideas?

    Thanks in advance

    Cheers!

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s