/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* Copyright (c) 2008-2011, Red Hat Inc. or third-party contributors as
* indicated by the @author tags or express copyright attribution
* statements applied by the authors. All third-party contributions are
* distributed under license by Red Hat Inc.
*
* 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
*/
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.engine.spi.SessionImplementor;
import org.hibernate.internal.CoreMessageLogger;
import org.hibernate.LockMode;
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.internal.util.config.ConfigurationHelper;
import org.hibernate.jdbc.AbstractReturningWork;
import org.hibernate.mapping.Table;
import org.hibernate.type.Type;
import org.jboss.logging.Logger;
/**
* An <tt>IdentifierGenerator</tt> that uses a database
* table to store the last generated value. It is not
* intended that applications use this strategy directly.
* However, it may be used to build other (efficient)
* strategies. The returned type is any supported by
* {@link IntegralDataTypeHolder}
* <p/>
* The value MUST be fetched in a separate transaction
* from that of the main {@link SessionImplementor session}
* transaction so the generator must be able to obtain a new
* connection and commit it. Hence this implementation may only
* be used when Hibernate is fetching connections, not when the
* user is supplying connections.
* <p/>
* Again, the return types supported here are any of the ones
* supported by {@link IntegralDataTypeHolder}. This is new
* as of 3.5. Prior to that this generator only returned {@link Integer}
* values.
* <p/>
* Mapping parameters supported: table, column
*
* @see TableHiLoGenerator
* @author Gavin King
*/
public class TableGenerator implements PersistentIdentifierGenerator, Configurable {
/* COLUMN and TABLE should be renamed but it would break the public API */
/** The column parameter */
public static final String COLUMN = "column";
/** Default column name */
public static final String DEFAULT_COLUMN_NAME = "next_hi";
/** The table parameter */
public static final String TABLE = "table";
/** Default table name */
public static final String DEFAULT_TABLE_NAME = "hibernate_unique_key";
private static final CoreMessageLogger LOG = Logger.getMessageLogger(CoreMessageLogger.class, TableGenerator.class.getName());
private Type identifierType;
private String tableName;
private String columnName;
private String query;
private String update;
public void configure(Type type, Properties params, Dialect dialect) {
identifierType = type;
ObjectNameNormalizer normalizer = ( ObjectNameNormalizer ) params.get( IDENTIFIER_NORMALIZER );
tableName = ConfigurationHelper.getString( TABLE, params, DEFAULT_TABLE_NAME );
if ( tableName.indexOf( '.' ) < 0 ) {
final String schemaName = normalizer.normalizeIdentifierQuoting( params.getProperty( SCHEMA ) );
final String catalogName = normalizer.normalizeIdentifierQuoting( params.getProperty( CATALOG ) );
tableName = Table.qualify(
dialect.quote( catalogName ),
dialect.quote( schemaName ),
dialect.quote( tableName )
);
}
else {
// if already qualified there is not much we can do in a portable manner so we pass it
// through and assume the user has set up the name correctly.
}
columnName = dialect.quote(
normalizer.normalizeIdentifierQuoting(
ConfigurationHelper.getString( COLUMN, params, DEFAULT_COLUMN_NAME )
)
);
query = "select " +
columnName +
" from " +
dialect.appendLockHint(LockMode.PESSIMISTIC_WRITE, tableName) +
dialect.getForUpdateString();
update = "update " +
tableName +
" set " +
columnName +
" = ? where " +
columnName +
" = ?";
}
public synchronized Serializable generate(SessionImplementor session, Object object) {
return generateHolder( session ).makeValue();
}
protected IntegralDataTypeHolder generateHolder(SessionImplementor session) {
final SqlStatementLogger statementLogger = session
.getFactory()
.getServiceRegistry()
.getService( JdbcServices.class )
.getSqlStatementLogger();
return session.getTransactionCoordinator().getTransaction().createIsolationDelegate().delegateWork(
new AbstractReturningWork<IntegralDataTypeHolder>() {
@Override
public IntegralDataTypeHolder execute(Connection connection) throws SQLException {
IntegralDataTypeHolder value = buildHolder();
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 = connection.prepareStatement( query );
try {
ResultSet rs = qps.executeQuery();
if ( !rs.next() ) {
String err = "could not read a hi value - you need to populate the table: " + tableName;
LOG.error(err);
throw new IdentifierGenerationException(err);
}
value.initialize( rs, 1 );
rs.close();
}
catch (SQLException e) {
LOG.error("Could not read a hi value", e);
throw e;
}
finally {
qps.close();
}
statementLogger.logStatement( update, FormatStyle.BASIC.getFormatter() );
PreparedStatement ups = connection.prepareStatement(update);
try {
value.copy().increment().bind( ups, 1 );
value.bind( ups, 2 );
rows = ups.executeUpdate();
}
catch (SQLException sqle) {
LOG.error(LOG.unableToUpdateHiValue(tableName), sqle);
throw sqle;
}
finally {
ups.close();
}
}
while (rows==0);
return value;
}
},
true
);
}
public String[] sqlCreateStrings(Dialect dialect) throws HibernateException {
return new String[] {
dialect.getCreateTableString() + " " + tableName + " ( " + columnName + " " + dialect.getTypeName(Types.INTEGER) + " )",
"insert into " + tableName + " values ( 0 )"
};
}
public String[] sqlDropStrings(Dialect dialect) {
StringBuffer sqlDropString = new StringBuffer( "drop table " );
if ( dialect.supportsIfExistsBeforeTableName() ) {
sqlDropString.append( "if exists " );
}
sqlDropString.append( tableName ).append( dialect.getCascadeConstraintsString() );
if ( dialect.supportsIfExistsAfterTableName() ) {
sqlDropString.append( " if exists" );
}
return new String[] { sqlDropString.toString() };
}
public Object generatorKey() {
return tableName;
}
protected IntegralDataTypeHolder buildHolder() {
return IdentifierGeneratorHelper.getIntegralDataTypeHolder( identifierType.getReturnedClass() );
}
}