-->
These old forums are deprecated now and set to read-only. We are waiting for you on our new forums!
More modern, Discourse-based and with GitHub/Google/Twitter authentication built-in.

All times are UTC - 5 hours [ DST ]



Forum locked This topic is locked, you cannot edit posts or make further replies.  [ 4 posts ] 
Author Message
 Post subject: Distributed (replicated) database ID generator
PostPosted: Sun Jan 29, 2006 11:07 pm 
Newbie

Joined: Sat Jun 25, 2005 11:18 pm
Posts: 7
I designed an ID Generator Hibernate class that is multi-platform friendly (similar to native id generator) but additionally provided for safe values in a distributed database environment. I wanted something that could be implemented natively on many databases (i.e. Oracle, Firebird, MySQL, etc.) without the need for loading extra library code into the database (i.e. Oracle 8 doesn't support out of the box UUIDs as SQL-Server does).

Often this is done on the database side by having a numeric database identifier used as part of the id. For example if using a two digit numeric database id and sequences the code on the dabase side would be similar to the following for Oracle:

Code:
SELECT S_TableIDSeq.NEXTVAL * 100 + v_DBID INTO :NEW.SCode FROM DUAL;


The above would normally be used in an insert trigger where v_DBID is the database id retrieved via a lookup table on the database or a function, etc.

The numeric database identifier is only guaranteed to be unique by design vs inherently unique and does require manual assignment of some sort or other. This has been an acceptable compromise in the past.

I didn't want to use the "assigned" generator as I wanted to take advantage of the auto-generation of the IDs.

The new generator can take advantage like the native generator of whether the generator should be based on a sequence, a table hi-lo or an identity. I've extensively tested with Oracle, Firebird and MySQL. It has been in use in a manufacturing machine automated data collection environment for the past 3 months. It has two additional parameters (compared to the native generator): databaseID and multiplier. Both are optional as the databaseID can be set/retrieved through a DatabaseIDSingleton class which was useful for me as I could set this DatabaseIDSingleton value at the onset of the program based on a query from the database connected to and therefore if the database connected to it switched it required no manual change of databaseID property in the mapping files. However, if the multiplier is ommitted it will ignore the databaseID and behave exactly like the native generator. I thought this would be useful as the same generator could be used whether in a distributed environment (multiplier parameter required) and in a non distributed enviroment (multiplier parameter not required).

The question is then to incorporate this new capability would it be best to extend the native class or create an entirely new class?

Does the Hibernate group want me to contribute this to the project?

In any case I'll post the code for the two classes as a reply to this thread and hopefully with a simple enough example of how the Singleton is intended to be used (when opting to use this vs a mapping file provided databaseID).


Top
 Profile  
 
 Post subject: DatabaseIDSequenceGenerator
PostPosted: Sun Jan 29, 2006 11:09 pm 
Newbie

Joined: Sat Jun 25, 2005 11:18 pm
Posts: 7
Code:

import java.io.Serializable;
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.id.Configurable;
import org.hibernate.id.IdentifierGenerationException;
import org.hibernate.id.PersistentIdentifierGenerator;
import org.hibernate.id.SequenceGenerator;
import org.hibernate.id.SequenceHiLoGenerator;
import org.hibernate.id.TableHiLoGenerator;
import org.hibernate.type.Type;

/**
* <b>DatabaseIDSequenceGenerator</b><br>
* <br>
* Generates <tt>long</tt> values using an oracle-style sequence with an
* embedded database ID. This is intended for distributed (replicated) database
* environment in order to ensure that the primary key is unique across
* databases. There is a dependency with the DatabaseIDSingleton class where the
* fixed database ID to be used is stored.<br>
* <br>
* Mapping parameters supported: sequence, parameters, databaseID, multiplier.
*
* @see SequenceGenerator
* @see SequenceHiLoGenerator
* @see TableHiLoGenerator
* @author Yves Blouin
*/

public class DatabaseIDSequenceGenerator implements
      PersistentIdentifierGenerator, Configurable {

   /**
    * The sequence parameter
    */
   public static final String SEQUENCE = "sequence";

   /**
    * The parameters parameter, appended to the create sequence DDL. For
    * example (Oracle):
    * <tt>INCREMENT BY 1 START WITH 1 MAXVALUE 100 NOCACHE</tt>.
    */
   public static final String PARAMETERS = "parameters";

   public static final String DATABASEID = "databaseID";

   public static final String MULTIPLIER = "multiplier";

   // private String sequenceName;

   // private String parameters;

   private Type identifierType;

   // private String sql;

   private String str_databaseID;

   private String str_multiplier;

   private TableHiLoGenerator tableHiLoGenerator = null;

   private SequenceHiLoGenerator sequenceHiLoGenerator = null;

   private boolean tableHiLoGeneratorUsed = true;

   private static DatabaseIDSingleton dbID = DatabaseIDSingleton.getInstance();

   /*
    * private static final Log log = LogFactory
    * .getLog(DatabaseIDSequenceGenerator.class);
    */

public void configure(Type type, Properties params, Dialect dialect)
         throws MappingException {
      Class clazz = dialect.getNativeIdentifierGeneratorClass();
      this.identifierType = type;
      this.str_multiplier = params.getProperty(MULTIPLIER);
      this.str_databaseID = params.getProperty(DATABASEID);

      if (clazz == SequenceGenerator.class) {
         setTableHiLoGeneratorUsed(false);
         if (this.sequenceHiLoGenerator == null) {
            this.sequenceHiLoGenerator = new SequenceHiLoGenerator();
            this.sequenceHiLoGenerator.configure(type, params, dialect);
         }
      } else {
         setTableHiLoGeneratorUsed(true);
         if (this.tableHiLoGenerator == null) {
            this.tableHiLoGenerator = new TableHiLoGenerator();
            this.tableHiLoGenerator.configure(type, params, dialect);
         }
      }
   }   public Serializable generate(SessionImplementor session, Object obj)
         throws HibernateException {

      final Serializable result;
      Serializable baseID;
      if (isTableHiLoGeneratorUsed()) {
         baseID = this.tableHiLoGenerator.generate(session, obj);
      } else {
         baseID = this.sequenceHiLoGenerator.generate(session, obj);
      }
      result = AddDBIdentification(baseID);
      return result;
   } // End generate method

   public String[] sqlCreateStrings(Dialect dialect) throws HibernateException {
      String[] a_str_ddl;
      if (isTableHiLoGeneratorUsed()) {
         a_str_ddl = this.tableHiLoGenerator.sqlCreateStrings(dialect);
      } else {
         a_str_ddl = this.sequenceHiLoGenerator.sqlCreateStrings(dialect);
      }
      return a_str_ddl;
   } // End sqlCreateStrings method

   public String[] sqlDropStrings(Dialect dialect) throws HibernateException {
      String[] a_str_ddl;
      if (isTableHiLoGeneratorUsed()) {
         a_str_ddl = this.tableHiLoGenerator.sqlDropStrings(dialect);
      } else {
         a_str_ddl = this.sequenceHiLoGenerator.sqlDropStrings(dialect);
      }
      return a_str_ddl;
   } // End sqlDropStrings method

   public Object generatorKey() {
      Object genKey;
      if (isTableHiLoGeneratorUsed()) {
         genKey = this.tableHiLoGenerator.generatorKey();
      } else {
         genKey = this.sequenceHiLoGenerator.generatorKey();
      }
      return genKey;
   } // End generatorKey method

   private Serializable AddDBIdentification(Serializable baseID)
         throws IdentifierGenerationException {
      short multiplier = 1;
      short databaseID = 0;
      // String str_dbID = "";
      Serializable result;

      Class idClass = identifierType.getReturnedClass();
      if (this.str_multiplier == null) {
         multiplier = 1;
         databaseID = 0;
      } else {
         try {
            multiplier = Short.parseShort(this.str_multiplier);
         } catch (NumberFormatException excNumFmt) {
            multiplier = 1;
         }
         if (dbID.getIdentification() != null) {
            databaseID = dbID.getIdentification().shortValue();
         } else {
            try {
               databaseID = Short.parseShort(this.str_databaseID);
            } catch (NumberFormatException excNumFmt) {
               databaseID = 0;
            }
         } // if (dbID.getIdentification() != null)
      } // if (this.str_multiplier == null)

      if (idClass == Long.class) {
         result = new Long(((Long) baseID).longValue() * multiplier
               + databaseID);
      } else if (idClass == Integer.class) {
         result = new Integer(((Integer) baseID).intValue() * multiplier
               + databaseID);
      } else if (idClass == Short.class) {
         result = new Short((short) (((Short) baseID).shortValue()
               * multiplier + databaseID));
      } else if (idClass == String.class) {
         if (this.str_databaseID != null) {
            result = ((String) baseID) + this.str_databaseID;
         } else {
            result = ((String) baseID);
         } // if (this.str_databaseID != null)
      } else {
         throw new IdentifierGenerationException(
               "this id generator generates long, integer, short or string");
      } // if (idClass == Long.class)
      return result;
   }

   private boolean isTableHiLoGeneratorUsed() {
      return tableHiLoGeneratorUsed;
   }

   private void setTableHiLoGeneratorUsed(boolean tableHiLoGeneratorUsed) {
      this.tableHiLoGeneratorUsed = tableHiLoGeneratorUsed;
   }

}


Top
 Profile  
 
 Post subject: DatabaseIDSingleton
PostPosted: Sun Jan 29, 2006 11:11 pm 
Newbie

Joined: Sat Jun 25, 2005 11:18 pm
Posts: 7
Code:
public class DatabaseIDSingleton {

   private static DatabaseIDSingleton databaseID = null;

   private Short identification = null;

   private DatabaseIDSingleton() {
      super();
   }

   public static DatabaseIDSingleton getInstance() {
      if (databaseID == null) {
         databaseID = new DatabaseIDSingleton();
      }//End if (databaseID == null)
      return databaseID;
   }// End getInstance method

   /**
    * Don't make any methods static other than getInstance in order to force
    * instantion of singleton before use
    */

   /**
    * Return the database identification in a Short. Using this vs
    * <Integer>.shortValue().
    *
    * @return Short identification
    */
   public Short getIdentification() {
      return identification;
   }

   /**
    * Return the database identification in a Byte. Using this vs
    * getIdentification.byteValue() ensures that if the identification is null
    * there is no null pointer exceptions thrown by the byteValue() method.
    *
    * @return Byte identification
    */
   public Byte getByteIdentification() {
      return (identification == null) ? null : new Byte(identification
            .byteValue());
   }

   public void setIdentification(Integer identification) {
      // Allow ID to be only set once
      if (this.identification == null) {
         this.identification = new Short(identification.shortValue());
      } // if (this.identification == null)
   }

   public void setIdentification(Short identification) {
      // Allow ID to be only set once
      if (this.identification == null) {
         this.identification = identification;
      } // if (this.identification == null)
   }

}


Top
 Profile  
 
 Post subject: Sample usage of the singleton
PostPosted: Sun Jan 29, 2006 11:28 pm 
Newbie

Joined: Sat Jun 25, 2005 11:18 pm
Posts: 7
The mapping file generator would look something like this:

Code:
    <id
        name="bundleScode"
        type="java.lang.Long"
        column="BUNDLE_SCODE"
        unsaved-value="0"
    >
        <generator class="com.whateverPkgName.DatabaseIDSequenceGenerator">
            <param name="sequence">S_BUNDLES_SCODE</param>
            <param name="table">S_BUNDLES_SCODE</param>
            <param name="column">NEXT_VALUE</param>
            <param name="max_lo">0</param>
            <param name="multiplier">100</param>
            <param name="databaseID">0</param>
        </generator>
    </id>


The databaseID is specified here but overridden by the application using the value retrieved from the database.

I make the following declaration at the top of each of the Hibernate POJOs to ensure the Singleton is instantiated.

Code:
   private DatabaseIDSingleton dbIDSingleton = DatabaseIDSingleton
         .getInstance();


Early on in the life of the application (before I need to store/insert any data) I query the database for it's database ID (in my case I have a hibernate object mapped to a database view that contains the database id - called a database entry point - and it's stored in the singleton as part of the constructor of this view hibernate object).

Code:
      dbIDSingleton.setIdentification(new Integer(dbEntryPoint));


where dbEntryPoint is the database ID retrieved by the view.

The other option to using the Singleton is to manually specify the database id via the mapping file.

I submit this in the hope that it can be use to someone else.


Top
 Profile  
 
Display posts from previous:  Sort by  
Forum locked This topic is locked, you cannot edit posts or make further replies.  [ 4 posts ] 

All times are UTC - 5 hours [ DST ]


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum

Search for:
© Copyright 2014, Red Hat Inc. All rights reserved. JBoss and Hibernate are registered trademarks and servicemarks of Red Hat, Inc.