Search This Blog

Saturday, 10 September 2011

Creating Components in Hibernate

The Java POJOs that we create need not be composed solely of Primitive types. It is possible to group together value fields into Java classes. Such objects which exist as member fields inside the mapped java classes are called components.
Components are classes (or columns in the table) that do not have their own life-cycle.
They exist only as a part of the owning entity. If the Java Class is removed, then the component own its own does not exists. in UML terms there exists a composition relationship between the Components and the Java POJO (or Entity Class).
If the Entity remains,then the Component exists. The Entity does has its own (database) identity. The Component is owned by the Entity and does not have its own identity. Its persistent fields are actually the columns of the record representing the Entity in the database table. As it maps to within a single row, Components can only be associated with a single entity.
As stated earlier, the components can be used to group together similar data. e.g. The Person record may include a state, city and pin code field in its record. Rather than representing this data as individual columns in the Person class
class Person {
    private int id;
    private String name;
    //...
    private String city;
    private String state;
    private  String zipcode;
}
They could be grouped together as an address entity and placed within the person object.
class Address {
    private String city;
    private String state;
    private  String zipcode;
    //...
}

class Person {
    private int id;
    private String name;
    private Address residence;
    //...        
}
This also provides us with a better way to represent multiple addresses if the need were to arise. 
For my example I decided to create a component for auditing purposes. I have noticed in most applications that I have worked in, the Entities all include certain fields to help manage a minimum audit trail. These fields are generally of the form
private Integer createdBy;
private Date createdDate;
private Integer modifiedBy;
private Date modifiedDate;
The fields have no domain relationship with the rest of the Entity. They are a very good case of Value Types. Also they exist only for a single entity (i.e. within the same table row). These fields can be represented as components. The SQL code for one such Entity is as below.
create table USER  (
  ID int(11) not null auto_increment,
  NAME varchar(100) not null,
  Created_Date datetime default null,
  Modified_Date datetime default null,
  Created_By int(11) default null,
  Modified_By int(11) default null,
  PRIMARY KEY (ID)
);
The audit fields are present as columns in the row.The java Classes for this would be as below:
public class User {
    private Long id;
    private String name;
    private AuditField auditField;

    //setter getter for the component
    public AuditField getAuditField() {
        return auditField;
    }

    public void setAuditField(AuditField auditField) {
        this.auditField = auditField;
    }
//other setter-getters 
 }
public class AuditField {
    private Integer createdBy;
    private Date createdDate;
    private Integer modifiedBy;
    private Date modifiedDate;
    //setter-getter for the properties
}
The mapping document is as follows:
<?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.model">
    <class name="User" table="USER">
        <id name="id" type="long">
            <column name="ID" />
            <generator class="native" />
        </id>
        <property name="name" type="string">
            <column name="NAME" />
        </property>

        <component name="auditField" class="com.model.component.AuditField">
            <property name="createdBy" type="integer">
                <column name="Created_By" />
            </property>
            <property name="createdDate" type="timestamp">
                <column name="Created_Date" length="19" />
            </property>
            <property name="modifiedBy" type="integer">
                <column name="Modified_By" />
            </property>
            <property name="modifiedDate" type="timestamp">
                <column name="Modified_Date" length="19" />
            </property>
        </component>
    </class>
</hibernate-mapping>
On executing the code to create a User record, the logs are as below:
static void testCreate() {
    Session session = SESSION_FACTORY.openSession();
    Transaction transaction = null;
    final Date crtDate = new Date();
    User user = new User();
    user.setName("New User");
    
    AuditField auditField = new AuditField();
    auditField.setCreatedBy(1);
    auditField.setModifiedBy(1);
    auditField.setCreatedDate(crtDate);
    auditField.setModifiedDate(crtDate);
    user.setAuditField(auditField);
    
    transaction = session.beginTransaction();
    Long id = (Long) session.save(user);
    System.out.println("New record is " + id);
    transaction.commit();                
    session.close();    
}
765  [main] INFO  org.hibernate.cfg.HbmBinder  - Mapping class: com.model.User -
> USER
765  [main] DEBUG org.hibernate.cfg.HbmBinder  - Mapped property: id -> ID
781  [main] DEBUG org.hibernate.cfg.HbmBinder  - Mapped property: name -> NAME
796  [main] DEBUG org.hibernate.cfg.HbmBinder  - Mapped property: createdBy -> C
reated_By
796  [main] DEBUG org.hibernate.cfg.HbmBinder  - Mapped property: createdDate ->
 Created_Date
