HHH-7725 - Make handling multi-table bulk HQL operations more pluggable

This commit is contained in:
Steve Ebersole 2012-10-31 11:22:33 -05:00
parent 9f0bbe10a6
commit 0ab36bed8c
8 changed files with 243 additions and 53 deletions

View File

@ -178,6 +178,10 @@ subprojects { subProject ->
)
test {
// pass along command line defined system props (-D) to the test
// pretty sure I used to have this limited to just certain prefixes, but that is no longer here
// and I no longer remember that groovy-magic needed to accomplish that
systemProperties = System.properties
systemProperties['hibernate.test.validatefailureexpected'] = true
maxHeapSize = "1024m"
}

View File

@ -32,16 +32,18 @@ import antlr.collections.AST;
import org.hibernate.HibernateException;
import org.hibernate.JDBCException;
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.hql.internal.ast.SqlGenerator;
import org.hibernate.internal.util.StringHelper;
import org.hibernate.mapping.Table;
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;
import org.hibernate.sql.SelectValues;
/**
* @author Steve Ebersole
@ -50,9 +52,22 @@ public class AbstractTableBasedBulkIdHandler {
private final SessionFactoryImplementor sessionFactory;
private final HqlSqlWalker walker;
private final String catalog;
private final String schema;
public AbstractTableBasedBulkIdHandler(SessionFactoryImplementor sessionFactory, HqlSqlWalker walker) {
this( sessionFactory, walker, null, null );
}
public AbstractTableBasedBulkIdHandler(
SessionFactoryImplementor sessionFactory,
HqlSqlWalker walker,
String catalog,
String schema) {
this.sessionFactory = sessionFactory;
this.walker = walker;
this.catalog = catalog;
this.schema = schema;
}
protected SessionFactoryImplementor factory() {
@ -115,9 +130,10 @@ public class AbstractTableBasedBulkIdHandler {
protected String generateIdInsertSelect(Queryable persister, String tableAlias, ProcessedWhereClause whereClause) {
Select select = new Select( sessionFactory.getDialect() );
SelectFragment selectFragment = new SelectFragment()
SelectValues selectClause = new SelectValues( sessionFactory.getDialect() )
.addColumns( tableAlias, persister.getIdentifierColumnNames(), persister.getIdentifierColumnNames() );
select.setSelectClause( selectFragment.toFragmentString().substring( 2 ) + extraIdSelectValues() );
addAnyExtraIdSelectValues( selectClause );
select.setSelectClause( selectClause.render() );
String rootTableName = persister.getTableName();
String fromJoinFragment = persister.fromJoinFragment( tableAlias, true, false );
@ -135,13 +151,12 @@ public class AbstractTableBasedBulkIdHandler {
}
}
if ( whereClause.userWhereClauseFragment.length() > 0 ) {
if ( whereClause.getUserWhereClauseFragment().length() > 0 ) {
if ( whereJoinFragment.length() > 0 ) {
whereJoinFragment += " and ";
}
}
select.setWhereClause( whereJoinFragment + whereClause.userWhereClauseFragment );
select.setWhereClause( whereJoinFragment + whereClause.getUserWhereClauseFragment() );
InsertSelect insert = new InsertSelect( sessionFactory.getDialect() );
if ( sessionFactory.getSettings().isCommentsEnabled() ) {
@ -152,12 +167,12 @@ public class AbstractTableBasedBulkIdHandler {
return insert.toStatementString();
}
protected String extraIdSelectValues() {
return "";
protected void addAnyExtraIdSelectValues(SelectValues selectClause) {
}
protected String determineIdTableName(Queryable persister) {
return persister.getTemporaryIdTableName();
// todo : use the identifier/name qualifier service once we pull that over to master
return Table.qualify( catalog, schema, persister.getTemporaryIdTableName() );
}
protected String generateIdSubselect(Queryable persister) {

View File

@ -28,6 +28,7 @@ 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.jdbc.spi.JdbcServices;
import org.hibernate.engine.spi.Mapping;
import org.hibernate.engine.spi.QueryParameters;
import org.hibernate.engine.spi.SessionFactoryImplementor;
@ -47,21 +48,21 @@ public interface MultiTableBulkIdStrategy {
* <li>Manually creating the tables immediately through the passed JDBC Connection access</li>
* </ul>
*
* @param dialect The dialect
* @param jdbcServices The JdbcService object
* @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);
public void prepare(JdbcServices jdbcServices, JdbcConnectionAccess connectionAccess, Mappings mappings, Mapping mapping, Map settings);
/**
* Release the strategy. Called as the SessionFactory is being shut down.
*
* @param dialect The dialect
* @param jdbcServices The JdbcService object
* @param connectionAccess Access to the JDBC Connection
*/
public void release(Dialect dialect, JdbcConnectionAccess connectionAccess);
public void release(JdbcServices jdbcServices, JdbcConnectionAccess connectionAccess);
/**
* Handler for dealing with multi-table HQL bulk update statements.

View File

@ -36,27 +36,33 @@ import java.util.Map;
import org.jboss.logging.Logger;
import org.hibernate.HibernateException;
import org.hibernate.JDBCException;
import org.hibernate.cfg.AvailableSettings;
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.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.CoreMessageLogger;
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.sql.SelectValues;
import org.hibernate.type.UUIDCharType;
/**
* @author Steve Ebersole
*/
public class PersistentTableBulkIdStrategy implements MultiTableBulkIdStrategy {
private static final Logger log = Logger.getLogger( PersistentTableBulkIdStrategy.class );
private static final CoreMessageLogger log = Logger.getMessageLogger(
CoreMessageLogger.class,
PersistentTableBulkIdStrategy.class.getName()
);
public static final String CLEAN_UP_ID_TABLES = "hibernate.hql.bulk_id_strategy.persistent.clean_up";
public static final String SCHEMA = "hibernate.hql.bulk_id_strategy.persistent.schema";
@ -69,7 +75,7 @@ public class PersistentTableBulkIdStrategy implements MultiTableBulkIdStrategy {
@Override
public void prepare(
Dialect dialect,
JdbcServices jdbcServices,
JdbcConnectionAccess connectionAccess,
Mappings mappings,
Mapping mapping,
@ -93,7 +99,7 @@ public class PersistentTableBulkIdStrategy implements MultiTableBulkIdStrategy {
final Table idTableDefinition = generateIdTableDefinition( entityMapping );
idTableDefinitions.add( idTableDefinition );
}
exportTableDefinitions( idTableDefinitions, dialect, connectionAccess, mappings, mapping );
exportTableDefinitions( idTableDefinitions, jdbcServices, connectionAccess, mappings, mapping );
}
protected Table generateIdTableDefinition(PersistentClass entityMapping) {
@ -104,7 +110,7 @@ public class PersistentTableBulkIdStrategy implements MultiTableBulkIdStrategy {
if ( schema != null ) {
idTable.setSchema( schema );
}
Iterator itr = entityMapping.getIdentityTable().getPrimaryKey().getColumnIterator();
Iterator itr = entityMapping.getTable().getPrimaryKey().getColumnIterator();
while( itr.hasNext() ) {
Column column = (Column) itr.next();
idTable.addColumn( column.clone() );
@ -120,12 +126,20 @@ public class PersistentTableBulkIdStrategy implements MultiTableBulkIdStrategy {
protected void exportTableDefinitions(
List<Table> idTableDefinitions,
Dialect dialect,
JdbcServices jdbcServices,
JdbcConnectionAccess connectionAccess,
Mappings mappings,
Mapping mapping) {
try {
Connection connection = connectionAccess.obtainConnection();
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 {
Statement statement = connection.createStatement();
@ -135,10 +149,12 @@ public class PersistentTableBulkIdStrategy implements MultiTableBulkIdStrategy {
if ( tableCleanUpDdl == null ) {
tableCleanUpDdl = new ArrayList<String>();
}
tableCleanUpDdl.add( idTableDefinition.sqlDropString( dialect, null, null ) );
tableCleanUpDdl.add( idTableDefinition.sqlDropString( jdbcServices.getDialect(), null, null ) );
}
try {
statement.execute( idTableDefinition.sqlCreateString( dialect, mapping, null, null ) );
final String sql = idTableDefinition.sqlCreateString( jdbcServices.getDialect(), mapping, null, null );
jdbcServices.getSqlStatementLogger().logStatement( sql );
statement.execute( sql );
}
catch (SQLException e) {
log.debugf( "Error attempting to export id-table [%s] : %s", idTableDefinition.getName(), e.getMessage() );
@ -162,8 +178,8 @@ public class PersistentTableBulkIdStrategy implements MultiTableBulkIdStrategy {
}
@Override
public void release(Dialect dialect, JdbcConnectionAccess connectionAccess) {
if ( ! cleanUpTables ) {
public void release(JdbcServices jdbcServices, JdbcConnectionAccess connectionAccess) {
if ( ! cleanUpTables || tableCleanUpDdl == null ) {
return;
}
@ -175,6 +191,7 @@ public class PersistentTableBulkIdStrategy implements MultiTableBulkIdStrategy {
for ( String cleanupDdl : tableCleanUpDdl ) {
try {
jdbcServices.getSqlStatementLogger().logStatement( cleanupDdl );
statement.execute( cleanupDdl );
}
catch (SQLException e) {
@ -202,8 +219,8 @@ public class PersistentTableBulkIdStrategy implements MultiTableBulkIdStrategy {
public UpdateHandler buildUpdateHandler(SessionFactoryImplementor factory, HqlSqlWalker walker) {
return new TableBasedUpdateHandlerImpl( factory, walker ) {
@Override
protected String extraIdSelectValues() {
return "cast(? as char)";
protected void addAnyExtraIdSelectValues(SelectValues selectClause) {
selectClause.addParameter( Types.CHAR, 36 );
}
@Override
@ -213,32 +230,65 @@ public class PersistentTableBulkIdStrategy implements MultiTableBulkIdStrategy {
@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 );
bindSessionIdentifier( ps, session, pos );
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 );
bindSessionIdentifier( ps, session, position );
}
@Override
protected void releaseFromUse(Queryable persister, SessionImplementor session) {
// clean up our id-table rows
cleanUpRows( determineIdTableName( persister ), session );
}
};
}
private void bindSessionIdentifier(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 );
}
private void cleanUpRows(String tableName, SessionImplementor session) {
final String sql = "delete from " + tableName + " where hib_sess_id=?";
try {
PreparedStatement ps = null;
try {
ps = session.getTransactionCoordinator().getJdbcCoordinator().getStatementPreparer().prepareStatement( sql, false );
bindSessionIdentifier( ps, session, 1 );
ps.executeUpdate();
}
finally {
if ( ps != null ) {
try {
ps.close();
}
catch( Throwable ignore ) {
// ignore
}
}
}
}
catch (SQLException e) {
throw convert( session.getFactory(), e, "Unable to clean up id table [" + tableName + "]", sql );
}
}
protected JDBCException convert(SessionFactoryImplementor factory, SQLException e, String message, String sql) {
throw factory.getSQLExceptionHelper().convert( e, message, sql );
}
@Override
public DeleteHandler buildDeleteHandler(SessionFactoryImplementor factory, HqlSqlWalker walker) {
return new TableBasedDeleteHandlerImpl( factory, walker ) {
@Override
protected String extraIdSelectValues() {
final Dialect dialect = factory().getDialect();
return dialect.requiresCastingOfParametersInSelectClause()
? dialect.cast( "?", Types.CHAR, 36 )
: "?";
protected void addAnyExtraIdSelectValues(SelectValues selectClause) {
selectClause.addParameter( Types.CHAR, 36 );
}
@Override
@ -248,19 +298,19 @@ public class PersistentTableBulkIdStrategy implements MultiTableBulkIdStrategy {
@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 );
bindSessionIdentifier( ps, session, pos );
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 );
bindSessionIdentifier( ps, session, 1 );
}
@Override
protected void releaseFromUse(Queryable persister, SessionImplementor session) {
// clean up our id-table rows
cleanUpRows( determineIdTableName( persister ), session );
}
};
}

View File

@ -43,7 +43,7 @@ import org.hibernate.sql.Delete;
/**
* @author Steve Ebersole
*/
class TableBasedDeleteHandlerImpl
public class TableBasedDeleteHandlerImpl
extends AbstractTableBasedBulkIdHandler
implements MultiTableBulkIdStrategy.DeleteHandler {
private static final Logger log = Logger.getLogger( TableBasedDeleteHandlerImpl.class );

View File

@ -32,7 +32,6 @@ import java.util.Map;
import org.jboss.logging.Logger;
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;
@ -56,12 +55,12 @@ public class TemporaryTableBulkIdStrategy implements MultiTableBulkIdStrategy {
);
@Override
public void prepare(Dialect dialect, JdbcConnectionAccess connectionAccess, Mappings mappings, Mapping mapping, Map settings) {
public void prepare(JdbcServices jdbcServices, JdbcConnectionAccess connectionAccess, Mappings mappings, Mapping mapping, Map settings) {
// nothing to do
}
@Override
public void release(Dialect dialect, JdbcConnectionAccess connectionAccess) {
public void release(JdbcServices jdbcServices, JdbcConnectionAccess connectionAccess) {
// nothing to do
}

View File

@ -486,7 +486,7 @@ public final class SessionFactoryImpl
LOG.debug( "Instantiated session factory" );
settings.getMultiTableBulkIdStrategy().prepare(
dialect,
jdbcServices,
buildLocalConnectionAccess(),
cfg.createMappings(),
cfg.buildMapping(),
@ -1383,7 +1383,7 @@ public final class SessionFactoryImpl
isClosed = true;
settings.getMultiTableBulkIdStrategy().release( dialect, buildLocalConnectionAccess() );
settings.getMultiTableBulkIdStrategy().release( jdbcServices, buildLocalConnectionAccess() );
Iterator iter = entityPersisters.values().iterator();
while ( iter.hasNext() ) {

View File

@ -0,0 +1,121 @@
/*
* 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.sql;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Set;
import org.jboss.logging.Logger;
import org.hibernate.dialect.Dialect;
/**
* Models a SELECT values lists. Eventually, rather than Strings, pass in the Column/Formula representations (something
* like {@link org.hibernate.sql.ordering.antlr.ColumnReference}/{@link org.hibernate.sql.ordering.antlr.FormulaReference}
*
* @author Steve Ebersole
*/
public class SelectValues {
private static final Logger log = Logger.getLogger( SelectValues.class );
private static class SelectValue {
private final String qualifier;
private final String value;
private final String alias;
private SelectValue(String qualifier, String value, String alias) {
this.qualifier = qualifier;
this.value = value;
this.alias = alias;
}
}
private final Dialect dialect;
private final ArrayList<SelectValue> selectValueList = new ArrayList<SelectValue>();
public SelectValues(Dialect dialect) {
this.dialect = dialect;
}
public SelectValues addColumns(String qualifier, String[] columnNames, String[] columnAliases) {
for ( int i = 0; i < columnNames.length; i++ ) {
if ( columnNames[i] != null ) {
addColumn( qualifier, columnNames[i], columnAliases[i] );
}
}
return this;
}
public SelectValues addColumn(String qualifier, String columnName, String columnAlias) {
selectValueList.add( new SelectValue( qualifier, columnName, columnAlias ) );
return this;
}
public SelectValues addParameter(int jdbcTypeCode, int length) {
final String selectExpression = dialect.requiresCastingOfParametersInSelectClause()
? dialect.cast( "?", jdbcTypeCode, length )
: "?";
selectValueList.add( new SelectValue( null, selectExpression, null ) );
return this;
}
public SelectValues addParameter(int jdbcTypeCode, int precision, int scale) {
final String selectExpression = dialect.requiresCastingOfParametersInSelectClause()
? dialect.cast( "?", jdbcTypeCode, precision, scale )
: "?";
selectValueList.add( new SelectValue( null, selectExpression, null ) );
return this;
}
public String render() {
final StringBuilder buf = new StringBuilder( selectValueList.size() * 10 );
final HashSet<String> uniqueAliases = new HashSet<String>();
boolean firstExpression = true;
for ( SelectValue selectValue : selectValueList ) {
if ( selectValue.alias != null ) {
if ( ! uniqueAliases.add( selectValue.alias ) ) {
log.debug( "Skipping select-value with non-unique alias" );
continue;
}
}
if ( firstExpression ) {
firstExpression = false;
}
else {
buf.append( ", " );
}
if ( selectValue.qualifier != null ) {
buf.append( selectValue.qualifier ).append( '.' );
}
buf.append( selectValue.value );
if ( selectValue.alias != null ) {
buf.append( " as " ).append( selectValue.alias );
}
}
return buf.toString();
}
}