/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.id;
import java.io.Serializable;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;
import java.util.Properties;
import org.hibernate.HibernateException;
import org.hibernate.LockMode;
import org.hibernate.MappingException;
import org.hibernate.boot.model.naming.Identifier;
import org.hibernate.boot.model.relational.Database;
import org.hibernate.boot.model.relational.Namespace;
import org.hibernate.boot.model.relational.QualifiedName;
import org.hibernate.boot.model.relational.QualifiedNameParser;
import org.hibernate.dialect.Dialect;
import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment;
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.SessionEventListenerManager;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.id.enhanced.AccessCallback;
import org.hibernate.id.enhanced.LegacyHiLoAlgorithmOptimizer;
import org.hibernate.internal.CoreLogging;
import org.hibernate.internal.CoreMessageLogger;
import org.hibernate.internal.log.DeprecationLogger;
import org.hibernate.internal.util.config.ConfigurationHelper;
import org.hibernate.jdbc.AbstractReturningWork;
import org.hibernate.jdbc.WorkExecutorVisitable;
import org.hibernate.mapping.Column;
import org.hibernate.mapping.PrimaryKey;
import org.hibernate.mapping.Table;
import org.hibernate.service.ServiceRegistry;
import org.hibernate.type.LongType;
import org.hibernate.type.StringType;
import org.hibernate.type.Type;
A hilo IdentifierGenerator that returns a Long, constructed using
a hi/lo algorithm. The hi value MUST be fetched in a seperate transaction
to the Session transaction so the generator must be able to obtain
a new connection and commit it. Hence this implementation may not
be used when the user is supplying connections. In this
case a SequenceHiLoGenerator would be a better choice (where
supported).
A hilo IdentifierGenerator that uses a database
table to store the last generated values. A table can contains
several hi values. They are distinct from each other through a key
This implementation is not compliant with a user connection
Allowed parameters (all of them are optional):
- table: table name (default hibernate_sequences)
- primary_key_column: key column name (default sequence_name)
- value_column: hi value column name(default sequence_next_hi_value)
- primary_key_value: key value for the current entity (default to the entity's primary table name)
- primary_key_length: length of the key column in DB represented as a varchar (default to 255)
- max_lo: max low value before increasing hi (default to Short.MAX_VALUE)
Author: Emmanuel Bernard, Klaus Richarz. Deprecated: Use TableGenerator
instead.
/**
* A hilo <tt>IdentifierGenerator</tt> that returns a <tt>Long</tt>, constructed using
* a hi/lo algorithm. The hi value MUST be fetched in a seperate transaction
* to the <tt>Session</tt> transaction so the generator must be able to obtain
* a new connection and commit it. Hence this implementation may not
* be used when the user is supplying connections. In this
* case a <tt>SequenceHiLoGenerator</tt> would be a better choice (where
* supported).<br>
* <br>
* <p/>
* A hilo <tt>IdentifierGenerator</tt> that uses a database
* table to store the last generated values. A table can contains
* several hi values. They are distinct from each other through a key
* <p/>
* <p>This implementation is not compliant with a user connection</p>
* <p/>
* <p/>
* <p>Allowed parameters (all of them are optional):</p>
* <ul>
* <li>table: table name (default <tt>hibernate_sequences</tt>)</li>
* <li>primary_key_column: key column name (default <tt>sequence_name</tt>)</li>
* <li>value_column: hi value column name(default <tt>sequence_next_hi_value</tt>)</li>
* <li>primary_key_value: key value for the current entity (default to the entity's primary table name)</li>
* <li>primary_key_length: length of the key column in DB represented as a varchar (default to 255)</li>
* <li>max_lo: max low value before increasing hi (default to Short.MAX_VALUE)</li>
* </ul>
*
* @author Emmanuel Bernard
* @author <a href="mailto:kr@hbt.de">Klaus Richarz</a>.
*
* @deprecated Use {@link org.hibernate.id.enhanced.TableGenerator} instead.
*/
@Deprecated
public class MultipleHiLoPerTableGenerator implements PersistentIdentifierGenerator, Configurable {
private static final CoreMessageLogger LOG = CoreLogging.messageLogger( MultipleHiLoPerTableGenerator.class );
public static final String ID_TABLE = "table";
public static final String PK_COLUMN_NAME = "primary_key_column";
public static final String PK_VALUE_NAME = "primary_key_value";
public static final String VALUE_COLUMN_NAME = "value_column";
public static final String PK_LENGTH_NAME = "primary_key_length";
private static final int DEFAULT_PK_LENGTH = 255;
public static final String DEFAULT_TABLE = "hibernate_sequences";
private static final String DEFAULT_PK_COLUMN = "sequence_name";
private static final String DEFAULT_VALUE_COLUMN = "sequence_next_hi_value";
private QualifiedName qualifiedTableName;
private String tableName;
private String segmentColumnName;
private String segmentName;
private String valueColumnName;
private String query;
private String insert;
private String update;
//hilo params
public static final String MAX_LO = "max_lo";
private int maxLo;
private LegacyHiLoAlgorithmOptimizer hiloOptimizer;
private Class returnClass;
private int keySize;
public synchronized Serializable generate(final SharedSessionContractImplementor session, Object obj) {
DeprecationLogger.DEPRECATION_LOGGER.deprecatedTableGenerator( getClass().getName() );
final SqlStatementLogger statementLogger = session.getFactory().getServiceRegistry()
.getService( JdbcServices.class )
.getSqlStatementLogger();
final SessionEventListenerManager statsCollector = session.getEventListenerManager();
final WorkExecutorVisitable<IntegralDataTypeHolder> work = new AbstractReturningWork<IntegralDataTypeHolder>() {
@Override
public IntegralDataTypeHolder execute(Connection connection) throws SQLException {
IntegralDataTypeHolder value = IdentifierGeneratorHelper.getIntegralDataTypeHolder( returnClass );
int rows;
do {
final PreparedStatement queryPreparedStatement = prepareStatement(
connection,
query,
statementLogger,
statsCollector
);
try {
final ResultSet rs = executeQuery( queryPreparedStatement, statsCollector );
boolean isInitialized = rs.next();
if ( !isInitialized ) {
value.initialize( 0 );
final PreparedStatement insertPreparedStatement = prepareStatement(
connection,
insert,
statementLogger,
statsCollector
);
try {
value.bind( insertPreparedStatement, 1 );
executeUpdate( insertPreparedStatement, statsCollector );
}
finally {
insertPreparedStatement.close();
}
}
else {
value.initialize( rs, 0 );
}
rs.close();
}
catch (SQLException sqle) {
LOG.unableToReadOrInitHiValue( sqle );
throw sqle;
}
finally {
queryPreparedStatement.close();
}
final PreparedStatement updatePreparedStatement = prepareStatement(
connection,
update,
statementLogger,
statsCollector
);
try {
value.copy().increment().bind( updatePreparedStatement, 1 );
value.bind( updatePreparedStatement, 2 );
rows = executeUpdate( updatePreparedStatement, statsCollector );
}
catch (SQLException sqle) {
LOG.error( LOG.unableToUpdateHiValue( tableName ), sqle );
throw sqle;
}
finally {
updatePreparedStatement.close();
}
} while ( rows == 0 );
return value;
}
};
// maxLo < 1 indicates a hilo generator with no hilo :?
if ( maxLo < 1 ) {
//keep the behavior consistent even for boundary usages
IntegralDataTypeHolder value = null;
while ( value == null || value.lt( 1 ) ) {
value = session.getTransactionCoordinator().createIsolationDelegate().delegateWork( work, true );
}
return value.makeValue();
}
return hiloOptimizer.generate(
new AccessCallback() {
public IntegralDataTypeHolder getNextValue() {
return session.getTransactionCoordinator().createIsolationDelegate().delegateWork(
work,
true
);
}
@Override
public String getTenantIdentifier() {
return session.getTenantIdentifier();
}
}
);
}
private PreparedStatement prepareStatement(
Connection connection,
String sql,
SqlStatementLogger statementLogger,
SessionEventListenerManager statsCollector) throws SQLException {
statementLogger.logStatement( sql, FormatStyle.BASIC.getFormatter() );
try {
statsCollector.jdbcPrepareStatementStart();
return connection.prepareStatement( sql );
}
finally {
statsCollector.jdbcPrepareStatementEnd();
}
}
private int executeUpdate(PreparedStatement ps, SessionEventListenerManager statsCollector) throws SQLException {
try {
statsCollector.jdbcExecuteStatementStart();
return ps.executeUpdate();
}
finally {
statsCollector.jdbcExecuteStatementEnd();
}
}
private ResultSet executeQuery(PreparedStatement ps, SessionEventListenerManager statsCollector)
throws SQLException {
try {
statsCollector.jdbcExecuteStatementStart();
return ps.executeQuery();
}
finally {
statsCollector.jdbcExecuteStatementEnd();
}
}
@SuppressWarnings({"StatementWithEmptyBody", "deprecation"})
public void configure(Type type, Properties params, ServiceRegistry serviceRegistry) throws MappingException {
returnClass = type.getReturnedClass();
final JdbcEnvironment jdbcEnvironment = serviceRegistry.getService( JdbcEnvironment.class );
qualifiedTableName = determineGeneratorTableName( params, jdbcEnvironment );
segmentColumnName = determineSegmentColumnName( params, jdbcEnvironment );
keySize = ConfigurationHelper.getInt( PK_LENGTH_NAME, params, DEFAULT_PK_LENGTH );
segmentName = ConfigurationHelper.getString( PK_VALUE_NAME, params, params.getProperty( TABLE ) );
valueColumnName = determineValueColumnName( params, jdbcEnvironment );
//hilo config
maxLo = ConfigurationHelper.getInt( MAX_LO, params, Short.MAX_VALUE );
if ( maxLo >= 1 ) {
hiloOptimizer = new LegacyHiLoAlgorithmOptimizer( returnClass, maxLo );
}
}
protected QualifiedName determineGeneratorTableName(Properties params, JdbcEnvironment jdbcEnvironment) {
final String tableName = ConfigurationHelper.getString( ID_TABLE, params, DEFAULT_TABLE );
if ( tableName.contains( "." ) ) {
return QualifiedNameParser.INSTANCE.parse( tableName );
}
else {
// todo : need to incorporate implicit catalog and schema names
final Identifier catalog = jdbcEnvironment.getIdentifierHelper().toIdentifier(
ConfigurationHelper.getString( CATALOG, params )
);
final Identifier schema = jdbcEnvironment.getIdentifierHelper().toIdentifier(
ConfigurationHelper.getString( SCHEMA, params )
);
return new QualifiedNameParser.NameParts(
catalog,
schema,
jdbcEnvironment.getIdentifierHelper().toIdentifier( tableName )
);
}
}
protected String determineSegmentColumnName(Properties params, JdbcEnvironment jdbcEnvironment) {
final String name = ConfigurationHelper.getString( PK_COLUMN_NAME, params, DEFAULT_PK_COLUMN );
return jdbcEnvironment.getIdentifierHelper().toIdentifier( name ).render( jdbcEnvironment.getDialect() );
}
protected String determineValueColumnName(Properties params, JdbcEnvironment jdbcEnvironment) {
final String name = ConfigurationHelper.getString( VALUE_COLUMN_NAME, params, DEFAULT_VALUE_COLUMN );
return jdbcEnvironment.getIdentifierHelper().toIdentifier( name ).render( jdbcEnvironment.getDialect() );
}
@Override
public void registerExportables(Database database) {
final Namespace namespace = database.locateNamespace(
qualifiedTableName.getCatalogName(),
qualifiedTableName.getSchemaName()
);
Table table = namespace.locateTable( qualifiedTableName.getObjectName() );
if ( table == null ) {
table = namespace.createTable( qualifiedTableName.getObjectName(), false );
// todo : note sure the best solution here. do we add the columns if missing? other?
table.setPrimaryKey( new PrimaryKey( table ) );
final Column pkColumn = new ExportableColumn(
database,
table,
segmentColumnName,
StringType.INSTANCE,
database.getDialect().getTypeName( Types.VARCHAR, keySize, 0, 0 )
);
pkColumn.setNullable( false );
table.addColumn( pkColumn );
table.getPrimaryKey().addColumn( pkColumn );
final Column valueColumn = new ExportableColumn(
database,
table,
valueColumnName,
LongType.INSTANCE
);
table.addColumn( valueColumn );
}
final JdbcEnvironment jdbcEnvironment = database.getJdbcEnvironment();
// allow physical naming strategies a chance to kick in
tableName = jdbcEnvironment.getQualifiedObjectNameFormatter().format(
table.getQualifiedTableName(),
jdbcEnvironment.getDialect()
);
query = "select " +
valueColumnName +
" from " +
jdbcEnvironment.getDialect().appendLockHint( LockMode.PESSIMISTIC_WRITE, tableName ) +
" where " + segmentColumnName + " = '" + segmentName + "'" +
jdbcEnvironment.getDialect().getForUpdateString();
update = "update " +
tableName +
" set " +
valueColumnName +
" = ? where " +
valueColumnName +
" = ? and " +
segmentColumnName +
" = '" +
segmentName
+ "'";
insert = "insert into " + tableName +
"(" + segmentColumnName + ", " + valueColumnName + ") " +
"values('" + segmentName + "', ?)";
}
public String[] sqlCreateStrings(Dialect dialect) throws HibernateException {
return new String[] {
dialect.getCreateTableString()
+ ' ' + tableName + " ( "
+ segmentColumnName + ' ' + dialect.getTypeName( Types.VARCHAR, keySize, 0, 0 ) + ", "
+ valueColumnName + ' ' + dialect.getTypeName( Types.INTEGER )
+ " )" + dialect.getTableTypeString()
};
}
public String[] sqlDropStrings(Dialect dialect) throws HibernateException {
return new String[] {dialect.getDropTableString( tableName )};
}
public Object generatorKey() {
return tableName;
}
}