Search This Blog

Friday 28 October 2011

Ordered Collection

In an earlier post we saw that hibernate provides us with the option to use Sorted Collections. In these collections the objects were fetched form the database and inserted into collections in a sorted order. Thus the sorting of elements happened in the Java Domain. This was an example of in-memory sorting.
Hibernate also provides an alternative to the above technique called ordered collections.
In this case the data is fetched in a particular sequence/order from the database. The sort is achieved by using the SQL order-by clause. I tried the same for a set of hobbies of a Person.
public class HobbiedPerson {
    private String name;
    private Integer id;
    private Set<String> hobbies = new HashSet<String>();
        //setter-getter methods 
}
The java class here includes a set of hobbies. The mapping file provides information of the ordering to be used
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="com.collection.basic">
    <class name="HobbiedPerson" table="HOBBIED_PERSON">
        <id name="id" type="integer">
            <column name="ID" />
            <generator class="identity" />
        </id>
        <property name="name" type="string">
            <column name="NAME" />
        </property>

        <set name="hobbies" table="PERSON_HOBBIES" order-by="lower(HOBBY) asc">
            <key column="PERSON_ID" />
            <element type="string" column="HOBBY" not-null="true" />
        </set>

    </class>
</hibernate-mapping>
As can be seen above the order-by attribute includes a SQL order-by clause, that specifies the ordering to be used by Hibernate when loading the collection. I executed a code to insert records in the database
static void createHobbiedPerson() {
    HobbiedPerson person = new HobbiedPerson();
    Set<String> hobbies = new HashSet<String>();
    hobbies.add("Tennis");
    hobbies.add("Philately");
    hobbies.add("Gaming");
    hobbies.add("Cooking");
    hobbies.add("Singing");
    person.setName("Raman");
    person.setHobbies(hobbies);
    Session session = sessionFactory.openSession();
    Transaction t = session.beginTransaction();
    session.save(person);
    t.commit();
}
The logs(cleaned) at start up are as follows:
2281 [main] DEBUG org.hibernate.loader.collection.BasicCollectionLoader  - Stati
c select for collection com.collection.basic.HobbiedPerson.hobbies: select hobbi
es0_.PERSON_ID as PERSON1_0_, hobbies0_.HOBBY as HOBBY0_ from PERSON_HOBBIES hob
bies0_ where hobbies0_.PERSON_ID=? order by lower(hobbies0_.HOBBY) asc
...
2375 [main] DEBUG org.hibernate.tool.hbm2ddl.SchemaExport  - 
    create table HOBBIED_PERSON (
        ID integer not null auto_increment,
        NAME varchar(255),
        primary key (ID)
    )
2390 [main] DEBUG org.hibernate.tool.hbm2ddl.SchemaExport  - 
    create table PERSON_HOBBIES (
        PERSON_ID integer not null,
        HOBBY varchar(255) not null,
        primary key (PERSON_ID, HOBBY)
    )
2406 [main] DEBUG org.hibernate.tool.hbm2ddl.SchemaExport  - 
    alter table PERSON_HOBBIES 
        add index FK2310BEE62D1D2FB8 (PERSON_ID), 
        add constraint FK2310BEE62D1D2FB8 
        foreign key (PERSON_ID) 
        references HOBBIED_PERSON (ID)
...
2593 [main] DEBUG org.hibernate.SQL  - 
    insert 
    into
        HOBBIED_PERSON
        (NAME) 
    values
        (?)
...

2656 [main] DEBUG org.hibernate.SQL  - 
    insert 
    into
        PERSON_HOBBIES
        (PERSON_ID, HOBBY) 
    values
        (?, ?)
The generated SQL select statement for the collection includes the order-by clause specified in the file.
On calling the session.create() method, the insert queries were fired.
The insertion logs indicate the data is inserted successfully.
An interesting to note in the insert logs was the order of hobby creation.
2469 [main] DEBUG org.hibernate.pretty.Printer  - listing entities:
2469 [main] DEBUG org.hibernate.pretty.Printer  - com.collection.basic.HobbiedPe
rson{id=1, name=Raman, hobbies=[Philately, Tennis, Cooking, Gaming, Singing]}
...
2531 [main] DEBUG org.hibernate.type.IntegerType  - binding '1' to parameter: 1
2531 [main] DEBUG org.hibernate.type.StringType  - binding 'Philately' to parame
ter: 2
2531 [main] DEBUG org.hibernate.type.IntegerType  - binding '1' to parameter: 1
2531 [main] DEBUG org.hibernate.type.StringType  - binding 'Tennis' to parameter
: 2
2531 [main] DEBUG org.hibernate.type.IntegerType  - binding '1' to parameter: 1
2531 [main] DEBUG org.hibernate.type.StringType  - binding 'Cooking' to paramete
r: 2
2531 [main] DEBUG org.hibernate.type.IntegerType  - binding '1' to parameter: 1
2531 [main] DEBUG org.hibernate.type.StringType  - binding 'Gaming' to parameter
: 2
2531 [main] DEBUG org.hibernate.type.IntegerType  - binding '1' to parameter: 1
2531 [main] DEBUG org.hibernate.type.StringType  - binding 'Singing' to paramete
r: 2
As can be seen, my order of insertion in the Set was not the order used in the insert queries generated.Which makes sense , since I used a HashSet and not LinkedHashSet. But the records generated in the database did not match my insert sequence.

