Skip to content

Commit 0ab36be

Browse files
committed
HHH-7725 - Make handling multi-table bulk HQL operations more pluggable
1 parent 9f0bbe1 commit 0ab36be

File tree

8 files changed

+243
-53
lines changed

8 files changed

+243
-53
lines changed

build.gradle

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,10 @@ subprojects { subProject ->
178178
)
179179

180180
test {
181+
// pass along command line defined system props (-D) to the test
182+
// pretty sure I used to have this limited to just certain prefixes, but that is no longer here
183+
// and I no longer remember that groovy-magic needed to accomplish that
184+
systemProperties = System.properties
181185
systemProperties['hibernate.test.validatefailureexpected'] = true
182186
maxHeapSize = "1024m"
183187
}

hibernate-core/src/main/java/org/hibernate/hql/spi/AbstractTableBasedBulkIdHandler.java

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -32,16 +32,18 @@
3232

3333
import org.hibernate.HibernateException;
3434
import org.hibernate.JDBCException;
35+
import org.hibernate.engine.jdbc.spi.JdbcServices;
3536
import org.hibernate.engine.spi.SessionFactoryImplementor;
3637
import org.hibernate.engine.spi.SessionImplementor;
3738
import org.hibernate.hql.internal.ast.HqlSqlWalker;
3839
import org.hibernate.hql.internal.ast.SqlGenerator;
3940
import org.hibernate.internal.util.StringHelper;
41+
import org.hibernate.mapping.Table;
4042
import org.hibernate.param.ParameterSpecification;
4143
import org.hibernate.persister.entity.Queryable;
4244
import org.hibernate.sql.InsertSelect;
4345
import org.hibernate.sql.Select;
44-
import org.hibernate.sql.SelectFragment;
46+
import org.hibernate.sql.SelectValues;
4547

4648
/**
4749
* @author Steve Ebersole
@@ -50,9 +52,22 @@ public class AbstractTableBasedBulkIdHandler {
5052
private final SessionFactoryImplementor sessionFactory;
5153
private final HqlSqlWalker walker;
5254

55+
private final String catalog;
56+
private final String schema;
57+
5358
public AbstractTableBasedBulkIdHandler(SessionFactoryImplementor sessionFactory, HqlSqlWalker walker) {
59+
this( sessionFactory, walker, null, null );
60+
}
61+
62+
public AbstractTableBasedBulkIdHandler(
63+
SessionFactoryImplementor sessionFactory,
64+
HqlSqlWalker walker,
65+
String catalog,
66+
String schema) {
5467
this.sessionFactory = sessionFactory;
5568
this.walker = walker;
69+
this.catalog = catalog;
70+
this.schema = schema;
5671
}
5772

5873
protected SessionFactoryImplementor factory() {
@@ -115,9 +130,10 @@ protected ProcessedWhereClause processWhereClause(AST whereClause) {
115130

116131
protected String generateIdInsertSelect(Queryable persister, String tableAlias, ProcessedWhereClause whereClause) {
117132
Select select = new Select( sessionFactory.getDialect() );
118-
SelectFragment selectFragment = new SelectFragment()
133+
SelectValues selectClause = new SelectValues( sessionFactory.getDialect() )
119134
.addColumns( tableAlias, persister.getIdentifierColumnNames(), persister.getIdentifierColumnNames() );
120-
select.setSelectClause( selectFragment.toFragmentString().substring( 2 ) + extraIdSelectValues() );
135+
addAnyExtraIdSelectValues( selectClause );
136+
select.setSelectClause( selectClause.render() );
121137

122138
String rootTableName = persister.getTableName();
123139
String fromJoinFragment = persister.fromJoinFragment( tableAlias, true, false );
@@ -135,13 +151,12 @@ protected String generateIdInsertSelect(Queryable persister, String tableAlias,
135151
}
136152
}
137153

138-
if ( whereClause.userWhereClauseFragment.length() > 0 ) {
154+
if ( whereClause.getUserWhereClauseFragment().length() > 0 ) {
139155
if ( whereJoinFragment.length() > 0 ) {
140156
whereJoinFragment += " and ";
141157
}
142158
}
143-
144-
select.setWhereClause( whereJoinFragment + whereClause.userWhereClauseFragment );
159+
select.setWhereClause( whereJoinFragment + whereClause.getUserWhereClauseFragment() );
145160

146161
InsertSelect insert = new InsertSelect( sessionFactory.getDialect() );
147162
if ( sessionFactory.getSettings().isCommentsEnabled() ) {
@@ -152,12 +167,12 @@ protected String generateIdInsertSelect(Queryable persister, String tableAlias,
152167
return insert.toStatementString();
153168
}
154169

155-
protected String extraIdSelectValues() {
156-
return "";
170+
protected void addAnyExtraIdSelectValues(SelectValues selectClause) {
157171
}
158172

159173
protected String determineIdTableName(Queryable persister) {
160-
return persister.getTemporaryIdTableName();
174+
// todo : use the identifier/name qualifier service once we pull that over to master
175+
return Table.qualify( catalog, schema, persister.getTemporaryIdTableName() );
161176
}
162177

163178
protected String generateIdSubselect(Queryable persister) {

hibernate-core/src/main/java/org/hibernate/hql/spi/MultiTableBulkIdStrategy.java

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import org.hibernate.cfg.Mappings;
2929
import org.hibernate.dialect.Dialect;
3030
import org.hibernate.engine.jdbc.spi.JdbcConnectionAccess;
31+
import org.hibernate.engine.jdbc.spi.JdbcServices;
3132
import org.hibernate.engine.spi.Mapping;
3233
import org.hibernate.engine.spi.QueryParameters;
3334
import org.hibernate.engine.spi.SessionFactoryImplementor;
@@ -47,21 +48,21 @@ public interface MultiTableBulkIdStrategy {
4748
* <li>Manually creating the tables immediately through the passed JDBC Connection access</li>
4849
* </ul>
4950
*
50-
* @param dialect The dialect
51+
* @param jdbcServices The JdbcService object
5152
* @param connectionAccess Access to the JDBC Connection
5253
* @param mappings The Hibernate Mappings object, for access to O/RM mapping information
5354
* @param mapping The Hibernate Mapping contract, mainly for use in DDL generation
5455
* @param settings Configuration settings
5556
*/
56-
public void prepare(Dialect dialect, JdbcConnectionAccess connectionAccess, Mappings mappings, Mapping mapping, Map settings);
57+
public void prepare(JdbcServices jdbcServices, JdbcConnectionAccess connectionAccess, Mappings mappings, Mapping mapping, Map settings);
5758

5859
/**
5960
* Release the strategy. Called as the SessionFactory is being shut down.
6061
*
61-
* @param dialect The dialect
62+
* @param jdbcServices The JdbcService object
6263
* @param connectionAccess Access to the JDBC Connection
6364
*/
64-
public void release(Dialect dialect, JdbcConnectionAccess connectionAccess);
65+
public void release(JdbcServices jdbcServices, JdbcConnectionAccess connectionAccess);
6566

