HHH-15403 Likely Statement leak on invoking a stored procedure

This commit is contained in:
Andrea Boriero 2022-07-20 10:32:45 +02:00 committed by Sanne Grinovero
parent a4e52f91f8
commit 1f31284f33
6 changed files with 113 additions and 86 deletions

View File

@ -613,7 +613,7 @@ public class ProcedureCallImpl<R>
.getJdbcCoordinator() .getJdbcCoordinator()
.getStatementPreparer() .getStatementPreparer()
.prepareStatement( call.getSql(), true ); .prepareStatement( call.getSql(), true );
try {
// Register the parameter mode and type // Register the parameter mode and type
callableStatementSupport.registerParameters( callableStatementSupport.registerParameters(
procedureName, procedureName,
@ -648,8 +648,6 @@ public class ProcedureCallImpl<R>
} }
} }
final JdbcCallRefCursorExtractor[] extractors = refCursorExtractors.toArray( new JdbcCallRefCursorExtractor[0] );
final ExecutionContext executionContext = new ExecutionContext() { final ExecutionContext executionContext = new ExecutionContext() {
private final Callback callback = new CallbackImpl(); private final Callback callback = new CallbackImpl();
@ -687,7 +685,6 @@ public class ProcedureCallImpl<R>
// Note that this should actually happen in an executor // Note that this should actually happen in an executor
try {
int paramBindingPosition = call.getFunctionReturn() == null ? 1 : 2; int paramBindingPosition = call.getFunctionReturn() == null ? 1 : 2;
for ( JdbcParameterBinder parameterBinder : call.getParameterBinders() ) { for ( JdbcParameterBinder parameterBinder : call.getParameterBinders() ) {
parameterBinder.bindParameterValue( parameterBinder.bindParameterValue(
@ -700,13 +697,21 @@ public class ProcedureCallImpl<R>
} }
} }
catch (SQLException e) { catch (SQLException e) {
getSession().getJdbcCoordinator().getLogicalConnection().getResourceRegistry().release( statement );
throw getSession().getJdbcServices().getSqlExceptionHelper().convert( throw getSession().getJdbcServices().getSqlExceptionHelper().convert(
e, e,
"Error registering CallableStatement parameters", "Error registering CallableStatement parameters",
procedureName procedureName
); );
} }
return new ProcedureOutputsImpl( this, parameterRegistrations, extractors, statement );
return new ProcedureOutputsImpl(
this,
parameterRegistrations,
refCursorExtractors.toArray( new JdbcCallRefCursorExtractor[0] ),
statement
);
} }
@Override @Override

View File

@ -148,4 +148,9 @@ public class ProcedureOutputsImpl extends OutputsImpl implements ProcedureOutput
} }
} }
@Override
public void release() {
super.release();
getResultContext().getSession().getJdbcCoordinator().getLogicalConnection().getResourceRegistry().release( callableStatement );
}
} }

View File

