HHH-18692 Hibernate attempts to close batched statements multiple times

This commit is contained in:
Andrea Boriero 2024-10-08 17:24:44 +02:00
parent 8524c1e4d6
commit 7e411943cc
7 changed files with 93 additions and 29 deletions

View File

@ -162,26 +162,11 @@ public class BatchImpl implements Batch {
if ( batchPosition == batchSizeToUse ) { if ( batchPosition == batchSizeToUse ) {
notifyObserversImplicitExecution(); notifyObserversImplicitExecution();
performExecution(); performExecution();
batchPosition = 0;
batchExecuted = true;
} }
} }
protected void releaseStatements() { protected void releaseStatements() {
statementGroup.forEachStatement( (tableName, statementDetails) -> {
if ( statementDetails.getStatement() == null ) {
BATCH_LOGGER.debugf(
"PreparedStatementDetails did not contain PreparedStatement on #releaseStatements : %s",
statementDetails.getSqlString()
);
}
else {
clearBatch( statementDetails );
}
} );
statementGroup.release(); statementGroup.release();
jdbcCoordinator.afterStatementExecution();
} }
protected void clearBatch(PreparedStatementDetails statementDetails) { protected void clearBatch(PreparedStatementDetails statementDetails) {
@ -299,8 +284,10 @@ public class BatchImpl implements Batch {
} }
} }
} ); } );
batchExecuted = true;
} }
finally { finally {
jdbcCoordinator.afterStatementExecution();
batchPosition = 0; batchPosition = 0;
} }
} }

View File

@ -167,8 +167,12 @@ public class JdbcCoordinatorImpl implements JdbcCoordinator {
return currentBatch; return currentBatch;
} }
else { else {
currentBatch.execute(); try {
currentBatch.release(); currentBatch.execute();
}
finally {
currentBatch.release();
}
} }
} }
@ -194,7 +198,12 @@ public class JdbcCoordinatorImpl implements JdbcCoordinator {
public void conditionallyExecuteBatch(BatchKey key) { public void conditionallyExecuteBatch(BatchKey key) {
if ( currentBatch != null && !currentBatch.getKey().equals( key ) ) { if ( currentBatch != null && !currentBatch.getKey().equals( key ) ) {
JdbcBatchLogging.BATCH_LOGGER.debugf( "Conditionally executing batch - %s", currentBatch.getKey() ); JdbcBatchLogging.BATCH_LOGGER.debugf( "Conditionally executing batch - %s", currentBatch.getKey() );
currentBatch.execute(); try {
currentBatch.execute();
}
finally {
currentBatch.release();
}
} }
} }

View File

@ -56,4 +56,8 @@ public interface PreparedStatementDetails {
} }
void releaseStatement(SharedSessionContractImplementor session); void releaseStatement(SharedSessionContractImplementor session);
default boolean toRelease(){
return false;
}
} }

View File

@ -0,0 +1,58 @@
/*
* SPDX-License-Identifier: LGPL-2.1-or-later
* Copyright Red Hat Inc. and Hibernate Authors
*/
package org.hibernate.engine.jdbc.mutation.internal;
import org.hibernate.engine.jdbc.mutation.group.PreparedStatementDetails;
import org.hibernate.engine.jdbc.mutation.group.PreparedStatementGroup;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import static org.hibernate.engine.jdbc.batch.JdbcBatchLogging.BATCH_LOGGER;
import static org.hibernate.engine.jdbc.batch.JdbcBatchLogging.BATCH_MESSAGE_LOGGER;
public abstract class AbstractPreparedStatementGroup implements PreparedStatementGroup {
private final SharedSessionContractImplementor session;
public AbstractPreparedStatementGroup(SharedSessionContractImplementor session) {
this.session = session;
}
protected void clearBatch(PreparedStatementDetails statementDetails) {
final PreparedStatement statement = statementDetails.getStatement();
assert statement != null;
try {
// This code can be called after the connection is released
// and the statement is closed. If the statement is closed,
// then SQLException will be thrown when PreparedStatement#clearBatch
// is called.
// Ensure the statement is not closed before
// calling PreparedStatement#clearBatch.
if ( !statement.isClosed() ) {
statement.clearBatch();
}
}
catch ( SQLException e ) {
BATCH_MESSAGE_LOGGER.unableToReleaseBatchStatement();
}
}
protected void release(PreparedStatementDetails statementDetails) {
if ( statementDetails.toRelease() ) {
if ( statementDetails.getStatement() == null ) {
BATCH_LOGGER.debugf(
"PreparedStatementDetails did not contain PreparedStatement on #releaseStatements : %s",
statementDetails.getSqlString()
);
}
else {
clearBatch( statementDetails );
}
statementDetails.releaseStatement( session );
}
}
}

