diff --git a/hibernate-core/src/main/java/org/hibernate/procedure/internal/ProcedureCallImpl.java b/hibernate-core/src/main/java/org/hibernate/procedure/internal/ProcedureCallImpl.java index 989c439e22..dc88108bf0 100644 --- a/hibernate-core/src/main/java/org/hibernate/procedure/internal/ProcedureCallImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/procedure/internal/ProcedureCallImpl.java @@ -613,81 +613,78 @@ public class ProcedureCallImpl .getJdbcCoordinator() .getStatementPreparer() .prepareStatement( call.getSql(), true ); - - // Register the parameter mode and type - callableStatementSupport.registerParameters( - procedureName, - call, - statement, - parameterMetadata, - getSession() - ); - - // Apply the parameter bindings - final JdbcParameterBindings jdbcParameterBindings = new JdbcParameterBindingsImpl( parameterRegistrations.size() ); - for ( Map.Entry, JdbcCallParameterRegistration> entry : parameterRegistrations.entrySet() ) { - final JdbcCallParameterRegistration registration = entry.getValue(); - if ( registration.getParameterBinder() != null ) { - final ProcedureParameter parameter = entry.getKey(); - final QueryParameterBinding binding = getParameterBindings().getBinding( parameter ); - if ( !binding.isBound() ) { - if ( parameter.getPosition() == null ) { - throw new IllegalArgumentException( "The parameter named [" + parameter + "] was not set! You need to call the setParameter method." ); - } - else { - throw new IllegalArgumentException( "The parameter at position [" + parameter + "] was not set! You need to call the setParameter method." ); - } - } - jdbcParameterBindings.addBinding( - (JdbcParameter) registration.getParameterBinder(), - new JdbcParameterBindingImpl( - (JdbcMapping) registration.getParameterType(), - binding.getBindValue() - ) - ); - } - } - - final JdbcCallRefCursorExtractor[] extractors = refCursorExtractors.toArray( new JdbcCallRefCursorExtractor[0] ); - - final ExecutionContext executionContext = new ExecutionContext() { - private final Callback callback = new CallbackImpl(); - - @Override - public SharedSessionContractImplementor getSession() { - return ProcedureCallImpl.this.getSession(); - } - - @Override - public QueryOptions getQueryOptions() { - return new QueryOptionsAdapter() { - @Override - public Boolean isReadOnly() { - return false; - } - }; - } - - @Override - public String getQueryIdentifier(String sql) { - return sql; - } - - @Override - public QueryParameterBindings getQueryParameterBindings() { - return QueryParameterBindings.NO_PARAM_BINDINGS; - } - - @Override - public Callback getCallback() { - return callback; - } - - }; - - // Note that this should actually happen in an executor - try { + // Register the parameter mode and type + callableStatementSupport.registerParameters( + procedureName, + call, + statement, + parameterMetadata, + getSession() + ); + + // Apply the parameter bindings + final JdbcParameterBindings jdbcParameterBindings = new JdbcParameterBindingsImpl( parameterRegistrations.size() ); + for ( Map.Entry, JdbcCallParameterRegistration> entry : parameterRegistrations.entrySet() ) { + final JdbcCallParameterRegistration registration = entry.getValue(); + if ( registration.getParameterBinder() != null ) { + final ProcedureParameter parameter = entry.getKey(); + final QueryParameterBinding binding = getParameterBindings().getBinding( parameter ); + if ( !binding.isBound() ) { + if ( parameter.getPosition() == null ) { + throw new IllegalArgumentException( "The parameter named [" + parameter + "] was not set! You need to call the setParameter method." ); + } + else { + throw new IllegalArgumentException( "The parameter at position [" + parameter + "] was not set! You need to call the setParameter method." ); + } + } + jdbcParameterBindings.addBinding( + (JdbcParameter) registration.getParameterBinder(), + new JdbcParameterBindingImpl( + (JdbcMapping) registration.getParameterType(), + binding.getBindValue() + ) + ); + } + } + + final ExecutionContext executionContext = new ExecutionContext() { + private final Callback callback = new CallbackImpl(); + + @Override + public SharedSessionContractImplementor getSession() { + return ProcedureCallImpl.this.getSession(); + } + + @Override + public QueryOptions getQueryOptions() { + return new QueryOptionsAdapter() { + @Override + public Boolean isReadOnly() { + return false; + } + }; + } + + @Override + public String getQueryIdentifier(String sql) { + return sql; + } + + @Override + public QueryParameterBindings getQueryParameterBindings() { + return QueryParameterBindings.NO_PARAM_BINDINGS; + } + + @Override + public Callback getCallback() { + return callback; + } + + }; + + // Note that this should actually happen in an executor + int paramBindingPosition = call.getFunctionReturn() == null ? 1 : 2; for ( JdbcParameterBinder parameterBinder : call.getParameterBinders() ) { parameterBinder.bindParameterValue( @@ -700,13 +697,21 @@ public class ProcedureCallImpl } } catch (SQLException e) { + getSession().getJdbcCoordinator().getLogicalConnection().getResourceRegistry().release( statement ); throw getSession().getJdbcServices().getSqlExceptionHelper().convert( e, "Error registering CallableStatement parameters", procedureName ); } - return new ProcedureOutputsImpl( this, parameterRegistrations, extractors, statement ); + + return new ProcedureOutputsImpl( + this, + parameterRegistrations, + refCursorExtractors.toArray( new JdbcCallRefCursorExtractor[0] ), + statement + ); + } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/procedure/internal/ProcedureOutputsImpl.java b/hibernate-core/src/main/java/org/hibernate/procedure/internal/ProcedureOutputsImpl.java index 588b54119b..ac2ec87df2 100644 --- a/hibernate-core/src/main/java/org/hibernate/procedure/internal/ProcedureOutputsImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/procedure/internal/ProcedureOutputsImpl.java @@ -148,4 +148,9 @@ public class ProcedureOutputsImpl extends OutputsImpl implements ProcedureOutput } } + @Override + public void release() { + super.release(); + getResultContext().getSession().getJdbcCoordinator().getLogicalConnection().getResourceRegistry().release( callableStatement ); + } } diff --git a/hibernate-core/src/main/java/org/hibernate/resource/jdbc/internal/ResourceRegistryStandardImpl.java b/hibernate-core/src/main/java/org/hibernate/resource/jdbc/internal/ResourceRegistryStandardImpl.java index b19e1f2635..4e3c33df8b 100644 --- a/hibernate-core/src/main/java/org/hibernate/resource/jdbc/internal/ResourceRegistryStandardImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/resource/jdbc/internal/ResourceRegistryStandardImpl.java @@ -133,7 +133,14 @@ public final class ResourceRegistryStandardImpl implements ResourceRegistry { else { resultSets.remove( resultSet ); if ( resultSets.isEmpty() ) { - xref.remove( statement ); + try { + if ( statement.isClosed() ) { + xref.remove( statement ); + } + } + catch (SQLException e) { + log.debugf( "Unable to release JDBC statement [%s]", e.getMessage() ); + } } } } diff --git a/hibernate-core/src/main/java/org/hibernate/result/internal/OutputsImpl.java b/hibernate-core/src/main/java/org/hibernate/result/internal/OutputsImpl.java index edcd6eaffa..e32dcb086e 100644 --- a/hibernate-core/src/main/java/org/hibernate/result/internal/OutputsImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/result/internal/OutputsImpl.java @@ -67,6 +67,10 @@ public class OutputsImpl implements Outputs { } + protected ResultContext getResultContext(){ + return context; + } + protected void executeStatement() { try { final boolean isResultSet = jdbcStatement.execute(); @@ -134,12 +138,7 @@ public class OutputsImpl implements Outputs { @Override public void release() { - try { - jdbcStatement.close(); - } - catch (SQLException e) { - log.debug( "Unable to close PreparedStatement", e ); - } + context.getSession().getJdbcCoordinator().getLogicalConnection().getResourceRegistry().release( jdbcStatement ); } private List extractCurrentResults() { @@ -281,9 +280,6 @@ public class OutputsImpl implements Outputs { } return results; } -// catch (SQLException e) { -// throw context.getSession().getExceptionConverter().convert( e, "Error processing return rows" ); -// } finally { rowReader.finishUp( jdbcValuesSourceProcessingState ); jdbcValuesSourceProcessingState.finishUp(); diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/sql/refcursor/CursorFromCallableTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/sql/refcursor/CursorFromCallableTest.java index 787a9d1a34..c81f37f9a5 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/sql/refcursor/CursorFromCallableTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/sql/refcursor/CursorFromCallableTest.java @@ -10,6 +10,7 @@ import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.SQLException; import java.util.Arrays; +import java.util.List; import org.hibernate.Session; 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.spi.SessionImplementor; import org.hibernate.jdbc.Work; +import org.hibernate.procedure.ProcedureCall; import org.hibernate.testing.RequiresDialect; import org.hibernate.testing.TestForIssue; @@ -84,15 +86,19 @@ public class CursorFromCallableTest extends BaseCoreFunctionalTestCase { // Verify statement closing with JdbcCoordinator#hasRegisteredResources() instead. // 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 } + ProcedureCall namedStoredProcedureQuery = session.createNamedStoredProcedureQuery( "NumValue.getSomeValues" ); + List resultList = namedStoredProcedureQuery.getResultList(); Assert.assertEquals( 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(); Assert.assertFalse( "Prepared statement and result set should be released after query execution.", jdbcCoordinator.getLogicalConnection().getResourceRegistry().hasRegisteredResources() ); + session.getTransaction().commit(); session.close(); } diff --git a/hibernate-testing/src/main/java/org/hibernate/testing/orm/jdbc/PreparedStatementSpyConnectionProvider.java b/hibernate-testing/src/main/java/org/hibernate/testing/orm/jdbc/PreparedStatementSpyConnectionProvider.java index aad662d142..cce32918ec 100644 --- a/hibernate-testing/src/main/java/org/hibernate/testing/orm/jdbc/PreparedStatementSpyConnectionProvider.java +++ b/hibernate-testing/src/main/java/org/hibernate/testing/orm/jdbc/PreparedStatementSpyConnectionProvider.java @@ -119,6 +119,14 @@ public class PreparedStatementSpyConnectionProvider extends ConnectionProviderDe return statementSpy; } ).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 -> { Statement statement = (Statement) invocation.callRealMethod(); Statement statementSpy = spy( statement, settingsForStatements );