Search This Blog

Tuesday 15 November 2011

Uni Directional Collections

In this example I created the relation between shelf and book and set the direction of relation from Book to Shelf. i.e. the Book is aware of the shelf. Which is the same case as the db schema. The foreign key is in the Book table and not vice-versa.
This mapping is sufficient for most cases.
However Hibernate also offers the option of making the shelf class aware of the Books placed on it. It is possible to completely manage the life cycle of all Books on a given Shelf right from the shelf class, as we do in the case of components. The changes to the previous example would be in both the entities and the mapping files. To maintain the example of unidirectional relationships, I have removed the reference to shelf from the Book class.
public class Book {
    private String name;
    private Integer id;
//setter-getter methods
}
Shelf.java
public class Shelf {
    private Integer id;
    private String code;
    private Set<Book> books = new HashSet<Book>();
    //setter getter methods

    public void addBook(Book book1) {
        books.add(book1);
    }
}
The addBook() method is a utility method that is used to create a link between the Book and the Shelf entity. The changes to the mapping fields is as below:
Book.hbm.xml:
<?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.undirectional1">
    <class name="Book" table="BOOK">
        <id name="id" type="integer">
            <column name="ID" />
            <generator class="native" />
        </id>
        <property name="name" type="string">
            <column name="Name" length="50" not-null="true" />
        </property>
    </class>
</hibernate-mapping>
Shelf.hbm.xml
<?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.undirectional1">
    <class name="Shelf" table="SHELF">
        <id name="id" type="integer">
            <column name="ID" />
            <generator class="native" />
        </id>
        <property name="code" type="string">
            <column name="CODE" length="50" not-null="true" />
        </property>
        
        <set name="books"> 
             <!-- THE foreign-key attribute used for generated DDL -->
             <key column ="SHELF_ID" foreign-key=BOOK_FK_1" not-null="true" /> 
             <one-to-many class="Book" /> <!-- One Shelf has many books -->
        </set>        
    </class>
</hibernate-mapping>
The start up logs indicate the same SQL scripts were fired as before:
2875 [main] DEBUG org.hibernate.tool.hbm2ddl.SchemaExport  - 
    create table BOOK (
        ID integer not null auto_increment,
        Name varchar(50) not null,
        SHELF_ID integer not null,
        primary key (ID)
    )
2875 [main] DEBUG org.hibernate.tool.hbm2ddl.SchemaExport  - 
    create table SHELF (
        ID integer not null auto_increment,
        CODE varchar(50) not null,
        primary key (ID)
    )
2890 [main] DEBUG org.hibernate.tool.hbm2ddl.SchemaExport  - 
    alter table BOOK 
        add index SHELF_FK_1 (SHELF_ID), 
        add constraint BOOK_FK_1 
        foreign key (SHELF_ID) 
        references SHELF (ID)
The code to create a shelf with books is as below:
static void create() {
    Shelf shelf1 = new Shelf();
    shelf1.setCode("SH01");
        
    Book book1 = new Book();
    book1.setName("Lord Of The Rings");
        
    Book book2 = new Book();
    book2.setName("Simply Fly");

    shelf1.addBook(book1);
    shelf1.addBook(book2);
        
    Session session = sessionFactory.openSession();
    Transaction t = session.beginTransaction();
    session.save(shelf1);
    session.save(book1);
    session.save(book2);
    t.commit();
    System.out.println("The Chocolate Lover with name " + shelf1.getCode()
            + " was created with id " + shelf1.getId());
    System.out.println("Book1 saved with id " + book1.getId()
            + " and Book2 saved with id " + book2.getId());
}
The logs indicate that 3 SQL insert statements were executed as before to save the three entities.
3234 [main] DEBUG org.hibernate.SQL  - 
    insert 
    into
        SHELF
        (CODE) 
    values
        (?)
3328 [main] DEBUG org.hibernate.SQL  - 
    insert 
    into
        BOOK
        (Name, SHELF_ID) 
    values
        (?, ?)
...
3328 [main] DEBUG org.hibernate.SQL  - 
    insert 
    into
        BOOK
        (Name, SHELF_ID) 
    values
        (?, ?)
...
3406 [main] DEBUG org.hibernate.SQL  - 
    update
        BOOK 
    set
        SHELF_ID=? 
    where
        ID=?
3406 [main] DEBUG org.hibernate.SQL  - 
    update
        BOOK 
    set
        SHELF_ID=? 
    where
        ID=?
Along with the SQL statements two update queries were also fired to update the foreign-key reference in the Book table.The Shelf _Id column is a not null field. The value in the new row created for Book includes the value. Hence the update queries are of no real point.
As before we can use cascade settings here to manage the association. The saving of shelf should also save the books on it. This would then be like a parent child relationship.
This can be achieved with the cascade settings.
<set name="books" cascade="save-update,delete"> 
    <key column ="SHELF_ID" foreign-key="BOOK_FK_1" 
               not-null="true" /> 
    <one-to-many class="Book" /> 
</set>
The save code would now change to
static void() {
        Transaction t = session.beginTransaction();
        session.save(shelf1);
//        session.save(book1);
//        session.save(book2);
        t.commit();
}
While the save now occurs with just one line of code, the number of fired queries does not change. Update queries are still fired.

Also calling a delete on shelf entity would delete the children entities. As books cannot exists if the shelf is removed. But care needs to be taken that the references between shelf and its books is broken from any other rows in the table. In the next example I shall look at working with a bidirectional relationship.

No comments:

Post a Comment