FileDocCategorySizeDatePackage
Sequencer.javaAPI DocExample11560Mon Mar 31 23:10:16 BST 2003org.dasein.persist

Sequencer

public class Sequencer extends Object
A tool for the automatic generation of unique numbers. This class goes to the database once every MAX_KEYS requests to get a new seed for the numbers it generates. This class is thread-safe, meaning multiple threads can be safely requesting unique numbers from it. It is also multi-process safe. In other words, multiple machines can simultaneously be generating unique values and those values will be guaranteed to be unique across all applications. The only caveat is that they all must be using the same algorithm for generating the numbers and getting seeds from the same database. In order to access the database, this class expects a system property called org.dasein.persist.SequencerDSN. It should be set to the name of the DSN that provides connections to the database with the Sequencer table. That table should have the following CREATE: CREATE TABLE Sequencer ( name VARCHAR(20) NOT NULL, seed BIGINT UNSIGNED NOT NULL, lastUpdate BIGINT UNSIGNED NOT NULL, PRIMARY KEY ( name, lastUpdate ) );
Last modified $Date$
version
$Revision$
author
George Reese

Fields Summary
private static final long
MAX_KEYS
The maximum number of keys that may be safely generated without going to the database. You should lower this number for client applications and other short-lived programs. The number can be higher for applications with long uptimes. All applications using the same sequencer, however, should have the same value for MAX_KEYS.
private static final HashMap
sequencers
All sequencers currently in memory.
private String
name
The name of this sequencer.
private long
seed
The seed this sequencer will use for generating its ID's.
private long
sequence
The current sequence within this sequencer's seed.
private static final String
CREATE_SEQ
The SQL for creating a new sequence in the database.
private static final int
INS_NAME
Constant for the name parameter.
private static final int
INS_SEED
Constant for the seed parameter.
private static final int
INS_UPDATE
Constant for the lastUpdate parameter
private static final String
DEFAULT_DSN
The name of a DSN to use if none is configured in the system properties.
private static final String
DSN_PROP
The name of the system property to check for a DSN.
private static final String
FIND_SEQ
The SQL for getting a seed for a sequence from the database.
private static final int
SEL_NAME
Constant for the name parameter.
private static final int
SEL_SEED
Constant for the seed column.
private static final int
SEL_UPDATE
Constant for the lastUpdate column.
private static String
UPDATE_SEQ
The SQL for incrementing the seed in the database.
private static final int
UPD_SEED
Constant for the seed parameter.
private static final int
UPD_SET_UPDATE
Constant for the lastUpdate set parameter
private static final int
UPD_NAME
Constant for the name parameter.
private static final int
UPD_WHERE_UPDATE
Constant for the lastUpdate parameter.
Constructors Summary
private Sequencer(String nom)
Constructs a new sequencer with the specified name.

param
nom the name of the sequencer


                        
       
        super();
        name = nom;
    
Methods Summary
private voidcreate(java.sql.Connection conn)
Creates a new entry in the database for this sequence. This method will throw an error if two threads are simultaneously trying to create a sequence. This state should never occur if you go ahead and create the sequence in the database before deploying the application. It could be avoided by checking SQL exceptions for the proper XOPEN SQLState for duplicate keys. Unfortunately, that approach is error prone due to the lack of consistency in proper XOPEN SQLState reporting in JDBC drivers.

param
conn the JDBC connection to use
throws
java.sql.SQLException a database error occurred

    
                                                                                                        
          
        PreparedStatement stmt = null;
        ResultSet rs = null;

        try {
            stmt = conn.prepareStatement(CREATE_SEQ);
            stmt.setString(INS_NAME, name);
            stmt.setLong(INS_SEED, 0L);
            stmt.setLong(INS_UPDATE, System.currentTimeMillis());
            if( stmt.executeUpdate() != 1 ) {
                throw new SQLException("No row was inserted.");
            }
            seed = 0L;
        }
        finally {
            if( rs != null ) {
                try { rs.close(); }
                catch( SQLException e ) { }
            }
            if( stmt != null ) {
                try { stmt.close(); }
                catch( SQLException e ) { }
            }
        }
    
public static final org.dasein.persist.SequencergetInstance(java.lang.String name)
Looks to see if a sequencer has been generated for the sequence with the specified name. If not, it will instantiate one. Multiple calls to this method with the same name are guaranteed to receive the same sequencer object. For best performance, classes should save a reference to the sequencer once they get it in order to avoid the overhead of a HashMap lookup.

param
name the name of the desired sequencer
return
the sequencer with the specified name


                                                                                        
          
        synchronized( sequencers ) {
            if( !sequencers.containsKey(name) ) {
                Sequencer seq = new Sequencer(name);

                sequencers.put(name, seq);
                return seq;
            }
            else {
                return (Sequencer)sequencers.get(name);
            }
        }
    
public synchronized longnext()
Generates a new unique number. The unique number is based on the following algorithm:
unique number = seed multiple by maximum keys per seed added to seed sequence
The method then increments the seed sequence for the next ID to be generated. If the ID to be generated would exhaust the seed, then a new seed is retrieved from the database.

return
a unique number
throws
org.dasein.persist.PersistenceException a data store error occurred while generating the number


                                                                                      
          
        Connection conn = null;

        // when seed is -1 or the keys for this seed are exhausted,
        // get a new seed from the database
        if( (seed == -1L) || ((sequence + 1) >= MAX_KEYS) ) {
            try {
                String dsn = System.getProperty(DSN_PROP, DEFAULT_DSN);
                InitialContext ctx = new InitialContext();
                DataSource ds = (DataSource)ctx.lookup(dsn);
                
                conn = ds.getConnection();
                reseed(conn);
            }
            catch( SQLException e ) {
                throw new PersistenceException(e);
            }
            catch( NamingException e ) {
                throw new PersistenceException(e);
            }
            finally {
                if( conn != null ) {
                    try { conn.close(); }
                    catch( SQLException e ) { }
                }
            }
        }
        // up the sequence value for the next key
        sequence++;
        // the next key for this sequencer
        return ((seed * MAX_KEYS) + sequence);
    
private voidreseed(java.sql.Connection conn)
Gets the next seed from the database for this sequence.

param
conn the database connection
throws
java.sql.SQLException a database error occurred


                              
          
        PreparedStatement stmt = null;
        ResultSet rs = null;

        try {
            // Keep in this loop as long as we encounter concurrency errors
            do {
                stmt = conn.prepareStatement(FIND_SEQ);
                stmt.setString(SEL_NAME, name);
                rs = stmt.executeQuery();
                if( !rs.next() ) {
                    // no such sequence, create it
                    {
                        // close resources
                        try { rs.close(); }
                        catch( SQLException e ) { }
                        rs = null;
                        try { stmt.close(); }
                        catch( SQLException e ) { }
                        stmt = null;
                    }
                    create(conn);
                }
                else {
                    long ts;

                    seed = rs.getLong(SEL_SEED) + 1L;
                    ts = rs.getLong(SEL_UPDATE);
                    {
                        // close resources
                        try { rs.close(); }
                        catch( SQLException e ) { }
                        rs = null;
                        try { stmt.close(); }
                        catch( SQLException e ) { }
                        stmt = null;
                    }
                    // increment the seed in the database
                    stmt = conn.prepareStatement(UPDATE_SEQ);
                    stmt.setLong(UPD_SEED, seed);
                    stmt.setLong(UPD_SET_UPDATE, System.currentTimeMillis());
                    stmt.setString(UPD_NAME, name);
                    stmt.setLong(UPD_WHERE_UPDATE, ts);
                    if( stmt.executeUpdate() != 1 ) {
                        // someone changed the database! try again!
                        seed = -1L;
                    }
                }
            } while( seed == -1L );
            sequence = -1L;
        }
        finally {
            if( rs != null ) {
                try { rs.close(); }
                catch( SQLException e ) { }
            }
            if( stmt != null ) {
                try { stmt.close(); }
                catch( SQLException e ) { }
            }
        }