Saturday, January 31, 2009

Custom Id generator Class for Hibernate

Custom Id generator Class for Hibernate.

Scenario 1,

Suppose that you are not going to use hibernate built in id generation configuration (e.g. assigned, native, increment, etc ...), then you can use your own id generator class to generate your own id at run time.

To create your own id generator class, first you have to create

your own class that implement hibernate
org.hibernate.id.IdentifierGenerator
interface. Implementing generate method with your own
business requirement and return any Serializable object Value as your id.
(As an example here I use random Long value as my custom id.)
 
Class implementation,
 

package com.danushka.hibernate;

 

import java.io.Serializable;

import org.hibernate.HibernateException;

import org.hibernate.engine.SessionImplementor;

import org.hibernate.id.IdentifierGenerator;

/**

* This class will generate a random value which can be use as Hibernate mapping

* generator value. To use this class ,In the *.hbm.xml file's generator class

* need to change into this class fully qualified name.

*

* @author Danushka.

*

*/

public class CustomGenerator implements IdentifierGenerator {

/**

* This method will generate a random number and return it, hibernate can

* use this id as it generator class id.

*/

@Override

public Serializable generate(SessionImplementor sessionImplemetor,

Object object) throws HibernateException {

Long randNo = (long) Math.random();

return randNo;

}

}

To use this custom id with your hibernate mapping you have to change your hbm.xml file like below.

Example hbm .xml file.

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>

<class name="com.danushka.hib.Person" table="Person">

<id name="personId" type="int" column="personId">

<generator class=" com.danushka.hibernate.CustomGenerator " />

id>

<set name="adress">

<key column="personId" />

<one-to-many class="com.danushka.hib.Address" />

set>

class>

hibernate-mapping>

In this hbm.xml file generator class uses my custom ID generator class,

Scenario 2,

If you want to get any table properties which are defined in the hbm.xml file, you have to implements hibernated org.hibernate.id.Configurable Interface. With this interface configure method you can access all the properties available in the hbm.xml file.

In this scenario I am going to call a stored procedure which returns an id for each table, to return id the stored procedure needs table name as input parameter. To get table name at the run time I used configurable interface configure method.

This generate method has the Session Implementer instance as the input parameter, through this instance I can access hibernate session and execute database operations.

Getting table name and calling stored procedure through the hibernate session I can get the next value result as the id of the table. The implementation is given below.

package com.danushka.hibernate.test;

import java.io.Serializable;

import java.sql.PreparedStatement;

import java.sql.ResultSet;

import java.sql.SQLException;

import java.util.Properties;

import org.hibernate.HibernateException;

import org.hibernate.MappingException;

import org.hibernate.dialect.Dialect;

import org.hibernate.engine.SessionImplementor;

import org.hibernate.exception.JDBCExceptionHelper;

import org.hibernate.id.Configurable;

import org.hibernate.id.IdentifierGenerator;

import org.hibernate.id.PersistentIdentifierGenerator;

import org.hibernate.type.Type;

/**

* This class will generate a random value which can be use as Hibernate mapping

* generator value. To use this class ,In the *.hbm.xml file's generator class

* need to change into this class fully qualified name.

*

* @author Danushka.

*

*/

public class CustomGenerator implements IdentifierGenerator, Configurable {

private String tableName;

/**

* This method will generate a random number and return it, hibernate can

* use this id as it generator class id. {@inheritDoc}

*/

@Override

public Serializable generate(SessionImplementor sessionImplemetor,

Object object) throws HibernateException {

return getNextNumber(sessionImplemetor);

}

/**

* This method's parameters have all the available details of hbm.xml

* file.Since all the database table related data available ,Hibernate can

* access to the each and every table. In this example to get my id i used

* procedure. To execute my stored procedure i need to pass table name.

* {@inheritDoc}

*/

@Override

public synchronized void configure(Type type, Properties params, Dialect d)

throws MappingException {

tableName = params.getProperty(PersistentIdentifierGenerator.TABLE);

}

/**

* This method will call the stored procedure and return the next result.

*

* @return next value of the id

*/

private Long getNextNumber(SessionImplementor session) {

String sql = "{call stored procedure name}";

Long nextValue = null;

try {

PreparedStatement st =

session.getBatcher().prepareSelectStatement(sql);

st.setString(1, tableName);

try {

ResultSet rs = st.executeQuery();

try {

while (rs.next()) {

nextValue = Long.parseLong(rs.getString(1));

}

} finally {

rs.close();

}

} finally {

session.getBatcher().closeStatement(st);

}

} catch (SQLException sqle) {

throw JDBCExceptionHelper.convert(session.getFactory()

.getSQLExceptionConverter(), sqle,

"could not fetch initial value for increment generator",

sql);

}

return null;

}

}

7 comments:

  1. I like your approach on this. I work with an application that does almost exactly this. The only difference though is that the key generation tables are located in a different schema which would probably require a different connection to the database.

    Nice approach though.

    ReplyDelete
  2. David ,
    you dont need two database connection if your schemas are in the same database. If your domain tables are in the different schema like domain , then change your hbm.xml file like below

    <class name='com.danushka.hib.Person' table='domain.Person'>

    and you have to changed the key generation table key into domain.Person then your different schema problem will solve.

    ReplyDelete
  3. Can you please suggest how I can use a property for a class for ID generation.
    Consider the following scenario:
    I have a domain object "Issue" that has a property "Date createTime", that I want to be used as the id with some custom logic.

    For example suppose the first Issue is created on 2011/08/02, the ID should be 2011080201. The next Issue on the same day should have the ID 2011080202 and so on.(formatting Date to String??)

    Then, the first issue created on the following day 2011/08/03 the ID should be 2011080301.

    So the basic requirement is I have to append an incremented value on to the createTime property.

    ReplyDelete
  4. nice scenario sir thank you . but if i want to generate the identity values as FF001,FF002,FF003,..... with the help of SequnceGenerator then how we generate pls help me.......

    ReplyDelete
    Replies
    1. package com.pbsi.cs02.data;

      import java.io.Serializable;
      import java.sql.Connection;
      import java.sql.PreparedStatement;
      import java.sql.ResultSet;
      import java.sql.SQLException;

      import org.apache.commons.lang.StringUtils;
      import org.apache.log4j.Logger;
      import org.hibernate.HibernateException;
      import org.hibernate.engine.spi.SessionImplementor;
      import org.hibernate.id.IdentifierGenerator;

      public class StockCodeGenerator implements IdentifierGenerator {

      private static Logger log = Logger.getLogger(StockCodeGenerator.class);

      public Serializable generate(SessionImplementor session, Object object)
      throws HibernateException {

      String prefix = "FF";
      Connection connection = session.connection();
      try {

      PreparedStatement ps = connection
      .prepareStatement("SELECT nextval ('seq_stock_code') as nextval");

      ResultSet rs = ps.executeQuery();
      if (rs.next()) {
      int id = rs.getInt("nextval");
      String code = prefix + StringUtils.leftPad("" + id,3, '0');
      log.debug("Generated Stock Code: " + code);
      return code;
      }

      } catch (SQLException e) {
      log.error(e);
      throw new HibernateException(
      "Unable to generate Stock Code Sequence");
      }
      return null;
      }
      }

      Delete
  5. This comment has been removed by the author.

    ReplyDelete