@ -133,9 +133,16 @@ public final class ResourceRegistryStandardImpl implements ResourceRegistry {
else { else {
resultSets.remove( resultSet ); resultSets.remove( resultSet );
if ( resultSets.isEmpty() ) { if ( resultSets.isEmpty() ) {
try {
if ( statement.isClosed() ) {
xref.remove( statement ); xref.remove( statement );
} }
} }
catch (SQLException e) {
log.debugf( "Unable to release JDBC statement [%s]", e.getMessage() );
}
}
}
} }
else { else {
final Object removed = unassociatedResultSets == null ? null : unassociatedResultSets.remove( resultSet ); final Object removed = unassociatedResultSets == null ? null : unassociatedResultSets.remove( resultSet );

View File

@ -67,6 +67,10 @@ public class OutputsImpl implements Outputs {
} }
protected ResultContext getResultContext(){
return context;
}
protected void executeStatement() { protected void executeStatement() {
try { try {
final boolean isResultSet = jdbcStatement.execute(); final boolean isResultSet = jdbcStatement.execute();
@ -134,12 +138,7 @@ public class OutputsImpl implements Outputs {
@Override @Override
public void release() { public void release() {
try { context.getSession().getJdbcCoordinator().getLogicalConnection().getResourceRegistry().release( jdbcStatement );
jdbcStatement.close();
}
catch (SQLException e) {
log.debug( "Unable to close PreparedStatement", e );
}
} }
private List extractCurrentResults() { private List extractCurrentResults() {
@ -281,9 +280,6 @@ public class OutputsImpl implements Outputs {
} }
return results; return results;
} }
// catch (SQLException e) {
// throw context.getSession().getExceptionConverter().convert( e, "Error processing return rows" );
// }
finally { finally {
rowReader.finishUp( jdbcValuesSourceProcessingState ); rowReader.finishUp( jdbcValuesSourceProcessingState );
jdbcValuesSourceProcessingState.finishUp(); jdbcValuesSourceProcessingState.finishUp();

View File

@ -10,6 +10,7 @@ import java.sql.Connection;
import java.sql.PreparedStatement; import java.sql.PreparedStatement;
import java.sql.SQLException; import java.sql.SQLException;
import java.util.Arrays; import java.util.Arrays;
import java.util.List;
import org.hibernate.Session; import org.hibernate.Session;
import org.hibernate.dialect.OracleDialect; import org.hibernate.dialect.OracleDialect;
@ -18,6 +19,7 @@ import org.hibernate.engine.jdbc.spi.ResultSetReturn;
import org.hibernate.engine.jdbc.spi.StatementPreparer; import org.hibernate.engine.jdbc.spi.StatementPreparer;
import org.hibernate.engine.spi.SessionImplementor; import org.hibernate.engine.spi.SessionImplementor;
import org.hibernate.jdbc.Work; import org.hibernate.jdbc.Work;
import org.hibernate.procedure.ProcedureCall;
import org.hibernate.testing.RequiresDialect; import org.hibernate.testing.RequiresDialect;
import org.hibernate.testing.TestForIssue; import org.hibernate.testing.TestForIssue;
@ -84,15 +86,19 @@ public class CursorFromCallableTest extends BaseCoreFunctionalTestCase {
// Verify statement closing with JdbcCoordinator#hasRegisteredResources() instead. // Verify statement closing with JdbcCoordinator#hasRegisteredResources() instead.
// BigDecimal maxCursors = (BigDecimal) session.createSQLQuery( "SELECT value FROM v$parameter WHERE name = 'open_cursors'" ).uniqueResult(); // BigDecimal maxCursors = (BigDecimal) session.createSQLQuery( "SELECT value FROM v$parameter WHERE name = 'open_cursors'" ).uniqueResult();
// for ( int i = 0; i < maxCursors + 10; ++i ) { named_query_execution } // for ( int i = 0; i < maxCursors + 10; ++i ) { named_query_execution }
ProcedureCall namedStoredProcedureQuery = session.createNamedStoredProcedureQuery( "NumValue.getSomeValues" );
List resultList = namedStoredProcedureQuery.getResultList();
Assert.assertEquals( Assert.assertEquals(
Arrays.asList( new NumValue( 1, "Line 1" ), new NumValue( 2, "Line 2" ) ), Arrays.asList( new NumValue( 1, "Line 1" ), new NumValue( 2, "Line 2" ) ),
session.createNamedStoredProcedureQuery( "NumValue.getSomeValues" ).getResultList() resultList
); );
namedStoredProcedureQuery.close();
JdbcCoordinator jdbcCoordinator = ( (SessionImplementor) session ).getJdbcCoordinator(); JdbcCoordinator jdbcCoordinator = ( (SessionImplementor) session ).getJdbcCoordinator();
Assert.assertFalse( Assert.assertFalse(
"Prepared statement and result set should be released after query execution.", "Prepared statement and result set should be released after query execution.",
jdbcCoordinator.getLogicalConnection().getResourceRegistry().hasRegisteredResources() jdbcCoordinator.getLogicalConnection().getResourceRegistry().hasRegisteredResources()
); );
session.getTransaction().commit(); session.getTransaction().commit();
session.close(); session.close();
} }

View File

@ -119,6 +119,14 @@ public class PreparedStatementSpyConnectionProvider extends ConnectionProviderDe
return statementSpy; return statementSpy;
} ).when( connectionSpy ).prepareStatement( ArgumentMatchers.anyString() ); } ).when( connectionSpy ).prepareStatement( ArgumentMatchers.anyString() );
Mockito.doAnswer( invocation -> {
PreparedStatement statement = (PreparedStatement) invocation.callRealMethod();
PreparedStatement statementSpy = spy( statement, settingsForStatements );
String sql = (String) invocation.getArguments()[0];
preparedStatementMap.put( statementSpy, sql );
return statementSpy;
} ).when( connectionSpy ).prepareCall( ArgumentMatchers.anyString() );
Mockito.doAnswer( invocation -> { Mockito.doAnswer( invocation -> {
Statement statement = (Statement) invocation.callRealMethod(); Statement statement = (Statement) invocation.callRealMethod();
Statement statementSpy = spy( statement, settingsForStatements ); Statement statementSpy = spy( statement, settingsForStatements );