HHH-9724 - More complete "temp table" coverage and allow Dialects to influence which strategy is used - initial work

This commit is contained in:
Steve Ebersole 2015-04-17 16:24:52 -05:00
parent 7ca12c7d80
commit 06b6135a11
12 changed files with 731 additions and 323 deletions

View File

@ -585,15 +585,11 @@ public class SessionFactoryBuilderImpl implements SessionFactoryBuilder {
configurationSettings.get( MULTI_TENANT_IDENTIFIER_RESOLVER )
);
this.multiTableBulkIdStrategy = strategySelector.resolveStrategy(
this.multiTableBulkIdStrategy = strategySelector.resolveDefaultableStrategy(
MultiTableBulkIdStrategy.class,
configurationSettings.get( HQL_BULK_ID_STRATEGY )
configurationSettings.get( HQL_BULK_ID_STRATEGY ),
jdbcServices.getJdbcEnvironment().getDialect().getDefaultMultiTableBulkIdStrategy()
);
if ( this.multiTableBulkIdStrategy == null ) {
this.multiTableBulkIdStrategy = jdbcServices.getDialect().supportsTemporaryTables()
? TemporaryTableBulkIdStrategy.INSTANCE
: new PersistentTableBulkIdStrategy();
}
this.batchFetchStyle = BatchFetchStyle.interpret( configurationSettings.get( BATCH_FETCH_STYLE ) );
this.defaultBatchFetchSize = ConfigurationHelper.getInt( DEFAULT_BATCH_FETCH_SIZE, configurationSettings, -1 );

View File

@ -97,6 +97,8 @@ import org.hibernate.event.internal.EntityCopyAllowedLoggedObserver;
import org.hibernate.event.internal.EntityCopyAllowedObserver;
import org.hibernate.event.internal.EntityCopyNotAllowedObserver;
import org.hibernate.event.spi.EntityCopyObserver;
import org.hibernate.hql.spi.GlobalTemporaryTableBulkIdStrategy;
import org.hibernate.hql.spi.LocalTemporaryTableBulkIdStrategy;
import org.hibernate.hql.spi.MultiTableBulkIdStrategy;
import org.hibernate.hql.spi.PersistentTableBulkIdStrategy;
import org.hibernate.hql.spi.TemporaryTableBulkIdStrategy;
@ -371,6 +373,16 @@ public class StrategySelectorBuilder {
PersistentTableBulkIdStrategy.SHORT_NAME,
PersistentTableBulkIdStrategy.class
);
strategySelector.registerStrategyImplementor(
MultiTableBulkIdStrategy.class,
GlobalTemporaryTableBulkIdStrategy.SHORT_NAME,
GlobalTemporaryTableBulkIdStrategy.class
);
strategySelector.registerStrategyImplementor(
MultiTableBulkIdStrategy.class,
LocalTemporaryTableBulkIdStrategy.SHORT_NAME,
LocalTemporaryTableBulkIdStrategy.class
);
strategySelector.registerStrategyImplementor(
MultiTableBulkIdStrategy.class,
TemporaryTableBulkIdStrategy.SHORT_NAME,

View File

@ -22,6 +22,7 @@
* Boston, MA 02110-1301 USA
*/
package org.hibernate.dialect;
import java.sql.CallableStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

View File

@ -58,6 +58,9 @@ import org.hibernate.exception.spi.ConversionContext;
import org.hibernate.exception.spi.SQLExceptionConversionDelegate;
import org.hibernate.exception.spi.SQLExceptionConverter;
import org.hibernate.exception.spi.ViolatedConstraintNameExtracter;
import org.hibernate.hql.spi.LocalTemporaryTableBulkIdStrategy;
import org.hibernate.hql.spi.MultiTableBulkIdStrategy;
import org.hibernate.hql.spi.PersistentTableBulkIdStrategy;
import org.hibernate.id.IdentityGenerator;
import org.hibernate.id.SequenceGenerator;
import org.hibernate.id.enhanced.SequenceStyleGenerator;
@ -1516,6 +1519,13 @@ public abstract class Dialect implements ConversionContext {
// temporary table support ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
public MultiTableBulkIdStrategy getDefaultMultiTableBulkIdStrategy() {
// mimic the old behavior for now...
return supportsTemporaryTables()
? LocalTemporaryTableBulkIdStrategy.INSTANCE
: new PersistentTableBulkIdStrategy();
}
/**
* Does this dialect support temporary tables?
*

View File

@ -49,6 +49,8 @@ import org.hibernate.exception.LockTimeoutException;
import org.hibernate.exception.spi.SQLExceptionConversionDelegate;
import org.hibernate.exception.spi.TemplatedViolatedConstraintNameExtracter;
import org.hibernate.exception.spi.ViolatedConstraintNameExtracter;
import org.hibernate.hql.spi.GlobalTemporaryTableBulkIdStrategy;
import org.hibernate.hql.spi.MultiTableBulkIdStrategy;
import org.hibernate.internal.util.JdbcExceptionHelper;
import org.hibernate.procedure.internal.StandardCallableStatementSupport;
import org.hibernate.procedure.spi.CallableStatementSupport;
@ -582,6 +584,11 @@ public class Oracle8iDialect extends Dialect {
return true;
}
@Override
public MultiTableBulkIdStrategy getDefaultMultiTableBulkIdStrategy() {
return new GlobalTemporaryTableBulkIdStrategy();
}
@Override
public boolean supportsTemporaryTables() {
return true;

View File

@ -37,6 +37,8 @@ import org.hibernate.dialect.function.StandardSQLFunction;
import org.hibernate.dialect.function.VarArgsSQLFunction;
import org.hibernate.exception.spi.TemplatedViolatedConstraintNameExtracter;
import org.hibernate.exception.spi.ViolatedConstraintNameExtracter;
import org.hibernate.hql.spi.GlobalTemporaryTableBulkIdStrategy;
import org.hibernate.hql.spi.MultiTableBulkIdStrategy;
import org.hibernate.internal.CoreMessageLogger;
import org.hibernate.internal.util.JdbcExceptionHelper;
import org.hibernate.type.StandardBasicTypes;
@ -351,6 +353,11 @@ public class Oracle9Dialect extends Dialect {
return true;
}
@Override
public MultiTableBulkIdStrategy getDefaultMultiTableBulkIdStrategy() {
return new GlobalTemporaryTableBulkIdStrategy();
}
@Override
public boolean supportsTemporaryTables() {
return true;

View File

@ -0,0 +1,165 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* Copyright (c) 2015, 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.hql.spi;
import java.sql.PreparedStatement;
import java.util.ArrayList;
import java.util.List;
import org.hibernate.boot.spi.MetadataImplementor;
import org.hibernate.engine.config.spi.ConfigurationService;
import org.hibernate.engine.config.spi.StandardConverters;
import org.hibernate.engine.jdbc.connections.spi.JdbcConnectionAccess;
import org.hibernate.engine.jdbc.spi.JdbcServices;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.engine.spi.SessionImplementor;
import org.hibernate.hql.internal.ast.HqlSqlWalker;
import org.hibernate.mapping.PersistentClass;
import org.hibernate.mapping.Table;
import org.hibernate.persister.entity.Queryable;
/**
* Strategy based on ANSI SQL's definition of a "global temporary table".
*
* @author Steve Ebersole
*/
public class GlobalTemporaryTableBulkIdStrategy implements MultiTableBulkIdStrategy {
public static final String CLEAN_UP_ID_TABLES = "hibernate.hql.bulk_id_strategy.global_temporary.clean_up";
public static final String SHORT_NAME = "global_temporary";
private boolean cleanUpTables;
private List<String> tableCleanUpDdl;
@Override
public void prepare(
JdbcServices jdbcServices,
JdbcConnectionAccess connectionAccess,
MetadataImplementor metadata) {
final ConfigurationService configService = metadata.getMetadataBuildingOptions()
.getServiceRegistry()
.getService( ConfigurationService.class );
this.cleanUpTables = configService.getSetting(
CLEAN_UP_ID_TABLES,
StandardConverters.BOOLEAN,
false
);
final List<Table> idTableDefinitions = new ArrayList<Table>();
for ( PersistentClass entityBinding : metadata.getEntityBindings() ) {
if ( !MultiTableBulkIdHelper.INSTANCE.needsIdTable( entityBinding ) ) {
continue;
}
final Table idTableDefinition = generateIdTableDefinition( entityBinding, metadata );
idTableDefinitions.add( idTableDefinition );
if ( cleanUpTables ) {
if ( tableCleanUpDdl == null ) {
tableCleanUpDdl = new ArrayList<String>();
}
tableCleanUpDdl.add( idTableDefinition.sqlDropString( jdbcServices.getDialect(), null, null ) );
}
}
// we export them all at once to better reuse JDBC resources
exportTableDefinitions( idTableDefinitions, jdbcServices, connectionAccess, metadata );
}
protected Table generateIdTableDefinition(PersistentClass entityMapping, MetadataImplementor metadata) {
return MultiTableBulkIdHelper.INSTANCE.generateIdTableDefinition(
entityMapping,
null,
null,
false
);
}
protected void exportTableDefinitions(
List<Table> idTableDefinitions,
JdbcServices jdbcServices,
JdbcConnectionAccess connectionAccess,
MetadataImplementor metadata) {
MultiTableBulkIdHelper.INSTANCE.exportTableDefinitions(
idTableDefinitions,
jdbcServices,
connectionAccess,
metadata
);
}
@Override
public void release(
JdbcServices jdbcServices,
JdbcConnectionAccess connectionAccess) {
if ( ! cleanUpTables ) {
return;
}
MultiTableBulkIdHelper.INSTANCE.cleanupTableDefinitions( jdbcServices, connectionAccess, tableCleanUpDdl );
}
@Override
public UpdateHandler buildUpdateHandler(
SessionFactoryImplementor factory, HqlSqlWalker walker) {
return new TableBasedUpdateHandlerImpl( factory, walker ) {
@Override
protected void releaseFromUse(Queryable persister, SessionImplementor session) {
// clean up our id-table rows
cleanUpRows( determineIdTableName( persister ), session );
}
};
}
private void cleanUpRows(String tableName, SessionImplementor session) {
final String sql = "delete from " + tableName;
PreparedStatement ps = null;
try {
ps = session.getTransactionCoordinator().getJdbcCoordinator().getStatementPreparer().prepareStatement( sql, false );
session.getTransactionCoordinator().getJdbcCoordinator().getResultSetReturn().executeUpdate( ps );
}
finally {
if ( ps != null ) {
try {
session.getTransactionCoordinator().getJdbcCoordinator().release( ps );
}
catch( Throwable ignore ) {
// ignore
}
}
}
}
@Override
public DeleteHandler buildDeleteHandler(
SessionFactoryImplementor factory, HqlSqlWalker walker) {
return new TableBasedDeleteHandlerImpl( factory, walker ) {
@Override
protected void releaseFromUse(Queryable persister, SessionImplementor session) {
// clean up our id-table rows
cleanUpRows( determineIdTableName( persister ), session );
}
};
}
}

View File

@ -0,0 +1,263 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* Copyright (c) 2015, 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.hql.spi;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLWarning;
import java.sql.Statement;
import org.hibernate.boot.spi.MetadataImplementor;
import org.hibernate.engine.jdbc.connections.spi.JdbcConnectionAccess;
import org.hibernate.engine.jdbc.spi.JdbcServices;
import org.hibernate.engine.jdbc.spi.SqlExceptionHelper;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.engine.spi.SessionImplementor;
import org.hibernate.hql.internal.ast.HqlSqlWalker;
import org.hibernate.internal.CoreMessageLogger;
import org.hibernate.jdbc.AbstractWork;
import org.hibernate.persister.entity.Queryable;
import org.jboss.logging.Logger;
/**
* Strategy based on ANSI SQL's definition of a "local temporary table" (local to each db session).
*
* @author Steve Ebersole
*/
public class LocalTemporaryTableBulkIdStrategy implements MultiTableBulkIdStrategy {
public static final LocalTemporaryTableBulkIdStrategy INSTANCE = new LocalTemporaryTableBulkIdStrategy();
public static final String SHORT_NAME = "temporary";
private static final CoreMessageLogger log = Logger.getMessageLogger(
CoreMessageLogger.class,
LocalTemporaryTableBulkIdStrategy.class.getName()
);
@Override
public void prepare(JdbcServices jdbcServices, JdbcConnectionAccess connectionAccess, MetadataImplementor metadata) {
// nothing to do
}
@Override
public void release(JdbcServices jdbcServices, JdbcConnectionAccess connectionAccess) {
// nothing to do
}
@Override
public UpdateHandler buildUpdateHandler(SessionFactoryImplementor factory, HqlSqlWalker walker) {
return new TableBasedUpdateHandlerImpl( factory, walker ) {
@Override
protected void prepareForUse(Queryable persister, SessionImplementor session) {
createTempTable( persister, session );
}
@Override
protected void releaseFromUse(Queryable persister, SessionImplementor session) {
releaseTempTable( persister, session );
}
};
}
@Override
public DeleteHandler buildDeleteHandler(SessionFactoryImplementor factory, HqlSqlWalker walker) {
return new TableBasedDeleteHandlerImpl( factory, walker ) {
@Override
protected void prepareForUse(Queryable persister, SessionImplementor session) {
createTempTable( persister, session );
}
@Override
protected void releaseFromUse(Queryable persister, SessionImplementor session) {
releaseTempTable( persister, session );
}
};
}
protected void createTempTable(Queryable persister, SessionImplementor session) {
// Don't really know all the codes required to adequately decipher returned jdbc exceptions here.
// simply allow the failure to be eaten and the subsequent insert-selects/deletes should fail
TemporaryTableCreationWork work = new TemporaryTableCreationWork( persister );
if ( shouldIsolateTemporaryTableDDL( session ) ) {
session.getTransactionCoordinator()
.getTransaction()
.createIsolationDelegate()
.delegateWork( work, shouldTransactIsolatedTemporaryTableDDL( session ) );
}
else {
final Connection connection = session.getTransactionCoordinator()
.getJdbcCoordinator()
.getLogicalConnection()
.getConnection();
work.execute( connection );
session.getTransactionCoordinator()
.getJdbcCoordinator()
.afterStatementExecution();
}
}
protected void releaseTempTable(Queryable persister, SessionImplementor session) {
if ( session.getFactory().getDialect().dropTemporaryTableAfterUse() ) {
TemporaryTableDropWork work = new TemporaryTableDropWork( persister, session );
if ( shouldIsolateTemporaryTableDDL( session ) ) {
session.getTransactionCoordinator()
.getTransaction()
.createIsolationDelegate()
.delegateWork( work, shouldTransactIsolatedTemporaryTableDDL( session ) );
}
else {
final Connection connection = session.getTransactionCoordinator()
.getJdbcCoordinator()
.getLogicalConnection()
.getConnection();
work.execute( connection );
session.getTransactionCoordinator()
.getJdbcCoordinator()
.afterStatementExecution();
}
}
else {
// at the very least cleanup the data :)
PreparedStatement ps = null;
try {
final String sql = "delete from " + persister.getTemporaryIdTableName();
ps = session.getTransactionCoordinator().getJdbcCoordinator().getStatementPreparer().prepareStatement( sql, false );
session.getTransactionCoordinator().getJdbcCoordinator().getResultSetReturn().executeUpdate( ps );
}
catch( Throwable t ) {
log.unableToCleanupTemporaryIdTable(t);
}
finally {
if ( ps != null ) {
try {
session.getTransactionCoordinator().getJdbcCoordinator().release( ps );
}
catch( Throwable ignore ) {
// ignore
}
}
}
}
}
protected boolean shouldIsolateTemporaryTableDDL(SessionImplementor session) {
Boolean dialectVote = session.getFactory().getDialect().performTemporaryTableDDLInIsolation();
if ( dialectVote != null ) {
return dialectVote;
}
return session.getFactory().getSettings().isDataDefinitionImplicitCommit();
}
protected boolean shouldTransactIsolatedTemporaryTableDDL(SessionImplementor session) {
// is there ever a time when it makes sense to do this?
// return session.getFactory().getSettings().isDataDefinitionInTransactionSupported();
return false;
}
private static class TemporaryTableCreationWork extends AbstractWork {
private final Queryable persister;
private TemporaryTableCreationWork(Queryable persister) {
this.persister = persister;
}
@Override
public void execute(Connection connection) {
try {
Statement statement = connection.createStatement();
try {
statement.executeUpdate( persister.getTemporaryIdTableDDL() );
persister.getFactory()
.getServiceRegistry()
.getService( JdbcServices.class )
.getSqlExceptionHelper()
.handleAndClearWarnings( statement, CREATION_WARNING_HANDLER );
}
finally {
try {
statement.close();
}
catch( Throwable ignore ) {
// ignore
}
}
}
catch( Exception e ) {
log.debug( "unable to create temporary id table [" + e.getMessage() + "]" );
}
}
}
private static SqlExceptionHelper.WarningHandler CREATION_WARNING_HANDLER = new SqlExceptionHelper.WarningHandlerLoggingSupport() {
public boolean doProcess() {
return log.isDebugEnabled();
}
public void prepare(SQLWarning warning) {
log.warningsCreatingTempTable( warning );
}
@Override
protected void logWarning(String description, String message) {
log.debug( description );
log.debug( message );
}
};
private static class TemporaryTableDropWork extends AbstractWork {
private final Queryable persister;
private final SessionImplementor session;
private TemporaryTableDropWork(Queryable persister, SessionImplementor session) {
this.persister = persister;
this.session = session;
}
@Override
public void execute(Connection connection) {
final String command = session.getFactory().getDialect().getDropTemporaryTableString()
+ ' ' + persister.getTemporaryIdTableName();
try {
Statement statement = connection.createStatement();
try {
statement.executeUpdate( command );
}
finally {
try {
statement.close();
}
catch( Throwable ignore ) {
// ignore
}
}
}
catch( Exception e ) {
log.warn( "unable to drop temporary id table after use [" + e.getMessage() + "]" );
}
}
}
}

View File

@ -0,0 +1,207 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* Copyright (c) 2015, 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.hql.spi;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Iterator;
import java.util.List;
import org.hibernate.boot.spi.MetadataImplementor;
import org.hibernate.engine.jdbc.connections.spi.JdbcConnectionAccess;
import org.hibernate.engine.jdbc.spi.JdbcServices;
import org.hibernate.mapping.Column;
import org.hibernate.mapping.JoinedSubclass;
import org.hibernate.mapping.PersistentClass;
import org.hibernate.mapping.RootClass;
import org.hibernate.mapping.Subclass;
import org.hibernate.mapping.Table;
import org.hibernate.mapping.UnionSubclass;
import org.jboss.logging.Logger;
/**
* @author Steve Ebersole
*/
public class MultiTableBulkIdHelper {
private static final Logger log = Logger.getLogger( MultiTableBulkIdHelper.class );
/**
* Singleton access
*/
public static final MultiTableBulkIdHelper INSTANCE = new MultiTableBulkIdHelper();
private MultiTableBulkIdHelper() {
}
public boolean needsIdTable(PersistentClass entityBinding) {
// need id table if the entity has secondary tables (joins)
if ( entityBinding.getJoinClosureSpan() > 0 ) {
return true;
}
// need an id table if the entity is part of either a JOINED or UNION inheritance
// hierarchy. We do not allow inheritance strategy mixing, so work on that assumption
// here...
final RootClass rootEntityBinding = entityBinding.getRootClass();
final Iterator itr = rootEntityBinding.getSubclassIterator();
if ( itr.hasNext() ) {
final Subclass subclassEntityBinding = (Subclass) itr.next();
if ( subclassEntityBinding instanceof JoinedSubclass || subclassEntityBinding instanceof UnionSubclass ) {
return true;
}
}
return false;
}
public Table generateIdTableDefinition(
PersistentClass entityMapping,
String catalog,
String schema,
boolean generateSessionIdColumn) {
Table idTable = new Table( entityMapping.getTemporaryIdTableName() );
idTable.setComment( "Used to hold id values for the " + entityMapping.getEntityName() + " class" );
if ( catalog != null ) {
idTable.setCatalog( catalog );
}
if ( schema != null ) {
idTable.setSchema( schema );
}
Iterator itr = entityMapping.getTable().getPrimaryKey().getColumnIterator();
while( itr.hasNext() ) {
Column column = (Column) itr.next();
idTable.addColumn( column.clone() );
}
if ( generateSessionIdColumn ) {
Column sessionIdColumn = new Column( "hib_sess_id" );
sessionIdColumn.setSqlType( "CHAR(36)" );
sessionIdColumn.setComment( "Used to hold the Hibernate Session identifier" );
idTable.addColumn( sessionIdColumn );
}
return idTable;
}
protected void exportTableDefinitions(
List<Table> idTableDefinitions,
JdbcServices jdbcServices,
JdbcConnectionAccess connectionAccess,
MetadataImplementor metadata) {
try {
Connection connection;
try {
connection = connectionAccess.obtainConnection();
}
catch (UnsupportedOperationException e) {
// assume this comes from org.hibernate.engine.jdbc.connections.internal.UserSuppliedConnectionProviderImpl
log.debug( "Unable to obtain JDBC connection; assuming ID tables already exist or wont be needed" );
return;
}
try {
// TODO: session.getTransactionCoordinator().getJdbcCoordinator().getStatementPreparer().createStatement();
Statement statement = connection.createStatement();
for ( Table idTableDefinition : idTableDefinitions ) {
try {
final String sql = idTableDefinition.sqlCreateString( jdbcServices.getDialect(), metadata, null, null );
jdbcServices.getSqlStatementLogger().logStatement( sql );
// TODO: ResultSetExtractor
statement.execute( sql );
}
catch (SQLException e) {
log.debugf( "Error attempting to export id-table [%s] : %s", idTableDefinition.getName(), e.getMessage() );
}
}
// TODO
// session.getTransactionCoordinator().getJdbcCoordinator().release( statement );
statement.close();
}
catch (SQLException e) {
log.error( "Unable to use JDBC Connection to create Statement", e );
}
finally {
try {
connectionAccess.releaseConnection( connection );
}
catch (SQLException ignore) {
}
}
}
catch (SQLException e) {
log.error( "Unable obtain JDBC Connection", e );
}
}
public void cleanupTableDefinitions(
JdbcServices jdbcServices,
JdbcConnectionAccess connectionAccess,
List<String> tableCleanUpDdl) {
if ( tableCleanUpDdl == null ) {
return;
}
try {
Connection connection = connectionAccess.obtainConnection();
try {
// TODO: session.getTransactionCoordinator().getJdbcCoordinator().getStatementPreparer().createStatement();
Statement statement = connection.createStatement();
for ( String cleanupDdl : tableCleanUpDdl ) {
try {
jdbcServices.getSqlStatementLogger().logStatement( cleanupDdl );
statement.execute( cleanupDdl );
}
catch (SQLException e) {
log.debugf( "Error attempting to cleanup id-table : [%s]", e.getMessage() );
}
}
// TODO
// session.getTransactionCoordinator().getJdbcCoordinator().release( statement );
statement.close();
}
catch (SQLException e) {
log.error( "Unable to use JDBC Connection to create Statement", e );
}
finally {
try {
connectionAccess.releaseConnection( connection );
}
catch (SQLException ignore) {
}
}
}
catch (SQLException e) {
log.error( "Unable obtain JDBC Connection", e );
}
}
}

View File

@ -23,13 +23,10 @@
*/
package org.hibernate.hql.spi;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Types;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import org.hibernate.HibernateException;
@ -45,7 +42,6 @@ import org.hibernate.engine.spi.SessionImplementor;
import org.hibernate.hql.internal.ast.HqlSqlWalker;
import org.hibernate.internal.AbstractSessionImpl;
import org.hibernate.internal.CoreMessageLogger;
import org.hibernate.mapping.Column;
import org.hibernate.mapping.PersistentClass;
import org.hibernate.mapping.Table;
import org.hibernate.persister.entity.Queryable;
@ -55,6 +51,10 @@ import org.hibernate.type.UUIDCharType;
import org.jboss.logging.Logger;
/**
* This is a strategy that mimics temporary tables for databases which do not support
* temporary tables. It follows a pattern similar to the ANSI SQL definition of global
* temporary table using a "session id" column to segment rows from the various sessions.
*
* @author Steve Ebersole
*/
public class PersistentTableBulkIdStrategy implements MultiTableBulkIdStrategy {
@ -74,6 +74,13 @@ public class PersistentTableBulkIdStrategy implements MultiTableBulkIdStrategy {
private boolean cleanUpTables;
private List<String> tableCleanUpDdl;
/**
* Creates the tables for all the entities that might need it
*
* @param jdbcServices The JdbcService object
* @param connectionAccess Access to the JDBC Connection
* @param metadata Access to the O/RM mapping information
*/
@Override
public void prepare(
JdbcServices jdbcServices,
@ -101,32 +108,31 @@ public class PersistentTableBulkIdStrategy implements MultiTableBulkIdStrategy {
final List<Table> idTableDefinitions = new ArrayList<Table>();
for ( PersistentClass entityBinding : metadata.getEntityBindings() ) {
if ( !MultiTableBulkIdHelper.INSTANCE.needsIdTable( entityBinding ) ) {
continue;
}
final Table idTableDefinition = generateIdTableDefinition( entityBinding, metadata );
idTableDefinitions.add( idTableDefinition );
if ( cleanUpTables ) {
if ( tableCleanUpDdl == null ) {
tableCleanUpDdl = new ArrayList<String>();
}
tableCleanUpDdl.add( idTableDefinition.sqlDropString( jdbcServices.getDialect(), null, null ) );
}
}
// we export them all at once to better reuse JDBC resources
exportTableDefinitions( idTableDefinitions, jdbcServices, connectionAccess, metadata );
}
protected Table generateIdTableDefinition(PersistentClass entityMapping, MetadataImplementor metadata) {
Table idTable = new Table( entityMapping.getTemporaryIdTableName() );
if ( catalog != null ) {
idTable.setCatalog( catalog );
}
if ( schema != null ) {
idTable.setSchema( schema );
}
Iterator itr = entityMapping.getTable().getPrimaryKey().getColumnIterator();
while( itr.hasNext() ) {
Column column = (Column) itr.next();
idTable.addColumn( column.clone() );
}
Column sessionIdColumn = new Column( "hib_sess_id" );
sessionIdColumn.setSqlType( "CHAR(36)" );
sessionIdColumn.setComment( "Used to hold the Hibernate Session identifier" );
idTable.addColumn( sessionIdColumn );
idTable.setComment( "Used to hold id values for the " + entityMapping.getEntityName() + " class" );
return idTable;
return MultiTableBulkIdHelper.INSTANCE.generateIdTableDefinition(
entityMapping,
catalog,
schema,
true
);
}
protected void exportTableDefinitions(
@ -134,99 +140,21 @@ public class PersistentTableBulkIdStrategy implements MultiTableBulkIdStrategy {
JdbcServices jdbcServices,
JdbcConnectionAccess connectionAccess,
MetadataImplementor metadata) {
try {
Connection connection;
try {
connection = connectionAccess.obtainConnection();
}
catch (UnsupportedOperationException e) {
// assume this comes from org.hibernate.engine.jdbc.connections.internal.UserSuppliedConnectionProviderImpl
log.debug( "Unable to obtain JDBC connection; assuming ID tables already exist or wont be needed" );
return;
}
try {
// TODO: session.getTransactionCoordinator().getJdbcCoordinator().getStatementPreparer().createStatement();
Statement statement = connection.createStatement();
for ( Table idTableDefinition : idTableDefinitions ) {
if ( cleanUpTables ) {
if ( tableCleanUpDdl == null ) {
tableCleanUpDdl = new ArrayList<String>();
}
tableCleanUpDdl.add( idTableDefinition.sqlDropString( jdbcServices.getDialect(), null, null ) );
}
try {
final String sql = idTableDefinition.sqlCreateString( jdbcServices.getDialect(), metadata, null, null );
jdbcServices.getSqlStatementLogger().logStatement( sql );
// TODO: ResultSetExtractor
statement.execute( sql );
}
catch (SQLException e) {
log.debugf( "Error attempting to export id-table [%s] : %s", idTableDefinition.getName(), e.getMessage() );
}
}
// TODO
// session.getTransactionCoordinator().getJdbcCoordinator().release( statement );
statement.close();
}
catch (SQLException e) {
log.error( "Unable to use JDBC Connection to create Statement", e );
}
finally {
try {
connectionAccess.releaseConnection( connection );
}
catch (SQLException ignore) {
}
}
}
catch (SQLException e) {
log.error( "Unable obtain JDBC Connection", e );
}
MultiTableBulkIdHelper.INSTANCE.exportTableDefinitions(
idTableDefinitions,
jdbcServices,
connectionAccess,
metadata
);
}
@Override
public void release(JdbcServices jdbcServices, JdbcConnectionAccess connectionAccess) {
if ( ! cleanUpTables || tableCleanUpDdl == null ) {
if ( ! cleanUpTables ) {
return;
}
try {
Connection connection = connectionAccess.obtainConnection();
try {
// TODO: session.getTransactionCoordinator().getJdbcCoordinator().getStatementPreparer().createStatement();
Statement statement = connection.createStatement();
for ( String cleanupDdl : tableCleanUpDdl ) {
try {
jdbcServices.getSqlStatementLogger().logStatement( cleanupDdl );
statement.execute( cleanupDdl );
}
catch (SQLException e) {
log.debugf( "Error attempting to cleanup id-table : [%s]", e.getMessage() );
}
}
// TODO
// session.getTransactionCoordinator().getJdbcCoordinator().release( statement );
statement.close();
}
catch (SQLException e) {
log.error( "Unable to use JDBC Connection to create Statement", e );
}
finally {
try {
connectionAccess.releaseConnection( connection );
}
catch (SQLException ignore) {
}
}
}
catch (SQLException e) {
log.error( "Unable obtain JDBC Connection", e );
}
MultiTableBulkIdHelper.INSTANCE.cleanupTableDefinitions( jdbcServices, connectionAccess, tableCleanUpDdl );
}
@Override

View File

@ -23,239 +23,42 @@
*/
package org.hibernate.hql.spi;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLWarning;
import java.sql.Statement;
import org.hibernate.boot.spi.MetadataImplementor;
import org.hibernate.engine.jdbc.connections.spi.JdbcConnectionAccess;
import org.hibernate.engine.jdbc.spi.JdbcServices;
import org.hibernate.engine.jdbc.spi.SqlExceptionHelper;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.engine.spi.SessionImplementor;
import org.hibernate.hql.internal.ast.HqlSqlWalker;
import org.hibernate.internal.CoreMessageLogger;
import org.hibernate.jdbc.AbstractWork;
import org.hibernate.persister.entity.Queryable;
import org.jboss.logging.Logger;
import org.hibernate.internal.log.DeprecationLogger;
/**
* @author Steve Ebersole
*
* @deprecated Use the more specific {@link org.hibernate.hql.spi.LocalTemporaryTableBulkIdStrategy} instead.
*/
public class TemporaryTableBulkIdStrategy implements MultiTableBulkIdStrategy {
@Deprecated
public class TemporaryTableBulkIdStrategy extends LocalTemporaryTableBulkIdStrategy {
public static final TemporaryTableBulkIdStrategy INSTANCE = new TemporaryTableBulkIdStrategy();
public static final String SHORT_NAME = "temporary";
private static final CoreMessageLogger log = Logger.getMessageLogger(
CoreMessageLogger.class,
TemporaryTableBulkIdStrategy.class.getName()
);
@Override
public void prepare(JdbcServices jdbcServices, JdbcConnectionAccess connectionAccess, MetadataImplementor metadata) {
// nothing to do
DeprecationLogger.DEPRECATION_LOGGER.logDeprecationOfTemporaryTableBulkIdStrategy();
super.prepare( jdbcServices, connectionAccess, metadata );
}
@Override
public void release(JdbcServices jdbcServices, JdbcConnectionAccess connectionAccess) {
// nothing to do
super.release( jdbcServices, connectionAccess );
}
@Override
public UpdateHandler buildUpdateHandler(SessionFactoryImplementor factory, HqlSqlWalker walker) {
return new TableBasedUpdateHandlerImpl( factory, walker ) {
@Override
protected void prepareForUse(Queryable persister, SessionImplementor session) {
createTempTable( persister, session );
}
@Override
protected void releaseFromUse(Queryable persister, SessionImplementor session) {
releaseTempTable( persister, session );
}
};
return super.buildUpdateHandler( factory, walker );
}
@Override
public DeleteHandler buildDeleteHandler(SessionFactoryImplementor factory, HqlSqlWalker walker) {
return new TableBasedDeleteHandlerImpl( factory, walker ) {
@Override
protected void prepareForUse(Queryable persister, SessionImplementor session) {
createTempTable( persister, session );
}
@Override
protected void releaseFromUse(Queryable persister, SessionImplementor session) {
releaseTempTable( persister, session );
}
};
return super.buildDeleteHandler( factory, walker );
}
protected void createTempTable(Queryable persister, SessionImplementor session) {
// Don't really know all the codes required to adequately decipher returned jdbc exceptions here.
// simply allow the failure to be eaten and the subsequent insert-selects/deletes should fail
TemporaryTableCreationWork work = new TemporaryTableCreationWork( persister );
if ( shouldIsolateTemporaryTableDDL( session ) ) {
session.getTransactionCoordinator()
.getTransaction()
.createIsolationDelegate()
.delegateWork( work, shouldTransactIsolatedTemporaryTableDDL( session ) );
}
else {
final Connection connection = session.getTransactionCoordinator()
.getJdbcCoordinator()
.getLogicalConnection()
.getConnection();
work.execute( connection );
session.getTransactionCoordinator()
.getJdbcCoordinator()
.afterStatementExecution();
}
}
protected void releaseTempTable(Queryable persister, SessionImplementor session) {
if ( session.getFactory().getDialect().dropTemporaryTableAfterUse() ) {
TemporaryTableDropWork work = new TemporaryTableDropWork( persister, session );
if ( shouldIsolateTemporaryTableDDL( session ) ) {
session.getTransactionCoordinator()
.getTransaction()
.createIsolationDelegate()
.delegateWork( work, shouldTransactIsolatedTemporaryTableDDL( session ) );
}
else {
final Connection connection = session.getTransactionCoordinator()
.getJdbcCoordinator()
.getLogicalConnection()
.getConnection();
work.execute( connection );
session.getTransactionCoordinator()
.getJdbcCoordinator()
.afterStatementExecution();
}
}
else {
// at the very least cleanup the data :)
PreparedStatement ps = null;
try {
final String sql = "delete from " + persister.getTemporaryIdTableName();
ps = session.getTransactionCoordinator().getJdbcCoordinator().getStatementPreparer().prepareStatement( sql, false );
session.getTransactionCoordinator().getJdbcCoordinator().getResultSetReturn().executeUpdate( ps );
}
catch( Throwable t ) {
log.unableToCleanupTemporaryIdTable(t);
}
finally {
if ( ps != null ) {
try {
session.getTransactionCoordinator().getJdbcCoordinator().release( ps );
}
catch( Throwable ignore ) {
// ignore
}
}
}
}
}
protected boolean shouldIsolateTemporaryTableDDL(SessionImplementor session) {
Boolean dialectVote = session.getFactory().getDialect().performTemporaryTableDDLInIsolation();
if ( dialectVote != null ) {
return dialectVote;
}
return session.getFactory().getSettings().isDataDefinitionImplicitCommit();
}
protected boolean shouldTransactIsolatedTemporaryTableDDL(SessionImplementor session) {
// is there ever a time when it makes sense to do this?
// return session.getFactory().getSettings().isDataDefinitionInTransactionSupported();
return false;
}
private static class TemporaryTableCreationWork extends AbstractWork {
private final Queryable persister;
private TemporaryTableCreationWork(Queryable persister) {
this.persister = persister;
}
@Override
public void execute(Connection connection) {
try {
Statement statement = connection.createStatement();
try {
statement.executeUpdate( persister.getTemporaryIdTableDDL() );
persister.getFactory()
.getServiceRegistry()
.getService( JdbcServices.class )
.getSqlExceptionHelper()
.handleAndClearWarnings( statement, CREATION_WARNING_HANDLER );
}
finally {
try {
statement.close();
}
catch( Throwable ignore ) {
// ignore
}
}
}
catch( Exception e ) {
log.debug( "unable to create temporary id table [" + e.getMessage() + "]" );
}
}
}
private static SqlExceptionHelper.WarningHandler CREATION_WARNING_HANDLER = new SqlExceptionHelper.WarningHandlerLoggingSupport() {
public boolean doProcess() {
return log.isDebugEnabled();
}
public void prepare(SQLWarning warning) {
log.warningsCreatingTempTable( warning );
}
@Override
protected void logWarning(String description, String message) {
log.debug( description );
log.debug( message );
}
};
private static class TemporaryTableDropWork extends AbstractWork {
private final Queryable persister;
private final SessionImplementor session;
private TemporaryTableDropWork(Queryable persister, SessionImplementor session) {
this.persister = persister;
this.session = session;
}
@Override
public void execute(Connection connection) {
final String command = session.getFactory().getDialect().getDropTemporaryTableString()
+ ' ' + persister.getTemporaryIdTableName();
try {
Statement statement = connection.createStatement();
try {
statement.executeUpdate( command );
}
finally {
try {
statement.close();
}
catch( Throwable ignore ) {
// ignore
}
}
}
catch( Exception e ) {
log.warn( "unable to drop temporary id table after use [" + e.getMessage() + "]" );
}
}
}
}

View File

@ -151,4 +151,13 @@ public interface DeprecationLogger {
id = 90000010
)
void deprecatedManyToManyFetch();
@LogMessage(level = WARN)
@Message(
value = "org.hibernate.hql.spi.TemporaryTableBulkIdStrategy (temporary) has been deprecated in favor of the" +
" more specific org.hibernate.hql.spi.LocalTemporaryTableBulkIdStrategy (local_temporary).",
id = 90000010
)
void logDeprecationOfTemporaryTableBulkIdStrategy();
}