796  [main] DEBUG org.hibernate.cfg.HbmBinder  - Mapped property: modifiedBy -> 
Modified_By
796  [main] DEBUG org.hibernate.cfg.HbmBinder  - Mapped property: modifiedDate -
> Modified_Date
796  [main] DEBUG org.hibernate.cfg.HbmBinder  - Mapped property: auditField -> 
Created_By, Created_Date, Modified_By, Modified_Date
...
3078 [main] DEBUG org.hibernate.persister.entity.AbstractEntityPersister  -  Ver
sion select: select ID from USER where ID =?
3078 [main] DEBUG org.hibernate.persister.entity.AbstractEntityPersister  -  Sna
pshot select: select user_.ID, user_.NAME as NAME0_, user_.Created_By as Created
3_0_, user_.Created_Date as Created4_0_, user_.Modified_By as Modified5_0_, user
_.Modified_Date as Modified6_0_ from USER user_ where user_.ID=?
3078 [main] DEBUG org.hibernate.persister.entity.AbstractEntityPersister  -  Ins
ert 0: insert into USER (NAME, Created_By, Created_Date, Modified_By, Modified_D
ate, ID) values (?, ?, ?, ?, ?, ?)
3078 [main] DEBUG org.hibernate.persister.entity.AbstractEntityPersister  -  Upd
ate 0: update USER set NAME=?, Created_By=?, Created_Date=?, Modified_By=?, Modi
fied_Date=? where ID=?
3078 [main] DEBUG org.hibernate.persister.entity.AbstractEntityPersister  -  Del
ete 0: delete from USER where ID=?
...
3453 [main] DEBUG org.hibernate.jdbc.AbstractBatcher  - about to open PreparedSt
atement (open PreparedStatements: 0, globally: 0)
3468 [main] DEBUG org.hibernate.SQL  - 
    insert 
    into
        USER
        (NAME, Created_By, Created_Date, Modified_By, Modified_Date) 
    values
        (?, ?, ?, ?, ?)
3828 [main] DEBUG org.hibernate.pretty.Printer  - listing entities:
3828 [main] DEBUG org.hibernate.pretty.Printer  - com.model.User{id=1, auditFiel
d=component[createdBy,createdDate,modifiedBy,modifiedDate]{createdBy=1, modified
By=1, createdDate=2011-08-21 12:06:51, modifiedDate=2011-08-21 12:06:51}, name=N
ew User}
As can be seen from the logs, the process of creating an Object with components is very similar to that of a simple Entity. The component mappings are read at start up. The SQL scripts generated for CRUD operations are very similar to that of non-component entities. I retrieved the same record back using a session.load call. The generated logs are as below
2578 [main] DEBUG org.hibernate.SQL  - 
    select
        user0_.ID as ID0_0_,
        user0_.NAME as NAME0_0_,
        user0_.Created_By as Created3_0_0_,
        user0_.Created_Date as Created4_0_0_,
        user0_.Modified_By as Modified5_0_0_,
        user0_.Modified_Date as Modified6_0_0_ 
    from
        USER user0_ 
    where
        user0_.ID=?
2578 [main] DEBUG org.hibernate.jdbc.AbstractBatcher  - preparing statement
It is possible that the columns that map to the component do not have any value for a particular row. For e.g. the below code creates a User object with null values in the component fields.
User user = new User();
user.setName("New User with No Audit");
transaction = session.beginTransaction();
Long id = (Long) session.save(user);
The object is created successfully. If we retrieve the object then it will be found that the component field has a null value in the returned result. Hibernate will assume that if all component columns are null, then the entire component is null.
User user = new User();
user.setName("New User with No Audit");
transaction = session.beginTransaction();
Long id = (Long) session.save(user);
System.out.println("New record is " + id);
transaction.commit();
user = (User) session.load(User.class, 5L);
//the component was received as null
//below statement will throw Null Pointer exceptions
System.out.println("Name : " + user.getName() + "Date Created : " + user.getAuditField().getCreatedDate());

2 comments: