All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.literatejava.hibernate.allocator.LinearBlockAllocator Maven / Gradle / Ivy

Go to download

Linear Block Allocator -- a portable, table-based generator for Hibernate which simplifies & supersedes Hi-Lo strategies.

The newest version!
package com.literatejava.hibernate.allocator;

/*
 * Linear Block Allocator;  a superior & portable ID allocator for Hibernate.
 *     http://literatejava.com/hibernate/linear-block-allocator-a-superior-alternative-to-hilo/
 * 
 * Copyright (c) 2005-2015, Tom Whitmore/ LiterateJava.com and other contributors 
 * as indicated by @author tags or express copyright attribution statements. 
 * This implementation includes sources adapted from Hibernate, copyright (c) 
 * 2008 Red Hat Middleware LLC. All third-party contributions are distributed 
 * under license.
 *
 * This copyrighted material is made available to anyone wishing to use, modify,
 * copy, or redistribute it subject to the terms and conditions of the GNU
 * Lesser General Public License, as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License
 * for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this distribution; if not, write to:
 * Free Software Foundation, Inc.
 * 51 Franklin Street, Fifth Floor
 * Boston, MA  02110-1301  USA
 */

import java.io.Serializable;
import java.sql.*;
import java.util.Properties;

import org.hibernate.*;
import org.hibernate.cfg.ObjectNameNormalizer;
import org.hibernate.dialect.Dialect;
import org.hibernate.engine.jdbc.internal.FormatStyle;
import org.hibernate.engine.jdbc.spi.JdbcServices;
import org.hibernate.engine.jdbc.spi.SqlStatementLogger;
import org.hibernate.engine.spi.SessionImplementor;
import org.hibernate.id.*;
import org.hibernate.internal.util.config.ConfigurationHelper;
import org.hibernate.jdbc.AbstractReturningWork;
import org.hibernate.mapping.Table;
import org.hibernate.type.Type;
import org.jboss.logging.Logger;



/**
 * Allocate Long or Integer keys, using a portable table-based algorithm. 
 * 
    *
  • This strategy is highly efficient, database-portable, and simplifies/ supersedes HiLo-type strategies. *
  • Keys are allocated in blocks from an "allocator" table, with a current block held in memory; used by the application as required. *
  • Default block-size of 20 outperforms SEQUENCE allocation by a factor of 10 or more, using only standard SQL & fully portable * between databases. *
  • Block allocation is multiuser & cluster-safe; database contention is handled by retry. *
  • This allocator requires the ability to obtain a Session-independent connection to the database. This is * possible under most common configurations where connection acquisition is under Hibernate control. If * Hibernate is being driven with user-supplied connections, another generation strategy should be chosen. * *
* * Allocation using the "linear block" algorithm, can be understood as allocating blocks from a linear number-line. NEXT_VAL in the database * represents the start of the next block. Blocks are allocated by simply incrementing the counter in a concurrency-safe manner. Block-based * allocation and portable high performance are achieved simply, without unnecessary complexities. *

* * Compared to HiLo, "linear block allocation" treats keyspace as the linear number-line it fundamentally is, rather than breaking * it into a two-dimensional keyspace (the separate "hi" & "lo" words). Keeping NEXT_VAL the same type & magnitude as the actual keys * simplifies maintenance, reduces multiplication to simple addition, and removes unnecessary complexity. There is no performance advantage or * any other benefit to justify HiLo's more complicated number-space & design; it is merely a flawed concept rejected by Occam's razor. *

* * Database operation & maintenance are easy. With linear block allocation, "allocator state" and existing keys are always in direct correspondence. * NEXT_VAL corresponds directly to MAX(existing keys); and must always be greater. Bulk inserts, validity checking or manual updates to the allocator * are obvious & easy. Unlike Hi-Lo, tuning or changing block-size are possible without losing allocator position & database integrity. * Since NEXT_VAL represents values directly (rather than via multiplier) changing block-size does not affect the next key to be allocated. *

* * LinearBlockAllocator also generates better human-friendly keys. Block-sizes default to human-readable decimal-based (20, 100 etc) sizes -- * there is no forced bias towards large binary numbers! Key wastage is decreased & restarting your server twice allocates customer=60, not customer=98304 :) * Imagine how much easier development will be, without having to type stupid large ugly numbers for all keys. *

* * LinearBlockAllocator is configured with two groups of parameters -- those defining the allocator table, and those selecting/controlling allocation for the specific sequence. *

* * Allocator table definition: *

    *
  • table -- allocator table to use, default "KEY_ALLOC" *
  • sequenceColumn -- sequence-name column, default "SEQ" *
  • allocColumn -- next-block column, default "NEXT_VAL " *
* Sequence selection: *
    *
  • sequenceName -- sequence-name to use; defaults to table-name of the Hibernate entity *
  • blockSize -- block-size (# of keys) to cache in memory; default 20 *
* * Allocator table can contain multiple "allocation sequences", keyed by "name". Many applications can use a single allocator table to store * all their sequences. Each Hibernate generator is configured independently, so shared or independent allocator tables are equally able to * be configured. *

* * Block-size is the key parameter controlling performance. Larger block-sizes increase performance, at the expense of a slight increase in unused keys * lost on server restart. Increasing the block-size to 200 achieve most of the performance benefits practically possible; entities/rows must still be * INSERT'ed into the database. *

* * Compared with vendor-specific strategies such as Oracle SEQUENCE, "LinearBlockAllocator" approaches double the performance (half the number of database * operations per INSERT) in a completely portable & SQL-standard manner. *

* * * @see TableHiLoGenerator * @author Tom Whitmore * @author Hibernate implementation of this algorithm includes code adapted from Hibernate sources/ Gavin King, (c) 2008 Red Hat Middleware LLC. */ public class LinearBlockAllocator /*extends TransactionHelper*/ implements PersistentIdentifierGenerator, Configurable { /* COLUMN and TABLE should be renamed but it would break the public API */ public static final String ALLOC_TABLE = "table"; public static final String SEQUENCE_COLUMN = "sequenceColumn"; public static final String ALLOC_COLUMN = "allocColumn"; // public static final String SEQUENCE_NAME = "sequenceName"; public static final String BLOCK_SIZE = "blockSize"; /** default Table & Column names */ public static final String DEFAULT_TABLE = "KEY_ALLOC"; public static final String DEFAULT_SEQUENCE_COLUMN = "SEQ"; public static final String DEFAULT_ALLOC_COLUMN = "NEXT_VAL"; public static final int DEFAULT_BLOCK_SIZE = 100; /** internal defaults */ private static final int DEFAULT_SEQUENCE_COLUMNLENGTH = 128; // - DB configuration protected String tableName; protected String sequenceColumn; protected String allocColumn; protected String sequenceName; protected int blockSize; // - identifier/ allocator Type. protected Type returnType; // - compiled protected String query; protected String update; protected Class returnClass; // - current allocation state; // ENG NOTE -- uses 'long' internally for efficiency, allocation of super-big keys would require IntegralDataTypeHolder for these fields instead. protected long allocNext; protected long allocHi; protected IntegralDataTypeHolder resultFactory; // - statistics; mainly for unit testing. protected long statisticsTableAccessCount; // - private static final Logger log = Logger.getLogger( LinearBlockAllocator.class); public void configure (Type type, Properties params, Dialect dialect) { ObjectNameNormalizer normalizer = (ObjectNameNormalizer) params.get( IDENTIFIER_NORMALIZER); this.tableName = ConfigurationHelper.getString( ALLOC_TABLE, params, DEFAULT_TABLE); this.sequenceColumn = ConfigurationHelper.getString( SEQUENCE_COLUMN, params, DEFAULT_SEQUENCE_COLUMN); this.allocColumn = ConfigurationHelper.getString( ALLOC_COLUMN, params, DEFAULT_ALLOC_COLUMN); // get SequenceName; default to Entities' TableName. // - this.sequenceName = ConfigurationHelper.getString( SEQUENCE_NAME, params, params.getProperty(PersistentIdentifierGenerator.TABLE)); if (sequenceName == null) { throw new IdentifierGenerationException( "LinearBlockAllocator: '"+SEQUENCE_NAME+"' must be specified"); } this.blockSize = ConfigurationHelper.getInt( BLOCK_SIZE, params, DEFAULT_BLOCK_SIZE); if (blockSize < 1) { blockSize = 1; } // qualify Table Name, if necessary; // -- if (tableName.indexOf( '.') < 0) { String schemaName = normalizer.normalizeIdentifierQuoting( params.getProperty( SCHEMA)); String catalogName = normalizer.normalizeIdentifierQuoting( params.getProperty( CATALOG)); this.tableName = Table.qualify( catalogName, schemaName, tableName); } // build SQL strings; // -- use appendLockHint(LockMode) for now, as that is how Hibernate's generators do it. // this.query = "select " + allocColumn + " from " + dialect.appendLockHint( LockMode.PESSIMISTIC_WRITE, tableName) + " where " + sequenceColumn + " = ?" + dialect.getForUpdateString(); this.update = "update " + tableName + " set " + allocColumn + " = ? where " + sequenceColumn + " = ? and " + allocColumn + " = ?"; // setup Return Type & Result Holder. // -- this.returnType = type; this.returnClass = type.getReturnedClass(); this.resultFactory = IdentifierGeneratorHelper.getIntegralDataTypeHolder( returnClass); // done. } // ---------------------------------------------------------------------------------- /** allocate a Key; * - */ public synchronized Serializable generate (SessionImplementor session, Object object) throws HibernateException { if (allocNext >= allocHi) { if (log.isDebugEnabled()) log.debug( "allocating id block: " + tableName + ", " + sequenceName + ": blockSize=" + blockSize); long allocated = allocateBlock( session); // this.allocNext = allocated; this.allocHi = allocated + blockSize; if (log.isDebugEnabled()) log.debug( " allocated block: " + allocNext + "-" + allocHi); } long result = allocNext++; if (log.isDebugEnabled()) { log.debug( "allocated id: " + result); } // answer Result, as correct type; // -- use one single mutable instance 'Result Holder' instance as our Factory. // resultFactory.initialize( result); Number resultVal = resultFactory.makeValue(); // return resultVal; } protected long allocateBlock (final SessionImplementor session) { AbstractReturningWork work = new AbstractReturningWork() { @Override public Long execute (Connection conn) throws SQLException { final SqlStatementLogger statementLogger = session .getFactory() .getServiceRegistry() .getService( JdbcServices.class ) .getSqlStatementLogger(); long result; int rows; do { // The loop ensures atomicity of the // select + update even for no transaction // or read committed isolation level statementLogger.logStatement( query, FormatStyle.BASIC.getFormatter()); PreparedStatement qps = conn.prepareStatement( query); try { qps.setString( 1, sequenceName); ResultSet rs = qps.executeQuery(); if (!rs.next()) { String err = "could not read a hi value - you need to populate the table: " + tableName + ", " + sequenceName; log.error( err); throw new IdentifierGenerationException( err); } result = rs.getLong( 1); rs.close(); } catch (SQLException sqle) { log.error( "could not read a hi value", sqle); throw sqle; } finally { qps.close(); } statementLogger.logStatement( update, FormatStyle.BASIC.getFormatter()); PreparedStatement ups = conn.prepareStatement( update); try { ups.setLong( 1, result + blockSize); ups.setString( 2, sequenceName); ups.setLong( 3, result); rows = ups.executeUpdate(); } catch (SQLException sqle) { log.error( "could not update hi value in: " + tableName, sqle); throw sqle; } finally { ups.close(); } } while (rows == 0); // success; // -- allocated a Block. // statisticsTableAccessCount++; return new Long( result); } }; // perform in an isolated Transaction. long allocated = session.getTransactionCoordinator().getTransaction().createIsolationDelegate().delegateWork( work, true); return allocated; } // ---------------------------------------------------------------------------------- public String[] sqlCreateStrings (Dialect dialect) throws HibernateException { // create Sequence Table // SPEC - always output, skips with error if already exist in DB String tableStr = "create table " + dialect.quote(tableName) + " (" + dialect.quote(sequenceColumn) + " " + dialect.getTypeName( Types.VARCHAR, DEFAULT_SEQUENCE_COLUMNLENGTH, 0, 0) + ", " + dialect.quote(allocColumn) + " " + dialect.getTypeName( Types.BIGINT) + ", " + "primary key ("+dialect.quote(sequenceColumn)+")" + ")"; // create Row for this Sequence. String valueStr = "insert into " + tableName + "("+dialect.quote(sequenceColumn)+","+dialect.quote(allocColumn)+") values ( '" + sequenceName + "', "+blockSize+" )"; // done. return new String[]{ tableStr, valueStr}; } public String[] sqlDropStrings (Dialect dialect) { // delete Row for this Sequence. // String dropSequence = "delete from " + tableName + " where " + sequenceColumn + " = '" + sequenceName + "'"; return new String[]{ dropSequence}; } public Object generatorKey() { return "LinearBlockAllocator table="+tableName+", seq="+sequenceName; } // ---------------------------------------------------------------------------------- /** Statistics; Table Access Count. * @return */ public long getStats_TableAccessCount() { return statisticsTableAccessCount; } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy