Search This Blog

Sunday, 11 September 2011

Id Generators - 4

I shall cover the foreign and guid classes of generators in this post.

Foreign:


The foreign generator is used with objects that do not have their own key assigned to them.These use the identifier of another associated object. They are generally used along with <one-to-one> primary key association.
Consider the Person class.Each person has a PersonDetails object associated with it. The PersonDetails being in a one -to -one relationship with person, it can use the same identifier as the Person object it is associated to.
Java Classes:
public class Person {
    private Integer id;
    private String name;
    private Integer age;    
    private PersonDetails personDetails;    
    //setter getter methods
} 
public class PersonDetails {
    private Integer personId;
    private String fullName;
    private Person person;
    private String panCode;
    private BigDecimal weight;
    private BigDecimal height;
    
   //setter getter methods
}
Mapping documents:
The mapping document for Person is more or less same as the previous example. Only the association with person details has been added here. 
<?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="Person" table="PERSON">
        <id name="id" type="integer">
            <column name="ID" />
            <generator class="native" />
         </id>        
        <property name="name" type="string">
            <column name="NAME" />
        </property>
        <property name="age" type="integer">
            <column name="AGE" />
        </property>
        <one-to-one name="personDetails"
         class="PersonDetails" cascade="all"/>
    </class>
</hibernate-mapping>
The cascade property is set to "all". This means both the entities are related in all the operations. Adding, updating and deleting Person will also do same on related PersonDetails entity.
<?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="PersonDetails" table="Person_Details">
        <id name="personId" type="integer">
            <column name="PERSON_ID" />
            <generator class="foreign">
                <param name="property">person</param>
            </generator>
        </id>
        <one-to-one name="person" class="Person" constrained="true">
        </one-to-one>
        <property name="fullName" type="string">
            <column name="FULL_NAME" />
        </property>
        <property name="panCode" type="string">
            <column name="PAN_CODE" />
        </property>
        <property name="weight" type="big_decimal">
            <column name="WEIGHT" precision="10" />
        </property>
        <property name="height" type="big_decimal">
            <column name="HEIGHT" precision="10" />
        </property>
    </class>
</hibernate-mapping>
The foreign generator takes the name of the entity whose identifier will be used as a part of the <param> element. In this case the id attribute of Person object forms the identifier of the PersonDetails object. The one to one relation is also established here. 
The sql tables are as follows:
create table PERSON (
    ID integer not null auto_increment,
    NAME varchar(255),
    AGE integer,
    primary key (ID)
)
create table Person_Details (
    PERSON_ID integer not null,
    FULL_NAME varchar(255),
    PAN_CODE varchar(255),
    WEIGHT numeric(10,2),
    HEIGHT numeric(10,2),
    primary key (PERSON_ID)
)
alter table Person_Details 
    add index PERSON_DETAILS_FK1 (PERSON_ID), 
    add constraint PERSON_DETAILS_FK1 
    foreign key (PERSON_ID) 
    references PERSON (ID)
As can be seen from the sql, the primary key in the PERSON_DETAILS has a foreign key constraint on the associate PERSON table. To test the relationship the below code was used.
static SessionFactory sessionFactory;
    
public static void main(String[] args) {
    Configuration configuration = new Configuration();
    configuration = configuration.configure();
    sessionFactory = configuration.buildSessionFactory();
    testCreatePerson();
    testLoadPersonDetails();
    testPerson();
}
    
public static void testCreatePerson() {
    Person p = new Person();
    p.setAge(12);
    p.setName("Ramesh");
    PersonDetails personDetails = new PersonDetails();
    personDetails.setFullName("Ramesh Bhatia");
    personDetails.setHeight(BigDecimal.ZERO);
    personDetails.setPanCode("Aku786I");
    personDetails.setWeight(BigDecimal.ZERO);
    personDetails.setPerson(p);
    p.setPersonDetails(personDetails);
    Session session = sessionFactory.openSession();
    Transaction transaction = session.beginTransaction();
    session.save(p);
    transaction.commit();
}
    
public static void testPerson() {
    Session session = sessionFactory.openSession();
    Person person = (Person) session.load(Person.class, 2);
    System.out.println(person.getPersonDetails().getFullName());
}
    
public static void testLoadPersonDetails() {
    Session session = sessionFactory.openSession();
    PersonDetails personDetails = (PersonDetails) session.load(PersonDetails.class, 2);
    System.out.println(personDetails.getPersonId());
    System.out.println(personDetails.getPerson().getName());
}

On executing the create call:
2766 [main] DEBUG org.hibernate.event.def.AbstractSaveEventListener  - executing
 insertions