View File

@ -30,6 +30,8 @@ public class PreparedStatementDetailsStandard implements PreparedStatementDetail
private PreparedStatement statement; private PreparedStatement statement;
private boolean toRelease;
public PreparedStatementDetailsStandard( public PreparedStatementDetailsStandard(
PreparableMutationOperation tableMutation, PreparableMutationOperation tableMutation,
Supplier<PreparedStatement> jdbcStatementCreator, Supplier<PreparedStatement> jdbcStatementCreator,
@ -66,6 +68,7 @@ public class PreparedStatementDetailsStandard implements PreparedStatementDetail
if ( statement != null ) { if ( statement != null ) {
session.getJdbcCoordinator().getLogicalConnection().getResourceRegistry().release( statement ); session.getJdbcCoordinator().getLogicalConnection().getResourceRegistry().release( statement );
statement = null; statement = null;
toRelease = false;
} }
} }
@ -82,6 +85,7 @@ public class PreparedStatementDetailsStandard implements PreparedStatementDetail
@Override @Override
public PreparedStatement resolveStatement() { public PreparedStatement resolveStatement() {
if ( statement == null ) { if ( statement == null ) {
toRelease = true;
statement = jdbcStatementCreator.get(); statement = jdbcStatementCreator.get();
try { try {
expectation.prepare( statement ); expectation.prepare( statement );
@ -102,6 +106,11 @@ public class PreparedStatementDetailsStandard implements PreparedStatementDetail
return expectation; return expectation;
} }
@Override
public boolean toRelease() {
return toRelease;
}
@Override @Override
public String toString() { public String toString() {
return "PreparedStatementDetails(" + sql + ")"; return "PreparedStatementDetails(" + sql + ")";

View File

@ -8,7 +8,6 @@ import java.util.function.BiConsumer;
import java.util.function.Predicate; import java.util.function.Predicate;
import org.hibernate.engine.jdbc.mutation.group.PreparedStatementDetails; import org.hibernate.engine.jdbc.mutation.group.PreparedStatementDetails;
import org.hibernate.engine.jdbc.mutation.group.PreparedStatementGroup;
import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.generator.values.GeneratedValuesMutationDelegate; import org.hibernate.generator.values.GeneratedValuesMutationDelegate;
import org.hibernate.sql.model.PreparableMutationOperation; import org.hibernate.sql.model.PreparableMutationOperation;
@ -20,9 +19,8 @@ import org.hibernate.sql.model.TableMapping;
* *
* @author Steve Ebersole * @author Steve Ebersole
*/ */
public class PreparedStatementGroupSingleTable implements PreparedStatementGroup { public class PreparedStatementGroupSingleTable extends AbstractPreparedStatementGroup {
private final PreparableMutationOperation jdbcMutation; private final PreparableMutationOperation jdbcMutation;
private final SharedSessionContractImplementor session;
private final PreparedStatementDetails statementDetails; private final PreparedStatementDetails statementDetails;
@ -36,9 +34,9 @@ public class PreparedStatementGroupSingleTable implements PreparedStatementGroup
PreparableMutationOperation jdbcMutation, PreparableMutationOperation jdbcMutation,
GeneratedValuesMutationDelegate delegate, GeneratedValuesMutationDelegate delegate,
SharedSessionContractImplementor session) { SharedSessionContractImplementor session) {
super(session);
this.jdbcMutation = jdbcMutation; this.jdbcMutation = jdbcMutation;
this.statementDetails = ModelMutationHelper.standardPreparation( jdbcMutation, delegate, session ); this.statementDetails = ModelMutationHelper.standardPreparation( jdbcMutation, delegate, session );
this.session = session;
} }
protected TableMapping getMutatingTableDetails() { protected TableMapping getMutatingTableDetails() {
@ -89,7 +87,7 @@ public class PreparedStatementGroupSingleTable implements PreparedStatementGroup
@Override @Override
public void release() { public void release() {
if ( statementDetails != null ) { if ( statementDetails != null ) {
statementDetails.releaseStatement( session ); release( statementDetails );
} }
} }
} }

View File

@ -16,7 +16,6 @@ import java.util.function.Predicate;
import java.util.function.Supplier; import java.util.function.Supplier;
import org.hibernate.engine.jdbc.mutation.group.PreparedStatementDetails; import org.hibernate.engine.jdbc.mutation.group.PreparedStatementDetails;
import org.hibernate.engine.jdbc.mutation.group.PreparedStatementGroup;
import org.hibernate.engine.jdbc.spi.JdbcCoordinator; import org.hibernate.engine.jdbc.spi.JdbcCoordinator;
import org.hibernate.engine.jdbc.spi.MutationStatementPreparer; import org.hibernate.engine.jdbc.spi.MutationStatementPreparer;
import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.engine.spi.SharedSessionContractImplementor;
@ -32,11 +31,10 @@ import org.hibernate.sql.model.TableMapping;
* *
* @author Steve Ebersole * @author Steve Ebersole
*/ */
public class PreparedStatementGroupStandard implements PreparedStatementGroup { public class PreparedStatementGroupStandard extends AbstractPreparedStatementGroup {
private final MutationType mutationType; private final MutationType mutationType;
private final MutationTarget<?> mutationTarget; private final MutationTarget<?> mutationTarget;
private final List<PreparableMutationOperation> jdbcMutations; private final List<PreparableMutationOperation> jdbcMutations;
private final SharedSessionContractImplementor session;
private final SortedMap<String, PreparedStatementDetails> statementMap; private final SortedMap<String, PreparedStatementDetails> statementMap;
@ -47,11 +45,11 @@ public class PreparedStatementGroupStandard implements PreparedStatementGroup {
GeneratedValuesMutationDelegate generatedValuesDelegate, GeneratedValuesMutationDelegate generatedValuesDelegate,
List<PreparableMutationOperation> jdbcMutations, List<PreparableMutationOperation> jdbcMutations,
SharedSessionContractImplementor session) { SharedSessionContractImplementor session) {
super( session );
this.mutationType = mutationType; this.mutationType = mutationType;
this.mutationTarget = mutationTarget; this.mutationTarget = mutationTarget;
this.jdbcMutations = jdbcMutations; this.jdbcMutations = jdbcMutations;
this.session = session;
this.statementMap = createStatementDetailsMap( jdbcMutations, mutationType, generatedValuesDelegate, session ); this.statementMap = createStatementDetailsMap( jdbcMutations, mutationType, generatedValuesDelegate, session );
} }
@ -143,10 +141,11 @@ public class PreparedStatementGroupStandard implements PreparedStatementGroup {
@Override @Override
public void release() { public void release() {
statementMap.forEach( (tableName, statementDetails) -> statementDetails.releaseStatement( session ) ); statementMap.forEach( (tableName, statementDetails) -> {
release( statementDetails );
} );
} }
private static SortedMap<String, PreparedStatementDetails> createStatementDetailsMap( private static SortedMap<String, PreparedStatementDetails> createStatementDetailsMap(
List<PreparableMutationOperation> jdbcMutations, List<PreparableMutationOperation> jdbcMutations,
MutationType mutationType, MutationType mutationType,