6667
/**
6768
* Handler for dealing with multi-table HQL bulk update statements.

hibernate-core/src/main/java/org/hibernate/hql/spi/PersistentTableBulkIdStrategy.java

Lines changed: 84 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -36,27 +36,33 @@
3636
import org.jboss.logging.Logger;
3737

3838
import org.hibernate.HibernateException;
39+
import org.hibernate.JDBCException;
3940
import org.hibernate.cfg.AvailableSettings;
4041
import org.hibernate.cfg.Mappings;
41-
import org.hibernate.dialect.Dialect;
4242
import org.hibernate.engine.jdbc.spi.JdbcConnectionAccess;
43+
import org.hibernate.engine.jdbc.spi.JdbcServices;
4344
import org.hibernate.engine.spi.Mapping;
4445
import org.hibernate.engine.spi.SessionFactoryImplementor;
4546
import org.hibernate.engine.spi.SessionImplementor;
4647
import org.hibernate.hql.internal.ast.HqlSqlWalker;
4748
import org.hibernate.internal.AbstractSessionImpl;
49+
import org.hibernate.internal.CoreMessageLogger;
4850
import org.hibernate.internal.util.config.ConfigurationHelper;
4951
import org.hibernate.mapping.Column;
5052
import org.hibernate.mapping.PersistentClass;
5153
import org.hibernate.mapping.Table;
5254
import org.hibernate.persister.entity.Queryable;
55+
import org.hibernate.sql.SelectValues;
5356
import org.hibernate.type.UUIDCharType;
5457

5558
/**
5659
* @author Steve Ebersole
5760
*/
5861
public class PersistentTableBulkIdStrategy implements MultiTableBulkIdStrategy {
59-
private static final Logger log = Logger.getLogger( PersistentTableBulkIdStrategy.class );
62+
private static final CoreMessageLogger log = Logger.getMessageLogger(
63+
CoreMessageLogger.class,
64+
PersistentTableBulkIdStrategy.class.getName()
65+
);
6066

6167
public static final String CLEAN_UP_ID_TABLES = "hibernate.hql.bulk_id_strategy.persistent.clean_up";
6268
public static final String SCHEMA = "hibernate.hql.bulk_id_strategy.persistent.schema";
@@ -69,7 +75,7 @@ public class PersistentTableBulkIdStrategy implements MultiTableBulkIdStrategy {
6975

7076
@Override
7177
public void prepare(
72-
Dialect dialect,
78+
JdbcServices jdbcServices,
7379
JdbcConnectionAccess connectionAccess,
7480
Mappings mappings,
7581
Mapping mapping,
@@ -93,7 +99,7 @@ public void prepare(
9399
final Table idTableDefinition = generateIdTableDefinition( entityMapping );
94100
idTableDefinitions.add( idTableDefinition );
95101
}
96-
exportTableDefinitions( idTableDefinitions, dialect, connectionAccess, mappings, mapping );
102+
exportTableDefinitions( idTableDefinitions, jdbcServices, connectionAccess, mappings, mapping );
97103
}
98104

99105
protected Table generateIdTableDefinition(PersistentClass entityMapping) {
@@ -104,7 +110,7 @@ protected Table generateIdTableDefinition(PersistentClass entityMapping) {
104110
if ( schema != null ) {
105111
idTable.setSchema( schema );
106112
}
107-
Iterator itr = entityMapping.getIdentityTable().getPrimaryKey().getColumnIterator();
113+
Iterator itr = entityMapping.getTable().getPrimaryKey().getColumnIterator();
108114
while( itr.hasNext() ) {
109115
Column column = (Column) itr.next();
110116
idTable.addColumn( column.clone() );
@@ -120,12 +126,20 @@ protected Table generateIdTableDefinition(PersistentClass entityMapping) {
120126

121127
protected void exportTableDefinitions(
122128
List<Table> idTableDefinitions,
123-
Dialect dialect,
129+
JdbcServices jdbcServices,
124130
JdbcConnectionAccess connectionAccess,
125131
Mappings mappings,
126132
Mapping mapping) {
127133
try {
128-
Connection connection = connectionAccess.obtainConnection();
134+
Connection connection;
135+
try {
136+
connection = connectionAccess.obtainConnection();
137+
}
138+
catch (UnsupportedOperationException e) {
139+
// assume this comes from org.hibernate.engine.jdbc.connections.internal.UserSuppliedConnectionProviderImpl
140+
log.debug( "Unable to obtain JDBC connection; assuming ID tables already exist or wont be needed" );
141+
return;
142+
}
129143

130144
try {
131145
Statement statement = connection.createStatement();
@@ -135,10 +149,12 @@ protected void exportTableDefinitions(
135149
if ( tableCleanUpDdl == null ) {
136150
tableCleanUpDdl = new ArrayList<String>();
137151
}
138-
tableCleanUpDdl.add( idTableDefinition.sqlDropString( dialect, null, null ) );
152+
tableCleanUpDdl.add( idTableDefinition.sqlDropString( jdbcServices.getDialect(), null, null ) );
139153
}
140154
try {
141-
statement.execute( idTableDefinition.sqlCreateString( dialect, mapping, null, null ) );
155+
final String sql = idTableDefinition.sqlCreateString( jdbcServices.getDialect(), mapping, null, null );
156+
jdbcServices.getSqlStatementLogger().logStatement( sql );
157+
statement.execute( sql );
142158
}
143159
catch (SQLException e) {
144160
log.debugf( "Error attempting to export id-table [%s] : %s", idTableDefinition.getName(), e.getMessage() );
@@ -162,8 +178,8 @@ protected void exportTableDefinitions(
162178
}
163179

164180
@Override
165-
public void release(Dialect dialect, JdbcConnectionAccess connectionAccess) {
166-
if ( ! cleanUpTables ) {
181+
public void release(JdbcServices jdbcServices, JdbcConnectionAccess connectionAccess) {
182+
if ( ! cleanUpTables || tableCleanUpDdl == null ) {
167183
return;
168184
}
169185

@@ -175,6 +191,7 @@ public void release(Dialect dialect, JdbcConnectionAccess connectionAccess) {
175191

176192
for ( String cleanupDdl : tableCleanUpDdl ) {
177193
try {
194+
jdbcServices.getSqlStatementLogger().logStatement( cleanupDdl );
178195
statement.execute( cleanupDdl );
179196
}
180197
catch (SQLException e) {
@@ -202,8 +219,8 @@ public void release(Dialect dialect, JdbcConnectionAccess connectionAccess) {
202219
public UpdateHandler buildUpdateHandler(SessionFactoryImplementor factory, HqlSqlWalker walker) {
203220
return new TableBasedUpdateHandlerImpl( factory, walker ) {
204221
@Override
205-
protected String extraIdSelectValues() {
206-
return "cast(? as char)";
222+
protected void addAnyExtraIdSelectValues(SelectValues selectClause) {
223+
selectClause.addParameter( Types.CHAR, 36 );
207224
}
208225

209226
@Override
@@ -213,32 +230,65 @@ protected String generateIdSubselect(Queryable persister) {
213230

214231
@Override
215232
protected int handlePrependedParametersOnIdSelection(PreparedStatement ps, SessionImplementor session, int pos) throws SQLException {
216-
if ( ! AbstractSessionImpl.class.isInstance( session ) ) {
217-
throw new HibernateException( "Only available on SessionImpl instances" );
218-
}
219-
UUIDCharType.INSTANCE.set( ps, ( (AbstractSessionImpl) session ).getSessionIdentifier(), pos, session );
233+
bindSessionIdentifier( ps, session, pos );
220234
return 1;
221235
}
222236

223237
@Override
224238
protected void handleAddedParametersOnUpdate(PreparedStatement ps, SessionImplementor session, int position) throws SQLException {
225-
if ( ! AbstractSessionImpl.class.isInstance( session ) ) {
226-
throw new HibernateException( "Only available on SessionImpl instances" );
227-
}
228-
UUIDCharType.INSTANCE.set( ps, ( (AbstractSessionImpl) session ).getSessionIdentifier(), position, session );
239+
bindSessionIdentifier( ps, session, position );
240+
}
241+
242+
@Override
243+
protected void releaseFromUse(Queryable persister, SessionImplementor session) {
244+
// clean up our id-table rows
245+
cleanUpRows( determineIdTableName( persister ), session );
229246
}
230247
};
231248
}
232249

250+
private void bindSessionIdentifier(PreparedStatement ps, SessionImplementor session, int position) throws SQLException {
251+
if ( ! AbstractSessionImpl.class.isInstance( session ) ) {
252+
throw new HibernateException( "Only available on SessionImpl instances" );
253+
}
254+
UUIDCharType.INSTANCE.set( ps, ( (AbstractSessionImpl) session ).getSessionIdentifier(), position, session );
255+
}
256+
257+
private void cleanUpRows(String tableName, SessionImplementor session) {
258+
final String sql = "delete from " + tableName + " where hib_sess_id=?";
259+
try {
260+
PreparedStatement ps = null;
261+
try {
262+
ps = session.getTransactionCoordinator().getJdbcCoordinator().getStatementPreparer().prepareStatement( sql, false );
263+
bindSessionIdentifier( ps, session, 1 );
264+
ps.executeUpdate();
265+
}
266+
finally {
267+
if ( ps != null ) {
268+
try {
269+
ps.close();
270+
}
271+
catch( Throwable ignore ) {
272+
// ignore
273+
}
274+
}
275+
}
276+
}
277+
catch (SQLException e) {
278+
throw convert( session.getFactory(), e, "Unable to clean up id table [" + tableName + "]", sql );
279+
}
280+
}
281+
282+
protected JDBCException convert(SessionFactoryImplementor factory, SQLException e, String message, String sql) {
283+
throw factory.getSQLExceptionHelper().convert( e, message, sql );
284+
}
285+
233286
@Override
234287
public DeleteHandler buildDeleteHandler(SessionFactoryImplementor factory, HqlSqlWalker walker) {
235288
return new TableBasedDeleteHandlerImpl( factory, walker ) {
236289
@Override
237-
protected String extraIdSelectValues() {
238-
final Dialect dialect = factory().getDialect();
239-
return dialect.requiresCastingOfParametersInSelectClause()
240-
? dialect.cast( "?", Types.CHAR, 36 )
241-
: "?";
290+
protected void addAnyExtraIdSelectValues(SelectValues selectClause) {
291+
selectClause.addParameter( Types.CHAR, 36 );
242292
}
243293

244294
@Override
@@ -248,19 +298,19 @@ protected String generateIdSubselect(Queryable persister) {
248298

249299
@Override
250300
protected int handlePrependedParametersOnIdSelection(PreparedStatement ps, SessionImplementor session, int pos) throws SQLException {
251-
if ( ! AbstractSessionImpl.class.isInstance( session ) ) {
252-
throw new HibernateException( "Only available on SessionImpl instances" );
253-
}
254-
UUIDCharType.INSTANCE.set( ps, ( (AbstractSessionImpl) session ).getSessionIdentifier(), pos, session );
301+
bindSessionIdentifier( ps, session, pos );
255302
return 1;
256303
}
257304

258305
@Override
259306
protected void handleAddedParametersOnDelete(PreparedStatement ps, SessionImplementor session) throws SQLException {
260-
if ( ! AbstractSessionImpl.class.isInstance( session ) ) {
261-
throw new HibernateException( "Only available on SessionImpl instances" );
262-
}
263-
UUIDCharType.INSTANCE.set( ps, ( (AbstractSessionImpl) session ).getSessionIdentifier(), 1, session );
307+
bindSessionIdentifier( ps, session, 1 );
308+
}
309+
310+
@Override
311+
protected void releaseFromUse(Queryable persister, SessionImplementor session) {
312+
// clean up our id-table rows
313+
cleanUpRows( determineIdTableName( persister ), session );
264314
}
265315
};
266316
}

hibernate-core/src/main/java/org/hibernate/hql/spi/TableBasedDeleteHandlerImpl.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@
4343
/**
4444
* @author Steve Ebersole
4545
*/
46-
class TableBasedDeleteHandlerImpl
46+
public class TableBasedDeleteHandlerImpl
4747
extends AbstractTableBasedBulkIdHandler
4848
implements MultiTableBulkIdStrategy.DeleteHandler {
4949
private static final Logger log = Logger.getLogger( TableBasedDeleteHandlerImpl.class );

0 commit comments

Comments
 (0)