2781 [main] DEBUG org.hibernate.engine.Cascade  - processing cascade ACTION_SAVE
_UPDATE for: com.model.Person
2781 [main] DEBUG org.hibernate.engine.Cascade  - done processing cascade ACTION
_SAVE_UPDATE for: com.model.Person
2781 [main] DEBUG org.hibernate.event.def.AbstractSaveEventListener  - executing
 identity-insert immediately
2781 [main] DEBUG org.hibernate.persister.entity.AbstractEntityPersister  - Inse
rting entity: com.model.Person (native id)
2781 [main] DEBUG org.hibernate.jdbc.AbstractBatcher  - about to open PreparedSt
atement (open PreparedStatements: 0, globally: 0)
2797 [main] DEBUG org.hibernate.SQL  - 
    insert 
    into
        PERSON
        (NAME, AGE) 
    values
        (?, ?)
2797 [main] DEBUG org.hibernate.jdbc.AbstractBatcher  - preparing statement
2875 [main] DEBUG org.hibernate.persister.entity.AbstractEntityPersister  - Dehy
drating entity: [com.model.Person#<null>]
2875 [main] DEBUG org.hibernate.type.StringType  - binding 'Ramesh' to parameter
: 1
2875 [main] DEBUG org.hibernate.type.IntegerType  - binding '12' to parameter: 2
2922 [main] DEBUG org.hibernate.id.IdentifierGeneratorFactory  - Natively genera
ted identity: 2
...
2922 [main] DEBUG org.hibernate.event.def.AbstractSaveEventListener  - generated
 identifier: 2, using strategy: org.hibernate.id.ForeignGenerator
2922 [main] DEBUG org.hibernate.event.def.AbstractSaveEventListener  - saving [c
om.model.PersonDetails#2]
2922 [main] DEBUG org.hibernate.engine.Cascade  - done processing cascade ACTION
_SAVE_UPDATE for: com.model.Person
2922 [main] DEBUG org.hibernate.transaction.JDBCTransaction  - commit
2922 [main] DEBUG org.hibernate.impl.SessionImpl  - automatically flushing sessi
on
...
2953 [main] DEBUG org.hibernate.persister.entity.AbstractEntityPersister  - Inse
rting entity: [com.model.PersonDetails#2]
2953 [main] DEBUG org.hibernate.jdbc.AbstractBatcher  - about to open PreparedSt
atement (open PreparedStatements: 0, globally: 0)
2953 [main] DEBUG org.hibernate.SQL  - 
    insert 
    into
        Person_Details
        (FULL_NAME, PAN_CODE, WEIGHT, HEIGHT, PERSON_ID) 
    values
        (?, ?, ?, ?, ?)
As can be seen from the logs, 
  1. first the person object was created, then attempt was made to create the PersonDetails as part of the cascade operation. 
  2. The id of Person object was passed as a part of the insert call for the PersonDetails object. 
loading the record...testPerson()
 The method tries to load the newly created Person object and access the PersonDetails association. the logs generated on execution are below:
2250 [main] DEBUG org.hibernate.loader.Loader  - loading entity: [com.model.Pers
on#2]
2250 [main] DEBUG org.hibernate.jdbc.AbstractBatcher  - about to open PreparedSt
atement (open PreparedStatements: 0, globally: 0)
2250 [main] DEBUG org.hibernate.jdbc.ConnectionManager  - opening JDBC connectio
n
2250 [main] DEBUG org.hibernate.connection.DriverManagerConnectionProvider  - to
tal checked-out connections: 0
2250 [main] DEBUG org.hibernate.connection.DriverManagerConnectionProvider  - us
ing pooled JDBC connection, pool size: 0
2250 [main] DEBUG org.hibernate.SQL  - 
    select
        person0_.ID as ID1_1_,
        person0_.NAME as NAME1_1_,
        person0_.AGE as AGE1_1_,
        persondeta1_.PERSON_ID as PERSON1_2_0_,
        persondeta1_.FULL_NAME as FULL2_2_0_,
        persondeta1_.PAN_CODE as PAN3_2_0_,
        persondeta1_.WEIGHT as WEIGHT2_0_,
        persondeta1_.HEIGHT as HEIGHT2_0_ 
    from
        PERSON person0_ 
    left outer join
        Person_Details persondeta1_ 
            on person0_.ID=persondeta1_.PERSON_ID 
    where
        person0_.ID=?
loading the record...testLoadPersonDetails()
3750 [main] DEBUG org.hibernate.persister.entity.AbstractEntityPersister  - Fetc
hing entity: [com.model.PersonDetails#2]
3750 [main] DEBUG org.hibernate.loader.Loader  - loading entity: [com.model.Pers
onDetails#2]
3750 [main] DEBUG org.hibernate.jdbc.AbstractBatcher  - about to open PreparedSt
atement (open PreparedStatements: 0, globally: 0)
3750 [main] DEBUG org.hibernate.SQL  - 
    select
        persondeta0_.PERSON_ID as PERSON1_1_0_,
        persondeta0_.FULL_NAME as FULL2_1_0_,
        persondeta0_.PAN_CODE as PAN3_1_0_,
        persondeta0_.WEIGHT as WEIGHT1_0_,
        persondeta0_.HEIGHT as HEIGHT1_0_ 
    from
        Person_Details persondeta0_ 
    where
        persondeta0_.PERSON_ID=?
When trying to load the person object, Hibernate is not aware if the PersonDetail association is valid. Hence it would have to do an additional select query to check if the id exists in PERSON_DETAIL table. As it is anyways doing a query, it makes sense to load the relation. Hence it does a left outer join (thereby accounting for the case when personDetails is null)
To load the PersonDetails object no such complexity is needed as the PersonDetails object exists as a part of the association. The foreign key ensures that if person_id is valid then there has to be a Person object satisfying the association. Thus the association is lazy here.

guid:

In this method a database-generated GUID string is used.To understand what are guids, this article provides a good starting point. I used a simple class called Town to test this generator.
public class Town {
    private String id;
    private String name;
   //setter getter methods
}
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="Town" table="TOWN">
        <id name="id" column="ID" type="string">
            <generator class="guid" />
        </id>
        <property name="name" type="string">
            <column name="NAME" />
        </property>        
    </class>
</hibernate-mapping>
The database sql is for a simple table with varchar as the primary key:
create table TOWN (
        ID varchar(255) not null,
        NAME varchar(255) not null,
        primary key (ID)        
    )
On executing the below code to create a Town object the results are as follows:
public static void testCreateState() {
    Town town = new Town();
    town.setName("New Town");
    Session session = sessionFactory.openSession();
    Transaction transaction = session.beginTransaction();
    session.save(town);
    transaction.commit();
}
Logs:
2110 [main] DEBUG org.hibernate.jdbc.JDBCContext  - after transaction begin
2125 [main] DEBUG org.hibernate.event.def.DefaultSaveOrUpdateEventListener  - sa
ving transient instance
2125 [main] DEBUG org.hibernate.jdbc.AbstractBatcher  - about to open PreparedSt
atement (open PreparedStatements: 0, globally: 0)
2125 [main] DEBUG org.hibernate.SQL  - 
    select
        uuid()
2125 [main] DEBUG org.hibernate.jdbc.AbstractBatcher  - preparing statement
2203 [main] DEBUG org.hibernate.id.GUIDGenerator  - GUID identifier generated: f
bd47f3e-cb57-11e0-8efc-c437c8460442
2203 [main] DEBUG org.hibernate.jdbc.AbstractBatcher  - about to close PreparedS
tatement (open PreparedStatements: 1, globally: 1)
2203 [main] DEBUG org.hibernate.jdbc.AbstractBatcher  - closing statement
2203 [main] DEBUG org.hibernate.event.def.AbstractSaveEventListener  - generated
 identifier: fbd47f3e-cb57-11e0-8efc-c437c8460442, using strategy: org.hibernate
.id.GUIDGenerator
2203 [main] DEBUG org.hibernate.event.def.AbstractSaveEventListener  - saving [c
om.model.Town#fbd47f3e-cb57-11e0-8efc-c437c8460442]
...
2235 [main] DEBUG org.hibernate.persister.entity.AbstractEntityPersister  - Inse
rting entity: [com.model.Town#fbd47f3e-cb57-11e0-8efc-c437c8460442]
2250 [main] DEBUG org.hibernate.jdbc.AbstractBatcher  - about to open PreparedSt
atement (open PreparedStatements: 0, globally: 0)
2250 [main] DEBUG org.hibernate.SQL  - 
    insert 
    into
        TOWN
        (NAME, ID) 
    values
        (?, ?)
2250 [main] DEBUG org.hibernate.jdbc.AbstractBatcher  - preparing statement
2250 [main] DEBUG org.hibernate.persister.entity.AbstractEntityPersister  - Dehy
drating entity: [com.model.Town#fbd47f3e-cb57-11e0-8efc-c437c8460442]
As can be seen from the above logs, at the beginning of the transaction, Hibernate issues a call to the MySql uuid(). This function returns a unique id. The id is then used to save the new town object. The above created object can be fetched from the database with load call:
Town town = (Town) session.load(Town.class, "fbd47f3e-cb57-11e0-8efc-c437c8460442");

No comments:

Post a Comment