diff --git a/hibernate-core/src/main/java/org/hibernate/action/internal/BulkOperationCleanupAction.java b/hibernate-core/src/main/java/org/hibernate/action/internal/BulkOperationCleanupAction.java index 43d9b5acdb..f8917f4653 100644 --- a/hibernate-core/src/main/java/org/hibernate/action/internal/BulkOperationCleanupAction.java +++ b/hibernate-core/src/main/java/org/hibernate/action/internal/BulkOperationCleanupAction.java @@ -72,7 +72,7 @@ public class BulkOperationCleanupAction implements Executable, Serializable { * @param session The session to which this request is tied. * @param affectedQueryables The affected entity persisters. */ - public BulkOperationCleanupAction(SessionImplementor session, Queryable[] affectedQueryables) { + public BulkOperationCleanupAction(SessionImplementor session, Queryable... affectedQueryables) { SessionFactoryImplementor factory = session.getFactory(); LinkedHashSet spacesList = new LinkedHashSet(); for ( Queryable persister : affectedQueryables ) { diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/AvailableSettings.java b/hibernate-core/src/main/java/org/hibernate/cfg/AvailableSettings.java index 4a55bb0c36..c1aee82ac7 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/AvailableSettings.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/AvailableSettings.java @@ -594,4 +594,6 @@ public interface AvailableSettings { public static final String FORCE_DISCRIMINATOR_IN_SELECTS_BY_DEFAULT = "hibernate.discriminator.force_in_select"; public static final String ENABLE_LAZY_LOAD_NO_TRANS = "hibernate.enable_lazy_load_no_trans"; + + public static final String HQL_BULK_ID_STRATEGY = "hibernate.hql.bulk_id_strategy"; } diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/Settings.java b/hibernate-core/src/main/java/org/hibernate/cfg/Settings.java index 3bcb3065c1..ccc4ab46ee 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/Settings.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/Settings.java @@ -30,6 +30,7 @@ import org.hibernate.EntityMode; import org.hibernate.MultiTenancyStrategy; import org.hibernate.cache.spi.QueryCacheFactory; import org.hibernate.cache.spi.RegionFactory; +import org.hibernate.hql.spi.MultiTableBulkIdStrategy; import org.hibernate.hql.spi.QueryTranslatorFactory; import org.hibernate.service.jta.platform.spi.JtaPlatform; import org.hibernate.tuple.entity.EntityTuplizerFactory; @@ -89,6 +90,9 @@ public final class Settings { private JtaPlatform jtaPlatform; + private MultiTableBulkIdStrategy multiTableBulkIdStrategy; + + /** * Package protected constructor */ @@ -468,4 +472,12 @@ public final class Settings { void setInitializeLazyStateOutsideTransactions(boolean initializeLazyStateOutsideTransactions) { this.initializeLazyStateOutsideTransactions = initializeLazyStateOutsideTransactions; } + + public MultiTableBulkIdStrategy getMultiTableBulkIdStrategy() { + return multiTableBulkIdStrategy; + } + + void setMultiTableBulkIdStrategy(MultiTableBulkIdStrategy multiTableBulkIdStrategy) { + this.multiTableBulkIdStrategy = multiTableBulkIdStrategy; + } } diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/SettingsFactory.java b/hibernate-core/src/main/java/org/hibernate/cfg/SettingsFactory.java index 7d0ee355f4..1ea354ebcd 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/SettingsFactory.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/SettingsFactory.java @@ -33,6 +33,7 @@ import org.hibernate.ConnectionReleaseMode; import org.hibernate.EntityMode; import org.hibernate.HibernateException; import org.hibernate.MultiTenancyStrategy; +import org.hibernate.boot.registry.selector.spi.StrategySelector; import org.hibernate.cache.internal.NoCachingRegionFactory; import org.hibernate.cache.internal.RegionFactoryInitiator; import org.hibernate.cache.internal.StandardQueryCacheFactory; @@ -41,7 +42,10 @@ import org.hibernate.cache.spi.RegionFactory; import org.hibernate.engine.jdbc.spi.ExtractedDatabaseMetaData; import org.hibernate.engine.jdbc.spi.JdbcServices; import org.hibernate.engine.transaction.spi.TransactionFactory; +import org.hibernate.hql.spi.MultiTableBulkIdStrategy; +import org.hibernate.hql.spi.PersistentTableBulkIdStrategy; import org.hibernate.hql.spi.QueryTranslatorFactory; +import org.hibernate.hql.spi.TemporaryTableBulkIdStrategy; import org.hibernate.internal.CoreMessageLogger; import org.hibernate.internal.util.StringHelper; import org.hibernate.internal.util.config.ConfigurationHelper; @@ -97,6 +101,18 @@ public class SettingsFactory implements Serializable { // Transaction settings: settings.setJtaPlatform( serviceRegistry.getService( JtaPlatform.class ) ); + MultiTableBulkIdStrategy multiTableBulkIdStrategy = serviceRegistry.getService( StrategySelector.class ) + .resolveStrategy( + MultiTableBulkIdStrategy.class, + properties.getProperty( AvailableSettings.HQL_BULK_ID_STRATEGY ) + ); + if ( multiTableBulkIdStrategy == null ) { + multiTableBulkIdStrategy = jdbcServices.getDialect().supportsTemporaryTables() + ? TemporaryTableBulkIdStrategy.INSTANCE + : new PersistentTableBulkIdStrategy(); + } + settings.setMultiTableBulkIdStrategy( multiTableBulkIdStrategy ); + boolean flushBeforeCompletion = ConfigurationHelper.getBoolean(AvailableSettings.FLUSH_BEFORE_COMPLETION, properties); if ( debugEnabled ) { LOG.debugf( "Automatic flush during beforeCompletion(): %s", enabledDisabled(flushBeforeCompletion) ); diff --git a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/exec/BasicExecutor.java b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/exec/BasicExecutor.java index 04b7feb2e0..2c2aeaf164 100644 --- a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/exec/BasicExecutor.java +++ b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/exec/BasicExecutor.java @@ -31,9 +31,12 @@ import java.util.List; import antlr.RecognitionException; import org.hibernate.HibernateException; +import org.hibernate.action.internal.BulkOperationCleanupAction; import org.hibernate.engine.spi.QueryParameters; import org.hibernate.engine.spi.RowSelection; +import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SessionImplementor; +import org.hibernate.event.spi.EventSource; import org.hibernate.hql.internal.ast.HqlSqlWalker; import org.hibernate.hql.internal.ast.QuerySyntaxException; import org.hibernate.hql.internal.ast.SqlGenerator; @@ -45,17 +48,17 @@ import org.hibernate.persister.entity.Queryable; * * @author Steve Ebersole */ -public class BasicExecutor extends AbstractStatementExecutor { - +public class BasicExecutor implements StatementExecutor { + private final SessionFactoryImplementor factory; private final Queryable persister; private final String sql; private final List parameterSpecifications; public BasicExecutor(HqlSqlWalker walker, Queryable persister) { - super(walker, null); + this.factory = walker.getSessionFactoryHelper().getFactory(); this.persister = persister; try { - SqlGenerator gen = new SqlGenerator( getFactory() ); + SqlGenerator gen = new SqlGenerator( factory ); gen.statement( walker.getAST() ); sql = gen.getSQL(); gen.getParseErrorHandler().throwQueryException(); @@ -71,8 +74,13 @@ public class BasicExecutor extends AbstractStatementExecutor { } public int execute(QueryParameters parameters, SessionImplementor session) throws HibernateException { - - coordinateSharedCacheCleanup( session ); + BulkOperationCleanupAction action = new BulkOperationCleanupAction( session, persister ); + if ( session.isEventSource() ) { + ( (EventSource) session ).getActionQueue().addAction( action ); + } + else { + action.getAfterTransactionCompletionProcess().doAfterTransactionCompletion( true, session ); + } PreparedStatement st = null; RowSelection selection = parameters.getRowSelection(); @@ -101,16 +109,7 @@ public class BasicExecutor extends AbstractStatementExecutor { } } catch( SQLException sqle ) { - throw getFactory().getSQLExceptionHelper().convert( - sqle, - "could not execute update query", - sql - ); + throw factory.getSQLExceptionHelper().convert( sqle, "could not execute update query", sql ); } } - - @Override - protected Queryable[] getAffectedQueryables() { - return new Queryable[] { persister }; - } } diff --git a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/exec/MultiTableDeleteExecutor.java b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/exec/MultiTableDeleteExecutor.java index 5c2cb36ed6..b0b5a75e22 100644 --- a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/exec/MultiTableDeleteExecutor.java +++ b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/exec/MultiTableDeleteExecutor.java @@ -1,10 +1,10 @@ /* * Hibernate, Relational Persistence for Idiomatic Java * - * Copyright (c) 2008, Red Hat Middleware LLC or third-party contributors as + * Copyright (c) 2008, 2012, 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 Middleware LLC. + * 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 @@ -20,147 +20,46 @@ * Free Software Foundation, Inc. * 51 Franklin Street, Fifth Floor * Boston, MA 02110-1301 USA - * */ package org.hibernate.hql.internal.ast.exec; -import java.sql.PreparedStatement; -import java.sql.SQLException; -import java.util.Iterator; - -import org.jboss.logging.Logger; - import org.hibernate.HibernateException; +import org.hibernate.action.internal.BulkOperationCleanupAction; import org.hibernate.engine.spi.QueryParameters; import org.hibernate.engine.spi.SessionImplementor; +import org.hibernate.event.spi.EventSource; import org.hibernate.hql.internal.ast.HqlSqlWalker; -import org.hibernate.hql.internal.ast.tree.DeleteStatement; -import org.hibernate.hql.internal.ast.tree.FromElement; -import org.hibernate.internal.CoreMessageLogger; -import org.hibernate.internal.util.StringHelper; -import org.hibernate.param.ParameterSpecification; -import org.hibernate.persister.entity.Queryable; -import org.hibernate.sql.Delete; +import org.hibernate.hql.spi.MultiTableBulkIdStrategy; /** * Implementation of MultiTableDeleteExecutor. * * @author Steve Ebersole */ -public class MultiTableDeleteExecutor extends AbstractStatementExecutor { - - private static final CoreMessageLogger LOG = Logger.getMessageLogger(CoreMessageLogger.class, - MultiTableDeleteExecutor.class.getName()); - - private final Queryable persister; - private final String idInsertSelect; - private final String[] deletes; +public class MultiTableDeleteExecutor implements StatementExecutor { + private final MultiTableBulkIdStrategy.DeleteHandler deleteHandler; public MultiTableDeleteExecutor(HqlSqlWalker walker) { - super(walker, null); - - if ( !walker.getSessionFactoryHelper().getFactory().getDialect().supportsTemporaryTables() ) { - throw new HibernateException( "cannot doAfterTransactionCompletion multi-table deletes using dialect not supporting temp tables" ); - } - - DeleteStatement deleteStatement = ( DeleteStatement ) walker.getAST(); - FromElement fromElement = deleteStatement.getFromClause().getFromElement(); - String bulkTargetAlias = fromElement.getTableAlias(); - this.persister = fromElement.getQueryable(); - - this.idInsertSelect = generateIdInsertSelect( persister, bulkTargetAlias, deleteStatement.getWhereClause() ); - LOG.tracev( "Generated ID-INSERT-SELECT SQL (multi-table delete) : {0}", idInsertSelect ); - - String[] tableNames = persister.getConstraintOrderedTableNameClosure(); - String[][] columnNames = persister.getContraintOrderedTableKeyColumnClosure(); - String idSubselect = generateIdSubselect( persister ); - - deletes = new String[tableNames.length]; - for ( int i = tableNames.length - 1; i >= 0; i-- ) { - // TODO : an optimization here would be to consider cascade deletes and not gen those delete statements; - // the difficulty is the ordering of the tables here vs the cascade attributes on the persisters -> - // the table info gotten here should really be self-contained (i.e., a class representation - // defining all the needed attributes), then we could then get an array of those - final Delete delete = new Delete() - .setTableName( tableNames[i] ) - .setWhere( "(" + StringHelper.join( ", ", columnNames[i] ) + ") IN (" + idSubselect + ")" ); - if ( getFactory().getSettings().isCommentsEnabled() ) { - delete.setComment( "bulk delete" ); - } - - deletes[i] = delete.toStatementString(); - } + MultiTableBulkIdStrategy strategy = walker.getSessionFactoryHelper() + .getFactory() + .getSettings() + .getMultiTableBulkIdStrategy(); + this.deleteHandler = strategy.buildDeleteHandler( walker.getSessionFactoryHelper().getFactory(), walker ); } public String[] getSqlStatements() { - return deletes; + return deleteHandler.getSqlStatements(); } public int execute(QueryParameters parameters, SessionImplementor session) throws HibernateException { - coordinateSharedCacheCleanup( session ); - - createTemporaryTableIfNecessary( persister, session ); - - try { - // First, save off the pertinent ids, saving the number of pertinent ids for return - PreparedStatement ps = null; - int resultCount = 0; - try { - try { - ps = session.getTransactionCoordinator().getJdbcCoordinator().getStatementPreparer().prepareStatement( idInsertSelect, false ); - Iterator paramSpecifications = getIdSelectParameterSpecifications().iterator(); - int pos = 1; - while ( paramSpecifications.hasNext() ) { - final ParameterSpecification paramSpec = ( ParameterSpecification ) paramSpecifications.next(); - pos += paramSpec.bind( ps, parameters, session, pos ); - } - resultCount = ps.executeUpdate(); - } - finally { - if ( ps != null ) { - ps.close(); - } - } - } - catch( SQLException e ) { - throw getFactory().getSQLExceptionHelper().convert( - e, - "could not insert/select ids for bulk delete", - idInsertSelect - ); - } - - // Start performing the deletes - for ( int i = 0; i < deletes.length; i++ ) { - try { - try { - ps = session.getTransactionCoordinator().getJdbcCoordinator().getStatementPreparer().prepareStatement( deletes[i], false ); - ps.executeUpdate(); - } - finally { - if ( ps != null ) { - ps.close(); - } - } - } - catch( SQLException e ) { - throw getFactory().getSQLExceptionHelper().convert( - e, - "error performing bulk delete", - deletes[i] - ); - } - } - - return resultCount; + BulkOperationCleanupAction action = new BulkOperationCleanupAction( session, deleteHandler.getTargetedQueryable() ); + if ( session.isEventSource() ) { + ( (EventSource) session ).getActionQueue().addAction( action ); } - finally { - dropTemporaryTableIfNecessary( persister, session ); + else { + action.getAfterTransactionCompletionProcess().doAfterTransactionCompletion( true, session ); } - } - @Override - protected Queryable[] getAffectedQueryables() { - return new Queryable[] { persister }; + return deleteHandler.execute( session, parameters ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/exec/MultiTableUpdateExecutor.java b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/exec/MultiTableUpdateExecutor.java index b5168bb3cb..b78afe8f3f 100644 --- a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/exec/MultiTableUpdateExecutor.java +++ b/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/exec/MultiTableUpdateExecutor.java @@ -24,178 +24,44 @@ */ package org.hibernate.hql.internal.ast.exec; -import java.sql.PreparedStatement; -import java.sql.SQLException; -import java.util.ArrayList; -import java.util.Iterator; -import java.util.List; - -import org.jboss.logging.Logger; - import org.hibernate.HibernateException; +import org.hibernate.action.internal.BulkOperationCleanupAction; import org.hibernate.engine.spi.QueryParameters; import org.hibernate.engine.spi.SessionImplementor; +import org.hibernate.event.spi.EventSource; import org.hibernate.hql.internal.ast.HqlSqlWalker; -import org.hibernate.hql.internal.ast.tree.AssignmentSpecification; -import org.hibernate.hql.internal.ast.tree.FromElement; -import org.hibernate.hql.internal.ast.tree.UpdateStatement; -import org.hibernate.internal.CoreMessageLogger; -import org.hibernate.internal.util.StringHelper; -import org.hibernate.param.ParameterSpecification; -import org.hibernate.persister.entity.Queryable; -import org.hibernate.sql.Update; +import org.hibernate.hql.spi.MultiTableBulkIdStrategy; /** * Implementation of MultiTableUpdateExecutor. * * @author Steve Ebersole */ -public class MultiTableUpdateExecutor extends AbstractStatementExecutor { - - private static final CoreMessageLogger LOG = Logger.getMessageLogger(CoreMessageLogger.class, - MultiTableUpdateExecutor.class.getName()); - - private final Queryable persister; - private final String idInsertSelect; - private final String[] updates; - private final ParameterSpecification[][] hqlParameters; +public class MultiTableUpdateExecutor implements StatementExecutor { + private final MultiTableBulkIdStrategy.UpdateHandler updateHandler; public MultiTableUpdateExecutor(HqlSqlWalker walker) { - super(walker, null); - - if ( !walker.getSessionFactoryHelper().getFactory().getDialect().supportsTemporaryTables() ) { - throw new HibernateException( "cannot doAfterTransactionCompletion multi-table updates using dialect not supporting temp tables" ); - } - - UpdateStatement updateStatement = ( UpdateStatement ) walker.getAST(); - FromElement fromElement = updateStatement.getFromClause().getFromElement(); - String bulkTargetAlias = fromElement.getTableAlias(); - this.persister = fromElement.getQueryable(); - - this.idInsertSelect = generateIdInsertSelect( persister, bulkTargetAlias, updateStatement.getWhereClause() ); - LOG.tracev( "Generated ID-INSERT-SELECT SQL (multi-table update) : {0}", idInsertSelect ); - - String[] tableNames = persister.getConstraintOrderedTableNameClosure(); - String[][] columnNames = persister.getContraintOrderedTableKeyColumnClosure(); - - String idSubselect = generateIdSubselect( persister ); - List assignmentSpecifications = walker.getAssignmentSpecifications(); - - updates = new String[tableNames.length]; - hqlParameters = new ParameterSpecification[tableNames.length][]; - for ( int tableIndex = 0; tableIndex < tableNames.length; tableIndex++ ) { - boolean affected = false; - List parameterList = new ArrayList(); - Update update = new Update( getFactory().getDialect() ) - .setTableName( tableNames[tableIndex] ) - .setWhere( "(" + StringHelper.join( ", ", columnNames[tableIndex] ) + ") IN (" + idSubselect + ")" ); - if ( getFactory().getSettings().isCommentsEnabled() ) { - update.setComment( "bulk update" ); - } - final Iterator itr = assignmentSpecifications.iterator(); - while ( itr.hasNext() ) { - final AssignmentSpecification specification = ( AssignmentSpecification ) itr.next(); - if ( specification.affectsTable( tableNames[tableIndex] ) ) { - affected = true; - update.appendAssignmentFragment( specification.getSqlAssignmentFragment() ); - if ( specification.getParameters() != null ) { - for ( int paramIndex = 0; paramIndex < specification.getParameters().length; paramIndex++ ) { - parameterList.add( specification.getParameters()[paramIndex] ); - } - } - } - } - if ( affected ) { - updates[tableIndex] = update.toStatementString(); - hqlParameters[tableIndex] = ( ParameterSpecification[] ) parameterList.toArray( new ParameterSpecification[0] ); - } - } - } - - public Queryable getAffectedQueryable() { - return persister; + MultiTableBulkIdStrategy strategy = walker.getSessionFactoryHelper() + .getFactory() + .getSettings() + .getMultiTableBulkIdStrategy(); + this.updateHandler = strategy.buildUpdateHandler( walker.getSessionFactoryHelper().getFactory(), walker ); } public String[] getSqlStatements() { - return updates; + return updateHandler.getSqlStatements(); } public int execute(QueryParameters parameters, SessionImplementor session) throws HibernateException { - coordinateSharedCacheCleanup( session ); + BulkOperationCleanupAction action = new BulkOperationCleanupAction( session, updateHandler.getTargetedQueryable() ); - createTemporaryTableIfNecessary( persister, session ); - - try { - // First, save off the pertinent ids, as the return value - PreparedStatement ps = null; - int resultCount = 0; - try { - try { - ps = session.getTransactionCoordinator().getJdbcCoordinator().getStatementPreparer().prepareStatement( idInsertSelect, false ); -// int parameterStart = getWalker().getNumberOfParametersInSetClause(); -// List allParams = getIdSelectParameterSpecifications(); -// Iterator whereParams = allParams.subList( parameterStart, allParams.size() ).iterator(); - Iterator whereParams = getIdSelectParameterSpecifications().iterator(); - int sum = 1; // jdbc params are 1-based - while ( whereParams.hasNext() ) { - sum += ( ( ParameterSpecification ) whereParams.next() ).bind( ps, parameters, session, sum ); - } - resultCount = ps.executeUpdate(); - } - finally { - if ( ps != null ) { - ps.close(); - } - } - } - catch( SQLException e ) { - throw getFactory().getSQLExceptionHelper().convert( - e, - "could not insert/select ids for bulk update", - idInsertSelect - ); - } - - // Start performing the updates - for ( int i = 0; i < updates.length; i++ ) { - if ( updates[i] == null ) { - continue; - } - try { - try { - ps = session.getTransactionCoordinator().getJdbcCoordinator().getStatementPreparer().prepareStatement( updates[i], false ); - if ( hqlParameters[i] != null ) { - int position = 1; // jdbc params are 1-based - for ( int x = 0; x < hqlParameters[i].length; x++ ) { - position += hqlParameters[i][x].bind( ps, parameters, session, position ); - } - } - ps.executeUpdate(); - } - finally { - if ( ps != null ) { - ps.close(); - } - } - } - catch( SQLException e ) { - throw getFactory().getSQLExceptionHelper().convert( - e, - "error performing bulk update", - updates[i] - ); - } - } - - return resultCount; + if ( session.isEventSource() ) { + ( (EventSource) session ).getActionQueue().addAction( action ); } - finally { - dropTemporaryTableIfNecessary( persister, session ); + else { + action.getAfterTransactionCompletionProcess().doAfterTransactionCompletion( true, session ); } - } - @Override - protected Queryable[] getAffectedQueryables() { - return new Queryable[] { persister }; + return updateHandler.execute( session, parameters ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/hql/spi/AbstractTableBasedBulkIdHandler.java b/hibernate-core/src/main/java/org/hibernate/hql/spi/AbstractTableBasedBulkIdHandler.java new file mode 100644 index 0000000000..1bad685122 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/hql/spi/AbstractTableBasedBulkIdHandler.java @@ -0,0 +1,173 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2012, 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.SQLException; +import java.util.Collections; +import java.util.List; + +import antlr.RecognitionException; +import antlr.collections.AST; + +import org.hibernate.HibernateException; +import org.hibernate.JDBCException; +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.engine.spi.SessionImplementor; +import org.hibernate.hql.internal.ast.HqlSqlWalker; +import org.hibernate.hql.internal.ast.SqlGenerator; +import org.hibernate.internal.util.StringHelper; +import org.hibernate.param.ParameterSpecification; +import org.hibernate.persister.entity.Queryable; +import org.hibernate.sql.InsertSelect; +import org.hibernate.sql.Select; +import org.hibernate.sql.SelectFragment; + +/** + * @author Steve Ebersole + */ +public class AbstractTableBasedBulkIdHandler { + private final SessionFactoryImplementor sessionFactory; + private final HqlSqlWalker walker; + + public AbstractTableBasedBulkIdHandler(SessionFactoryImplementor sessionFactory, HqlSqlWalker walker) { + this.sessionFactory = sessionFactory; + this.walker = walker; + } + + protected SessionFactoryImplementor factory() { + return sessionFactory; + } + + protected HqlSqlWalker walker() { + return walker; + } + + protected JDBCException convert(SQLException e, String message, String sql) { + throw factory().getSQLExceptionHelper().convert( e, message, sql ); + } + + protected static class ProcessedWhereClause { + public static final ProcessedWhereClause NO_WHERE_CLAUSE = new ProcessedWhereClause(); + + private final String userWhereClauseFragment; + private final List idSelectParameterSpecifications; + + private ProcessedWhereClause() { + this( "", Collections.emptyList() ); + } + + public ProcessedWhereClause(String userWhereClauseFragment, List idSelectParameterSpecifications) { + this.userWhereClauseFragment = userWhereClauseFragment; + this.idSelectParameterSpecifications = idSelectParameterSpecifications; + } + + public String getUserWhereClauseFragment() { + return userWhereClauseFragment; + } + + public List getIdSelectParameterSpecifications() { + return idSelectParameterSpecifications; + } + } + + @SuppressWarnings("unchecked") + protected ProcessedWhereClause processWhereClause(AST whereClause) { + if ( whereClause.getNumberOfChildren() != 0 ) { + // If a where clause was specified in the update/delete query, use it to limit the + // returned ids here... + try { + SqlGenerator sqlGenerator = new SqlGenerator( sessionFactory ); + sqlGenerator.whereClause( whereClause ); + String userWhereClause = sqlGenerator.getSQL().substring( 7 ); // strip the " where " + List idSelectParameterSpecifications = sqlGenerator.getCollectedParameters(); + + return new ProcessedWhereClause( userWhereClause, idSelectParameterSpecifications ); + } + catch ( RecognitionException e ) { + throw new HibernateException( "Unable to generate id select for DML operation", e ); + } + } + else { + return ProcessedWhereClause.NO_WHERE_CLAUSE; + } + } + + protected String generateIdInsertSelect(Queryable persister, String tableAlias, ProcessedWhereClause whereClause) { + Select select = new Select( sessionFactory.getDialect() ); + SelectFragment selectFragment = new SelectFragment() + .addColumns( tableAlias, persister.getIdentifierColumnNames(), persister.getIdentifierColumnNames() ); + select.setSelectClause( selectFragment.toFragmentString().substring( 2 ) + extraIdSelectValues() ); + + String rootTableName = persister.getTableName(); + String fromJoinFragment = persister.fromJoinFragment( tableAlias, true, false ); + String whereJoinFragment = persister.whereJoinFragment( tableAlias, true, false ); + + select.setFromClause( rootTableName + ' ' + tableAlias + fromJoinFragment ); + + if ( whereJoinFragment == null ) { + whereJoinFragment = ""; + } + else { + whereJoinFragment = whereJoinFragment.trim(); + if ( whereJoinFragment.startsWith( "and" ) ) { + whereJoinFragment = whereJoinFragment.substring( 4 ); + } + } + + if ( whereClause.userWhereClauseFragment.length() > 0 ) { + if ( whereJoinFragment.length() > 0 ) { + whereJoinFragment += " and "; + } + } + + select.setWhereClause( whereJoinFragment + whereClause.userWhereClauseFragment ); + + InsertSelect insert = new InsertSelect( sessionFactory.getDialect() ); + if ( sessionFactory.getSettings().isCommentsEnabled() ) { + insert.setComment( "insert-select for " + persister.getEntityName() + " ids" ); + } + insert.setTableName( determineIdTableName( persister ) ); + insert.setSelect( select ); + return insert.toStatementString(); + } + + protected String extraIdSelectValues() { + return ""; + } + + protected String determineIdTableName(Queryable persister) { + return persister.getTemporaryIdTableName(); + } + + protected String generateIdSubselect(Queryable persister) { + return "select " + StringHelper.join( ", ", persister.getIdentifierColumnNames() ) + + " from " + determineIdTableName( persister ); + } + + protected void prepareForUse(Queryable persister, SessionImplementor session) { + } + + protected void releaseFromUse(Queryable persister, SessionImplementor session) { + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/hql/spi/MultiTableBulkIdStrategy.java b/hibernate-core/src/main/java/org/hibernate/hql/spi/MultiTableBulkIdStrategy.java new file mode 100644 index 0000000000..e98ff99ac5 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/hql/spi/MultiTableBulkIdStrategy.java @@ -0,0 +1,105 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2012, 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.util.Map; + +import org.hibernate.cfg.Mappings; +import org.hibernate.dialect.Dialect; +import org.hibernate.engine.jdbc.spi.JdbcConnectionAccess; +import org.hibernate.engine.spi.Mapping; +import org.hibernate.engine.spi.QueryParameters; +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.engine.spi.SessionImplementor; +import org.hibernate.hql.internal.ast.HqlSqlWalker; +import org.hibernate.persister.entity.Queryable; + +/** + * Generalized strategy contract for handling multi-table bulk HQL operations. + * + * @author Steve Ebersole + */ +public interface MultiTableBulkIdStrategy { + /** + * Prepare the strategy. Called as the SessionFactory is being built. Intended patterns here include:
    + *
  • Adding tables to the passed Mappings, to be picked by by "schema management tools"
  • + *
  • Manually creating the tables immediately through the passed JDBC Connection access
  • + *
+ * + * @param dialect The dialect + * @param connectionAccess Access to the JDBC Connection + * @param mappings The Hibernate Mappings object, for access to O/RM mapping information + * @param mapping The Hibernate Mapping contract, mainly for use in DDL generation + * @param settings Configuration settings + */ + public void prepare(Dialect dialect, JdbcConnectionAccess connectionAccess, Mappings mappings, Mapping mapping, Map settings); + + /** + * Release the strategy. Called as the SessionFactory is being shut down. + * + * @param dialect The dialect + * @param connectionAccess Access to the JDBC Connection + */ + public void release(Dialect dialect, JdbcConnectionAccess connectionAccess); + + /** + * Handler for dealing with multi-table HQL bulk update statements. + */ + public static interface UpdateHandler { + public Queryable getTargetedQueryable(); + public String[] getSqlStatements(); + + public int execute(SessionImplementor session, QueryParameters queryParameters); + } + + /** + * Build a handler capable of handling the bulk update indicated by the given walker. + * + * @param factory The SessionFactory + * @param walker The AST walker, representing the update query + * + * @return The handler + */ + public UpdateHandler buildUpdateHandler(SessionFactoryImplementor factory, HqlSqlWalker walker); + + /** + * Handler for dealing with multi-table HQL bulk delete statements. + */ + public static interface DeleteHandler { + public Queryable getTargetedQueryable(); + public String[] getSqlStatements(); + + public int execute(SessionImplementor session, QueryParameters queryParameters); + } + + /** + * Build a handler capable of handling the bulk delete indicated by the given walker. + * + * @param factory The SessionFactory + * @param walker The AST walker, representing the delete query + * + * @return The handler + */ + public DeleteHandler buildDeleteHandler(SessionFactoryImplementor factory, HqlSqlWalker walker); +} diff --git a/hibernate-core/src/main/java/org/hibernate/hql/spi/PersistentTableBulkIdStrategy.java b/hibernate-core/src/main/java/org/hibernate/hql/spi/PersistentTableBulkIdStrategy.java new file mode 100644 index 0000000000..1655f13340 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/hql/spi/PersistentTableBulkIdStrategy.java @@ -0,0 +1,241 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2012, 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.SQLException; +import java.sql.Statement; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import org.jboss.logging.Logger; + +import org.hibernate.HibernateException; +import org.hibernate.cfg.Mappings; +import org.hibernate.dialect.Dialect; +import org.hibernate.engine.jdbc.spi.JdbcConnectionAccess; +import org.hibernate.engine.spi.Mapping; +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.engine.spi.SessionImplementor; +import org.hibernate.hql.internal.ast.HqlSqlWalker; +import org.hibernate.internal.AbstractSessionImpl; +import org.hibernate.internal.util.config.ConfigurationHelper; +import org.hibernate.mapping.Column; +import org.hibernate.mapping.PersistentClass; +import org.hibernate.mapping.Table; +import org.hibernate.persister.entity.Queryable; +import org.hibernate.type.UUIDCharType; + +/** + * @author Steve Ebersole + */ +public class PersistentTableBulkIdStrategy implements MultiTableBulkIdStrategy { + private static final Logger log = Logger.getLogger( PersistentTableBulkIdStrategy.class ); + + public static final String CLEAN_UP_ID_TABLES = "hibernate.hql.bulk_id_strategy.persistent.clean_up"; + + private boolean cleanUpTables; + private List tableCleanUpDdl; + + @Override + public void prepare( + Dialect dialect, + JdbcConnectionAccess connectionAccess, + Mappings mappings, + Mapping mapping, + Map settings) { + cleanUpTables = ConfigurationHelper.getBoolean( CLEAN_UP_ID_TABLES, settings, false ); + final Iterator entityMappings = mappings.iterateClasses(); + final List idTableDefinitions = new ArrayList
(); + while ( entityMappings.hasNext() ) { + final PersistentClass entityMapping = entityMappings.next(); + final Table idTableDefinition = generateIdTableDefinition( entityMapping ); + idTableDefinitions.add( idTableDefinition ); + } + exportTableDefinitions( idTableDefinitions, dialect, connectionAccess, mappings, mapping ); + } + + protected Table generateIdTableDefinition(PersistentClass entityMapping) { + Table idTable = new Table( entityMapping.getTemporaryIdTableName() ); + Iterator itr = entityMapping.getIdentityTable().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; + } + + protected void exportTableDefinitions( + List
idTableDefinitions, + Dialect dialect, + JdbcConnectionAccess connectionAccess, + Mappings mappings, + Mapping mapping) { + try { + Connection connection = connectionAccess.obtainConnection(); + + try { + Statement statement = connection.createStatement(); + + for ( Table idTableDefinition : idTableDefinitions ) { + if ( cleanUpTables ) { + if ( tableCleanUpDdl == null ) { + tableCleanUpDdl = new ArrayList(); + } + tableCleanUpDdl.add( idTableDefinition.sqlDropString( dialect, null, null ) ); + } + try { + statement.execute( idTableDefinition.sqlCreateString( dialect, mapping, null, null ) ); + } + catch (SQLException e) { + log.debugf( "Error attempting to export id-table [%s] : %s", idTableDefinition.getName(), e.getMessage() ); + } + } + } + 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 ); + } + } + + @Override + public void release(Dialect dialect, JdbcConnectionAccess connectionAccess) { + if ( ! cleanUpTables ) { + return; + } + + try { + Connection connection = connectionAccess.obtainConnection(); + + try { + Statement statement = connection.createStatement(); + + for ( String cleanupDdl : tableCleanUpDdl ) { + try { + statement.execute( cleanupDdl ); + } + catch (SQLException e) { + log.debugf( "Error attempting to cleanup id-table : [%s]", e.getMessage() ); + } + } + } + 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 ); + } + } + + @Override + public UpdateHandler buildUpdateHandler(SessionFactoryImplementor factory, HqlSqlWalker walker) { + return new TableBasedUpdateHandlerImpl( factory, walker ) { + @Override + protected String extraIdSelectValues() { + return "cast(? as char)"; + } + + @Override + protected String generateIdSubselect(Queryable persister) { + return super.generateIdSubselect( persister ) + " where hib_sess_id=?"; + } + + @Override + protected int handlePrependedParametersOnIdSelection(PreparedStatement ps, SessionImplementor session, int pos) throws SQLException { + if ( ! AbstractSessionImpl.class.isInstance( session ) ) { + throw new HibernateException( "Only available on SessionImpl instances" ); + } + UUIDCharType.INSTANCE.set( ps, ( (AbstractSessionImpl) session ).getSessionIdentifier(), pos, session ); + return 1; + } + + @Override + protected void handleAddedParametersOnUpdate(PreparedStatement ps, SessionImplementor session, int position) throws SQLException { + if ( ! AbstractSessionImpl.class.isInstance( session ) ) { + throw new HibernateException( "Only available on SessionImpl instances" ); + } + UUIDCharType.INSTANCE.set( ps, ( (AbstractSessionImpl) session ).getSessionIdentifier(), position, session ); + } + }; + } + + @Override + public DeleteHandler buildDeleteHandler(SessionFactoryImplementor factory, HqlSqlWalker walker) { + return new TableBasedDeleteHandlerImpl( factory, walker ) { + @Override + protected String extraIdSelectValues() { + return "cast(? as char)"; + } + + @Override + protected String generateIdSubselect(Queryable persister) { + return super.generateIdSubselect( persister ) + " where hib_sess_id=?"; + } + + @Override + protected int handlePrependedParametersOnIdSelection(PreparedStatement ps, SessionImplementor session, int pos) throws SQLException { + if ( ! AbstractSessionImpl.class.isInstance( session ) ) { + throw new HibernateException( "Only available on SessionImpl instances" ); + } + UUIDCharType.INSTANCE.set( ps, ( (AbstractSessionImpl) session ).getSessionIdentifier(), pos, session ); + return 1; + } + + @Override + protected void handleAddedParametersOnDelete(PreparedStatement ps, SessionImplementor session) throws SQLException { + if ( ! AbstractSessionImpl.class.isInstance( session ) ) { + throw new HibernateException( "Only available on SessionImpl instances" ); + } + UUIDCharType.INSTANCE.set( ps, ( (AbstractSessionImpl) session ).getSessionIdentifier(), 1, session ); + } + }; + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/hql/spi/TableBasedDeleteHandlerImpl.java b/hibernate-core/src/main/java/org/hibernate/hql/spi/TableBasedDeleteHandlerImpl.java new file mode 100644 index 0000000000..27f510a0d1 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/hql/spi/TableBasedDeleteHandlerImpl.java @@ -0,0 +1,164 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2012, 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.sql.SQLException; +import java.util.List; + +import org.jboss.logging.Logger; + +import org.hibernate.engine.spi.QueryParameters; +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.engine.spi.SessionImplementor; +import org.hibernate.hql.internal.ast.HqlSqlWalker; +import org.hibernate.hql.internal.ast.tree.DeleteStatement; +import org.hibernate.hql.internal.ast.tree.FromElement; +import org.hibernate.internal.util.StringHelper; +import org.hibernate.param.ParameterSpecification; +import org.hibernate.persister.entity.Queryable; +import org.hibernate.sql.Delete; + +/** +* @author Steve Ebersole +*/ +class TableBasedDeleteHandlerImpl + extends AbstractTableBasedBulkIdHandler + implements MultiTableBulkIdStrategy.DeleteHandler { + private static final Logger log = Logger.getLogger( TableBasedDeleteHandlerImpl.class ); + + private final Queryable targetedPersister; + + private final String idInsertSelect; + private final List idSelectParameterSpecifications; + private final String[] deletes; + + TableBasedDeleteHandlerImpl(SessionFactoryImplementor factory, HqlSqlWalker walker) { + super( factory, walker ); + + DeleteStatement deleteStatement = ( DeleteStatement ) walker.getAST(); + FromElement fromElement = deleteStatement.getFromClause().getFromElement(); + + this.targetedPersister = fromElement.getQueryable(); + final String bulkTargetAlias = fromElement.getTableAlias(); + + final ProcessedWhereClause processedWhereClause = processWhereClause( deleteStatement.getWhereClause() ); + this.idSelectParameterSpecifications = processedWhereClause.getIdSelectParameterSpecifications(); + this.idInsertSelect = generateIdInsertSelect( targetedPersister, bulkTargetAlias, processedWhereClause ); + log.tracev( "Generated ID-INSERT-SELECT SQL (multi-table delete) : {0}", idInsertSelect ); + + String[] tableNames = targetedPersister.getConstraintOrderedTableNameClosure(); + String[][] columnNames = targetedPersister.getContraintOrderedTableKeyColumnClosure(); + String idSubselect = generateIdSubselect( targetedPersister ); + + deletes = new String[tableNames.length]; + for ( int i = tableNames.length - 1; i >= 0; i-- ) { + // TODO : an optimization here would be to consider cascade deletes and not gen those delete statements; + // the difficulty is the ordering of the tables here vs the cascade attributes on the persisters -> + // the table info gotten here should really be self-contained (i.e., a class representation + // defining all the needed attributes), then we could then get an array of those + final Delete delete = new Delete() + .setTableName( tableNames[i] ) + .setWhere( "(" + StringHelper.join( ", ", columnNames[i] ) + ") IN (" + idSubselect + ")" ); + if ( factory().getSettings().isCommentsEnabled() ) { + delete.setComment( "bulk delete" ); + } + + deletes[i] = delete.toStatementString(); + } + } + + @Override + public Queryable getTargetedQueryable() { + return targetedPersister; + } + + @Override + public String[] getSqlStatements() { + return deletes; + } + + @Override + public int execute(SessionImplementor session, QueryParameters queryParameters) { + prepareForUse( targetedPersister, session ); + try { + PreparedStatement ps = null; + int resultCount = 0; + try { + try { + ps = session.getTransactionCoordinator().getJdbcCoordinator().getStatementPreparer().prepareStatement( idInsertSelect, false ); + int pos = 1; + pos += handlePrependedParametersOnIdSelection( ps, session, pos ); + for ( ParameterSpecification parameterSpecification : idSelectParameterSpecifications ) { + pos += parameterSpecification.bind( ps, queryParameters, session, pos ); + } + resultCount = ps.executeUpdate(); + } + finally { + if ( ps != null ) { + ps.close(); + } + } + } + catch( SQLException e ) { + throw convert( e, "could not insert/select ids for bulk delete", idInsertSelect ); + } + + // Start performing the deletes + for ( String delete : deletes ) { + try { + try { + ps = session.getTransactionCoordinator() + .getJdbcCoordinator() + .getStatementPreparer() + .prepareStatement( delete, false ); + handleAddedParametersOnDelete( ps, session ); + ps.executeUpdate(); + } + finally { + if ( ps != null ) { + ps.close(); + } + } + } + catch (SQLException e) { + throw convert( e, "error performing bulk delete", delete ); + } + } + + return resultCount; + + } + finally { + releaseFromUse( targetedPersister, session ); + } + } + + protected int handlePrependedParametersOnIdSelection(PreparedStatement ps, SessionImplementor session, int pos) throws SQLException { + return 0; + } + + protected void handleAddedParametersOnDelete(PreparedStatement ps, SessionImplementor session) throws SQLException { + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/hql/spi/TableBasedUpdateHandlerImpl.java b/hibernate-core/src/main/java/org/hibernate/hql/spi/TableBasedUpdateHandlerImpl.java new file mode 100644 index 0000000000..fd5cb8b4ae --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/hql/spi/TableBasedUpdateHandlerImpl.java @@ -0,0 +1,190 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * Copyright (c) 2012, 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.sql.SQLException; +import java.util.ArrayList; +import java.util.List; + +import org.jboss.logging.Logger; + +import org.hibernate.engine.spi.QueryParameters; +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.engine.spi.SessionImplementor; +import org.hibernate.hql.internal.ast.HqlSqlWalker; +import org.hibernate.hql.internal.ast.tree.AssignmentSpecification; +import org.hibernate.hql.internal.ast.tree.FromElement; +import org.hibernate.hql.internal.ast.tree.UpdateStatement; +import org.hibernate.internal.util.StringHelper; +import org.hibernate.param.ParameterSpecification; +import org.hibernate.persister.entity.Queryable; +import org.hibernate.sql.Update; + +/** +* @author Steve Ebersole +*/ +public class TableBasedUpdateHandlerImpl + extends AbstractTableBasedBulkIdHandler + implements MultiTableBulkIdStrategy.UpdateHandler { + + private static final Logger log = Logger.getLogger( TableBasedUpdateHandlerImpl.class ); + + private final Queryable targetedPersister; + + private final String idInsertSelect; + private final List idSelectParameterSpecifications; + + private final String[] updates; + private final ParameterSpecification[][] assignmentParameterSpecifications; + + @SuppressWarnings("unchecked") + TableBasedUpdateHandlerImpl(SessionFactoryImplementor factory, HqlSqlWalker walker) { + super( factory, walker ); + + UpdateStatement updateStatement = ( UpdateStatement ) walker.getAST(); + FromElement fromElement = updateStatement.getFromClause().getFromElement(); + + this.targetedPersister = fromElement.getQueryable(); + final String bulkTargetAlias = fromElement.getTableAlias(); + + final ProcessedWhereClause processedWhereClause = processWhereClause( updateStatement.getWhereClause() ); + this.idSelectParameterSpecifications = processedWhereClause.getIdSelectParameterSpecifications(); + this.idInsertSelect = generateIdInsertSelect( targetedPersister, bulkTargetAlias, processedWhereClause ); + log.tracev( "Generated ID-INSERT-SELECT SQL (multi-table update) : {0}", idInsertSelect ); + + String[] tableNames = targetedPersister.getConstraintOrderedTableNameClosure(); + String[][] columnNames = targetedPersister.getContraintOrderedTableKeyColumnClosure(); + String idSubselect = generateIdSubselect( targetedPersister ); + + updates = new String[tableNames.length]; + assignmentParameterSpecifications = new ParameterSpecification[tableNames.length][]; + for ( int tableIndex = 0; tableIndex < tableNames.length; tableIndex++ ) { + boolean affected = false; + final List parameterList = new ArrayList(); + final Update update = new Update( factory().getDialect() ) + .setTableName( tableNames[tableIndex] ) + .setWhere( "(" + StringHelper.join( ", ", columnNames[tableIndex] ) + ") IN (" + idSubselect + ")" ); + if ( factory().getSettings().isCommentsEnabled() ) { + update.setComment( "bulk update" ); + } + final List assignmentSpecifications = walker.getAssignmentSpecifications(); + for ( AssignmentSpecification assignmentSpecification : assignmentSpecifications ) { + if ( assignmentSpecification.affectsTable( tableNames[tableIndex] ) ) { + affected = true; + update.appendAssignmentFragment( assignmentSpecification.getSqlAssignmentFragment() ); + if ( assignmentSpecification.getParameters() != null ) { + for ( int paramIndex = 0; paramIndex < assignmentSpecification.getParameters().length; paramIndex++ ) { + parameterList.add( assignmentSpecification.getParameters()[paramIndex] ); + } + } + } + } + if ( affected ) { + updates[tableIndex] = update.toStatementString(); + assignmentParameterSpecifications[tableIndex] = parameterList.toArray( new ParameterSpecification[parameterList.size()] ); + } + } + } + + @Override + public Queryable getTargetedQueryable() { + return targetedPersister; + } + + @Override + public String[] getSqlStatements() { + return updates; + } + + @Override + public int execute(SessionImplementor session, QueryParameters queryParameters) { + prepareForUse( targetedPersister, session ); + try { + // First, save off the pertinent ids, as the return value + PreparedStatement ps = null; + int resultCount = 0; + try { + try { + ps = session.getTransactionCoordinator().getJdbcCoordinator().getStatementPreparer().prepareStatement( idInsertSelect, false ); + int sum = 1; + sum += handlePrependedParametersOnIdSelection( ps, session, sum ); + for ( ParameterSpecification parameterSpecification : idSelectParameterSpecifications ) { + sum += parameterSpecification.bind( ps, queryParameters, session, sum ); + } + resultCount = ps.executeUpdate(); + } + finally { + if ( ps != null ) { + ps.close(); + } + } + } + catch( SQLException e ) { + throw convert( e, "could not insert/select ids for bulk update", idInsertSelect ); + } + + // Start performing the updates + for ( int i = 0; i < updates.length; i++ ) { + if ( updates[i] == null ) { + continue; + } + try { + try { + ps = session.getTransactionCoordinator().getJdbcCoordinator().getStatementPreparer().prepareStatement( updates[i], false ); + if ( assignmentParameterSpecifications[i] != null ) { + int position = 1; // jdbc params are 1-based + for ( int x = 0; x < assignmentParameterSpecifications[i].length; x++ ) { + position += assignmentParameterSpecifications[i][x].bind( ps, queryParameters, session, position ); + } + handleAddedParametersOnUpdate( ps, session, position ); + } + ps.executeUpdate(); + } + finally { + if ( ps != null ) { + ps.close(); + } + } + } + catch( SQLException e ) { + throw convert( e, "error performing bulk update", updates[i] ); + } + } + + return resultCount; + } + finally { + releaseFromUse( targetedPersister, session ); + } + } + + protected int handlePrependedParametersOnIdSelection(PreparedStatement ps, SessionImplementor session, int pos) throws SQLException { + return 0; + } + + protected void handleAddedParametersOnUpdate(PreparedStatement ps, SessionImplementor session, int position) throws SQLException { + //To change body of created methods use File | Settings | File Templates. + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/exec/AbstractStatementExecutor.java b/hibernate-core/src/main/java/org/hibernate/hql/spi/TemporaryTableBulkIdStrategy.java similarity index 50% rename from hibernate-core/src/main/java/org/hibernate/hql/internal/ast/exec/AbstractStatementExecutor.java rename to hibernate-core/src/main/java/org/hibernate/hql/spi/TemporaryTableBulkIdStrategy.java index d745bac252..c60e164a77 100644 --- a/hibernate-core/src/main/java/org/hibernate/hql/internal/ast/exec/AbstractStatementExecutor.java +++ b/hibernate-core/src/main/java/org/hibernate/hql/spi/TemporaryTableBulkIdStrategy.java @@ -1,7 +1,7 @@ /* * Hibernate, Relational Persistence for Idiomatic Java * - * Copyright (c) 2010, Red Hat Inc. or third-party contributors as + * Copyright (c) 2012, 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. @@ -21,122 +21,155 @@ * 51 Franklin Street, Fifth Floor * Boston, MA 02110-1301 USA */ -package org.hibernate.hql.internal.ast.exec; +package org.hibernate.hql.spi; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.SQLWarning; import java.sql.Statement; -import java.util.Collections; -import java.util.List; -import antlr.RecognitionException; -import antlr.collections.AST; import org.jboss.logging.Logger; -import org.hibernate.HibernateException; -import org.hibernate.action.internal.BulkOperationCleanupAction; +import org.hibernate.cfg.Mappings; +import org.hibernate.dialect.Dialect; +import org.hibernate.engine.jdbc.spi.JdbcConnectionAccess; import org.hibernate.engine.jdbc.spi.JdbcServices; import org.hibernate.engine.jdbc.spi.SqlExceptionHelper; +import org.hibernate.engine.spi.Mapping; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SessionImplementor; -import org.hibernate.event.spi.EventSource; import org.hibernate.hql.internal.ast.HqlSqlWalker; -import org.hibernate.hql.internal.ast.SqlGenerator; import org.hibernate.internal.CoreMessageLogger; -import org.hibernate.internal.util.StringHelper; import org.hibernate.jdbc.AbstractWork; import org.hibernate.persister.entity.Queryable; -import org.hibernate.sql.InsertSelect; -import org.hibernate.sql.Select; -import org.hibernate.sql.SelectFragment; /** - * Implementation of AbstractStatementExecutor. - * * @author Steve Ebersole */ -public abstract class AbstractStatementExecutor implements StatementExecutor { +public class TemporaryTableBulkIdStrategy implements MultiTableBulkIdStrategy { + public static final TemporaryTableBulkIdStrategy INSTANCE = new TemporaryTableBulkIdStrategy(); - private static final CoreMessageLogger LOG = Logger.getMessageLogger(CoreMessageLogger.class, - AbstractStatementExecutor.class.getName()); + private static final CoreMessageLogger log = Logger.getMessageLogger( + CoreMessageLogger.class, + TemporaryTableBulkIdStrategy.class.getName() + ); - private final HqlSqlWalker walker; - private List idSelectParameterSpecifications = Collections.EMPTY_LIST; - - public AbstractStatementExecutor( HqlSqlWalker walker, - CoreMessageLogger log ) { - this.walker = walker; + @Override + public void prepare(Dialect dialect, JdbcConnectionAccess connectionAccess, Mappings mappings, Mapping mapping) { + // nothing to do } - protected HqlSqlWalker getWalker() { - return walker; + @Override + public void release(Dialect dialect, JdbcConnectionAccess connectionAccess) { + // nothing to do } - protected SessionFactoryImplementor getFactory() { - return walker.getSessionFactoryHelper().getFactory(); + @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 ); + } + }; } - protected List getIdSelectParameterSpecifications() { - return idSelectParameterSpecifications; + @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 abstract Queryable[] getAffectedQueryables(); - protected String generateIdInsertSelect(Queryable persister, String tableAlias, AST whereClause) { - Select select = new Select( getFactory().getDialect() ); - SelectFragment selectFragment = new SelectFragment() - .addColumns( tableAlias, persister.getIdentifierColumnNames(), persister.getIdentifierColumnNames() ); - select.setSelectClause( selectFragment.toFragmentString().substring( 2 ) ); - - String rootTableName = persister.getTableName(); - String fromJoinFragment = persister.fromJoinFragment( tableAlias, true, false ); - String whereJoinFragment = persister.whereJoinFragment( tableAlias, true, false ); - - select.setFromClause( rootTableName + ' ' + tableAlias + fromJoinFragment ); - - if ( whereJoinFragment == null ) { - whereJoinFragment = ""; + 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, session.getFactory().getSettings().isDataDefinitionInTransactionSupported() ); } else { - whereJoinFragment = whereJoinFragment.trim(); - if ( whereJoinFragment.startsWith( "and" ) ) { - whereJoinFragment = whereJoinFragment.substring( 4 ); - } + final Connection connection = session.getTransactionCoordinator() + .getJdbcCoordinator() + .getLogicalConnection() + .getShareableConnectionProxy(); + work.execute( connection ); + session.getTransactionCoordinator() + .getJdbcCoordinator() + .getLogicalConnection() + .afterStatementExecution(); } - - String userWhereClause = ""; - if ( whereClause.getNumberOfChildren() != 0 ) { - // If a where clause was specified in the update/delete query, use it to limit the - // returned ids here... - try { - SqlGenerator sqlGenerator = new SqlGenerator( getFactory() ); - sqlGenerator.whereClause( whereClause ); - userWhereClause = sqlGenerator.getSQL().substring( 7 ); // strip the " where " - idSelectParameterSpecifications = sqlGenerator.getCollectedParameters(); - } - catch ( RecognitionException e ) { - throw new HibernateException( "Unable to generate id select for DML operation", e ); - } - if ( whereJoinFragment.length() > 0 ) { - whereJoinFragment += " and "; - } - } - - select.setWhereClause( whereJoinFragment + userWhereClause ); - - InsertSelect insert = new InsertSelect( getFactory().getDialect() ); - if ( getFactory().getSettings().isCommentsEnabled() ) { - insert.setComment( "insert-select for " + persister.getEntityName() + " ids" ); - } - insert.setTableName( persister.getTemporaryIdTableName() ); - insert.setSelect( select ); - return insert.toStatementString(); } - protected String generateIdSubselect(Queryable persister) { - return "select " + StringHelper.join( ", ", persister.getIdentifierColumnNames() ) + - " from " + persister.getTemporaryIdTableName(); + 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, session.getFactory().getSettings().isDataDefinitionInTransactionSupported() ); + } + else { + final Connection connection = session.getTransactionCoordinator() + .getJdbcCoordinator() + .getLogicalConnection() + .getShareableConnectionProxy(); + work.execute( connection ); + session.getTransactionCoordinator() + .getJdbcCoordinator() + .getLogicalConnection() + .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 ); + ps.executeUpdate(); + } + catch( Throwable t ) { + log.unableToCleanupTemporaryIdTable(t); + } + finally { + if ( ps != null ) { + try { + ps.close(); + } + catch( Throwable ignore ) { + // ignore + } + } + } + } + } + + @SuppressWarnings({ "UnnecessaryUnboxing" }) + protected boolean shouldIsolateTemporaryTableDDL(SessionImplementor session) { + Boolean dialectVote = session.getFactory().getDialect().performTemporaryTableDDLInIsolation(); + if ( dialectVote != null ) { + return dialectVote.booleanValue(); + } + return session.getFactory().getSettings().isDataDefinitionImplicitCommit(); } private static class TemporaryTableCreationWork extends AbstractWork { @@ -168,46 +201,24 @@ public abstract class AbstractStatementExecutor implements StatementExecutor { } } catch( Exception e ) { - LOG.debug( "unable to create temporary id table [" + e.getMessage() + "]" ); + log.debug( "unable to create temporary id table [" + e.getMessage() + "]" ); } } } - protected void createTemporaryTableIfNecessary(final Queryable persister, final 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.getTransactionCoordinator() - .getTransaction() - .createIsolationDelegate() - .delegateWork( work, getFactory().getSettings().isDataDefinitionInTransactionSupported() ); - } - else { - final Connection connection = session.getTransactionCoordinator() - .getJdbcCoordinator() - .getLogicalConnection() - .getShareableConnectionProxy(); - work.execute( connection ); - session.getTransactionCoordinator() - .getJdbcCoordinator() - .getLogicalConnection() - .afterStatementExecution(); - } - } private static SqlExceptionHelper.WarningHandler CREATION_WARNING_HANDLER = new SqlExceptionHelper.WarningHandlerLoggingSupport() { public boolean doProcess() { - return LOG.isDebugEnabled(); + return log.isDebugEnabled(); } public void prepare(SQLWarning warning) { - LOG.warningsCreatingTempTable( warning ); + log.warningsCreatingTempTable( warning ); } @Override protected void logWarning(String description, String message) { - LOG.debug( description ); - LOG.debug( message ); + log.debug( description ); + log.debug( message ); } }; @@ -240,71 +251,9 @@ public abstract class AbstractStatementExecutor implements StatementExecutor { } } catch( Exception e ) { - LOG.warn( "unable to drop temporary id table after use [" + e.getMessage() + "]" ); + log.warn( "unable to drop temporary id table after use [" + e.getMessage() + "]" ); } } } - protected void dropTemporaryTableIfNecessary(final Queryable persister, final SessionImplementor session) { - if ( getFactory().getDialect().dropTemporaryTableAfterUse() ) { - TemporaryTableDropWork work = new TemporaryTableDropWork( persister, session ); - if ( shouldIsolateTemporaryTableDDL() ) { - session.getTransactionCoordinator() - .getTransaction() - .createIsolationDelegate() - .delegateWork( work, getFactory().getSettings().isDataDefinitionInTransactionSupported() ); - } - else { - final Connection connection = session.getTransactionCoordinator() - .getJdbcCoordinator() - .getLogicalConnection() - .getShareableConnectionProxy(); - work.execute( connection ); - session.getTransactionCoordinator() - .getJdbcCoordinator() - .getLogicalConnection() - .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 ); - ps.executeUpdate(); - } - catch( Throwable t ) { - LOG.unableToCleanupTemporaryIdTable(t); - } - finally { - if ( ps != null ) { - try { - ps.close(); - } - catch( Throwable ignore ) { - // ignore - } - } - } - } - } - - protected void coordinateSharedCacheCleanup(SessionImplementor session) { - BulkOperationCleanupAction action = new BulkOperationCleanupAction( session, getAffectedQueryables() ); - - if ( session.isEventSource() ) { - ( ( EventSource ) session ).getActionQueue().addAction( action ); - } - else { - action.getAfterTransactionCompletionProcess().doAfterTransactionCompletion( true, session ); - } - } - - @SuppressWarnings({ "UnnecessaryUnboxing" }) - protected boolean shouldIsolateTemporaryTableDDL() { - Boolean dialectVote = getFactory().getDialect().performTemporaryTableDDLInIsolation(); - if (dialectVote != null) return dialectVote.booleanValue(); - return getFactory().getSettings().isDataDefinitionImplicitCommit(); - } } diff --git a/hibernate-core/src/main/java/org/hibernate/internal/AbstractSessionImpl.java b/hibernate-core/src/main/java/org/hibernate/internal/AbstractSessionImpl.java index 9f908bee6b..9d81f6fb18 100755 --- a/hibernate-core/src/main/java/org/hibernate/internal/AbstractSessionImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/AbstractSessionImpl.java @@ -27,6 +27,7 @@ import java.io.Serializable; import java.sql.Connection; import java.sql.SQLException; import java.util.List; +import java.util.UUID; import org.hibernate.HibernateException; import org.hibernate.MappingException; @@ -51,6 +52,7 @@ import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.engine.spi.SessionImplementor; import org.hibernate.engine.transaction.spi.TransactionContext; import org.hibernate.engine.transaction.spi.TransactionEnvironment; +import org.hibernate.id.uuid.StandardRandomStrategy; import org.hibernate.jdbc.WorkExecutor; import org.hibernate.jdbc.WorkExecutorVisitable; import org.hibernate.persister.entity.EntityPersister; @@ -269,6 +271,15 @@ public abstract class AbstractSessionImpl implements Serializable, SharedSession return jdbcConnectionAccess; } + private UUID sessionIdentifier; + + public UUID getSessionIdentifier() { + if ( sessionIdentifier == null ) { + sessionIdentifier = StandardRandomStrategy.INSTANCE.generateUUID( this ); + } + return sessionIdentifier; + } + private static class NonContextualJdbcConnectionAccess implements JdbcConnectionAccess, Serializable { private final ConnectionProvider connectionProvider; diff --git a/hibernate-core/src/main/java/org/hibernate/internal/SessionFactoryImpl.java b/hibernate-core/src/main/java/org/hibernate/internal/SessionFactoryImpl.java index f0172bf7f2..bdd1b3063c 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/SessionFactoryImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/SessionFactoryImpl.java @@ -29,6 +29,7 @@ import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; import java.sql.Connection; +import java.sql.SQLException; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; @@ -53,6 +54,7 @@ import org.hibernate.EntityNameResolver; import org.hibernate.HibernateException; import org.hibernate.Interceptor; import org.hibernate.MappingException; +import org.hibernate.MultiTenancyStrategy; import org.hibernate.ObjectNotFoundException; import org.hibernate.QueryException; import org.hibernate.Session; @@ -91,6 +93,8 @@ import org.hibernate.dialect.Dialect; import org.hibernate.dialect.function.SQLFunction; import org.hibernate.dialect.function.SQLFunctionRegistry; import org.hibernate.engine.ResultSetMappingDefinition; +import org.hibernate.engine.jdbc.connections.spi.MultiTenantConnectionProvider; +import org.hibernate.engine.jdbc.spi.JdbcConnectionAccess; import org.hibernate.engine.jdbc.spi.JdbcServices; import org.hibernate.engine.jdbc.spi.SqlExceptionHelper; import org.hibernate.engine.profile.Association; @@ -483,6 +487,15 @@ public final class SessionFactoryImpl LOG.debug( "Instantiated session factory" ); + settings.getMultiTableBulkIdStrategy().prepare( + dialect, + buildLocalConnectionAccess(), + cfg.createMappings(), + cfg.buildMapping(), + properties + ); + + if ( settings.isAutoCreateSchema() ) { new SchemaExport( serviceRegistry, cfg ) .setImportSqlCommandExtractor( serviceRegistry.getService( ImportSqlCommandExtractor.class ) ) @@ -558,6 +571,32 @@ public final class SessionFactoryImpl this.observer.sessionFactoryCreated( this ); } + private JdbcConnectionAccess buildLocalConnectionAccess() { + return new JdbcConnectionAccess() { + @Override + public Connection obtainConnection() throws SQLException { + return settings.getMultiTenancyStrategy() == MultiTenancyStrategy.NONE + ? serviceRegistry.getService( ConnectionProvider.class ).getConnection() + : serviceRegistry.getService( MultiTenantConnectionProvider.class ).getAnyConnection(); + } + + @Override + public void releaseConnection(Connection connection) throws SQLException { + if ( settings.getMultiTenancyStrategy() == MultiTenancyStrategy.NONE ) { + serviceRegistry.getService( ConnectionProvider.class ).closeConnection( connection ); + } + else { + serviceRegistry.getService( MultiTenantConnectionProvider.class ).releaseAnyConnection( connection ); + } + } + + @Override + public boolean supportsAggressiveRelease() { + return false; + } + }; + } + @SuppressWarnings({ "unchecked" }) private CustomEntityDirtinessStrategy determineCustomEntityDirtinessStrategy() { CustomEntityDirtinessStrategy defaultValue = new CustomEntityDirtinessStrategy() { @@ -1321,6 +1360,8 @@ public final class SessionFactoryImpl isClosed = true; + settings.getMultiTableBulkIdStrategy().release( dialect, buildLocalConnectionAccess() ); + Iterator iter = entityPersisters.values().iterator(); while ( iter.hasNext() ) { EntityPersister p = (EntityPersister) iter.next(); diff --git a/hibernate-core/src/main/java/org/hibernate/mapping/Column.java b/hibernate-core/src/main/java/org/hibernate/mapping/Column.java index 44dfc49d54..569b08e30d 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/Column.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/Column.java @@ -343,7 +343,8 @@ public class Column implements Selectable, Serializable, Cloneable { /** * Shallow copy, the value is not copied */ - protected Object clone() { + @Override + public Column clone() { Column copy = new Column(); copy.setLength( length ); copy.setScale( scale ); diff --git a/hibernate-core/src/main/java/org/hibernate/mapping/PersistentClass.java b/hibernate-core/src/main/java/org/hibernate/mapping/PersistentClass.java index 851f53e724..e8a299dd48 100644 --- a/hibernate-core/src/main/java/org/hibernate/mapping/PersistentClass.java +++ b/hibernate-core/src/main/java/org/hibernate/mapping/PersistentClass.java @@ -768,14 +768,14 @@ public abstract class PersistentClass implements Serializable, Filterable, MetaA } public void prepareTemporaryTables(Mapping mapping, Dialect dialect) { + temporaryIdTableName = dialect.generateTemporaryTableName( getTable().getName() ); if ( dialect.supportsTemporaryTables() ) { - temporaryIdTableName = dialect.generateTemporaryTableName( getTable().getName() ); Table table = new Table(); table.setName( temporaryIdTableName ); Iterator itr = getTable().getPrimaryKey().getColumnIterator(); while( itr.hasNext() ) { Column column = (Column) itr.next(); - table.addColumn( (Column) column.clone() ); + table.addColumn( column.clone() ); } temporaryIdTableDDL = table.sqlTemporaryTableCreateString( dialect, mapping ); }