On replacing the HashSet with a LinkedHasSet instance and keeping the rest of the cide same, the ordering was preserved:
2468 [main] DEBUG org.hibernate.pretty.Printer  - com.collection.basic.HobbiedPe
rson{id=1, name=Raman, hobbies=[Tennis, Philately, Gaming, Cooking, Singing]}
The insert queries were fired in the same sequence. But the database sequence of records remained unchanged. 
InfactI inserted a record "Moonlighting" for this person and then fired the select * query.
The result was :
I am still not sure why.
Fetching the records:
static void retrive() {
    Session session = sessionFactory.openSession();
    HobbiedPerson person = (HobbiedPerson) session.load(HobbiedPerson.class, 1);
    System.out.println(person.getHobbies());
}
The logs indicate the above SQL select query was fired and the output indicates the
Set contains data in the ascending order:
2657 [main] DEBUG org.hibernate.event.def.DefaultInitializeCollectionEventListen
er  - collection initialized
[Cooking, Gaming, Philately, Singing, Tennis]
Hibernate internally represents the set as a LinkedHashSet, thus successfully preserving the insertion order.
The same can be implemented for the other collections.
  1. Map: Hibernate ensures the ordering using a LinkedHashMap
  2. Bag: Hibernate ensures an ordered bag by internally using an ArrayList.
The functionality is not available for the list elements, as list have their own index column to manage the order.

6 comments:

  1. Hi
    I am a little confused on Collections , Ordering and Hibernate
    Suppose we have a Student class and Book class.
    and Student has an ordered collection of Books

    Now we can have following scenarios

    * store an ORDERED collection from java to DB table
    - if order is b1,b2,b3 then ( rownum wise )the entries in DB will be in that order
    - if order is b3,b2,b1 then ( rownum wise )the entries in DB will be in that order
    the irrespective of value for sort="xyz"

    * GET rows from DB to Java ORDERED Collection using "in memory sorting"
    - Now here requirement is irrespective of the order of rows in DB we want a particular ordered collection in java memory we implement comparable or comparator accordingly
    - so in and in Book class we implement CompareTo
    - in and in Book class we implement Comparable not compareTo
    - in here order will be as per the implemented class
    - if then whatever be default order in DB will come in Java collection

    * GET rows from DB to Java ORDERED Collection using "SQL sorting"

    right ??

    ReplyDelete
    Replies
    1. Hi,
      The order-by attribute is applicable when you are loading a set. It should not affect the way you insert records. If you are keen on an order of insertion, a list should help.
      For in memory sorting you have to provide a comparator via the sort attribute. So yes, whatever the order of records in db, or sequence in which records are returned, the set will hold data as per the comparator algorithm.

      Delete
  2. What does Hibernate internally represent List as ??

    ReplyDelete
  3. Hibernate uses its own representations for the Collections interfaces. Check this link for details: http://learningviacode.blogspot.in/2011/10/hibernate-and-collections-support.html

    ReplyDelete
  4. so by keeping LinkedHashSet or HashSet we decide the order by which insert statements are executed ... not stored in DB ... I was thinking WHY the insertion order is different from the order stored , may be it can be same if ROWNUM ( in case of oracle ) is used for ordering , SO if the ordering in java can be done by RowNum and DB naturally uses same then may be the insertion order will be same as storing order

    ReplyDelete
  5. Interesting thoughts, the insertion order would be coming from Hibernate. So yes, the LinkedHashSet must ensure that Hibernate reads the records in a fixed order. It could be that the insert queries are being fired in a different sequence - just a possibility, I need to try this scenario out in more detail.
    I am going to try this same functionality with an Entity class in the collection to see if there is any way to predict this behavior.
    Or maybe some code digging is needed. You have managed to make me curious about this :)
    Let me know if you solve it. Cheers

    ReplyDelete