HHH-7984 - Oracle callable statement closing

Conflicts:
	hibernate-core/src/main/java/org/hibernate/loader/Loader.java
	hibernate-core/src/main/java/org/hibernate/loader/entity/DynamicBatchingEntityLoaderBuilder.java
This commit is contained in:
Lukasz Antoniak 2013-03-25 13:48:55 -04:00 committed by Brett Meyer
parent 2fb5a9292d
commit 62cea68007
10 changed files with 92 additions and 39 deletions

View File

@ -370,15 +370,16 @@ public class JdbcCoordinatorImpl implements JdbcCoordinator {
} }
@Override @Override
public void register(ResultSet resultSet) { public void register(ResultSet resultSet, Statement statement) {
LOG.tracev( "Registering result set [{0}]", resultSet ); LOG.tracev( "Registering result set [{0}]", resultSet );
Statement statement; if ( statement == null ) {
try { try {
statement = resultSet.getStatement(); statement = resultSet.getStatement(); // best guess
} }
catch ( SQLException e ) { catch ( SQLException e ) {
throw exceptionHelper.convert( e, "unable to access statement from resultset" ); throw exceptionHelper.convert( e, "unable to access statement from resultset" );
} }
}
if ( statement != null ) { if ( statement != null ) {
if ( LOG.isEnabled( Level.WARN ) && !xref.containsKey( statement ) ) { if ( LOG.isEnabled( Level.WARN ) && !xref.containsKey( statement ) ) {
LOG.unregisteredStatement(); LOG.unregisteredStatement();

View File

@ -54,7 +54,7 @@ public class ResultSetReturnImpl implements ResultSetReturn {
} }
try { try {
ResultSet rs = statement.executeQuery(); ResultSet rs = statement.executeQuery();
postExtract( rs ); postExtract( rs, statement );
return rs; return rs;
} }
catch ( SQLException e ) { catch ( SQLException e ) {
@ -68,7 +68,7 @@ public class ResultSetReturnImpl implements ResultSetReturn {
// sql logged by StatementPreparerImpl // sql logged by StatementPreparerImpl
ResultSet rs = jdbcCoordinator.getLogicalConnection().getJdbcServices() ResultSet rs = jdbcCoordinator.getLogicalConnection().getJdbcServices()
.getDialect().getResultSet( statement ); .getDialect().getResultSet( statement );
postExtract( rs ); postExtract( rs, statement );
return rs; return rs;
} }
catch ( SQLException e ) { catch ( SQLException e ) {
@ -82,7 +82,7 @@ public class ResultSetReturnImpl implements ResultSetReturn {
.getSqlStatementLogger().logStatement( sql ); .getSqlStatementLogger().logStatement( sql );
try { try {
ResultSet rs = statement.executeQuery( sql ); ResultSet rs = statement.executeQuery( sql );
postExtract( rs ); postExtract( rs, statement );
return rs; return rs;
} }
catch ( SQLException e ) { catch ( SQLException e ) {
@ -100,7 +100,7 @@ public class ResultSetReturnImpl implements ResultSetReturn {
} }
} }
ResultSet rs = statement.getResultSet(); ResultSet rs = statement.getResultSet();
postExtract( rs ); postExtract( rs, statement );
return rs; return rs;
} }
catch ( SQLException e ) { catch ( SQLException e ) {
@ -119,7 +119,7 @@ public class ResultSetReturnImpl implements ResultSetReturn {
} }
} }
ResultSet rs = statement.getResultSet(); ResultSet rs = statement.getResultSet();
postExtract( rs ); postExtract( rs, statement );
return rs; return rs;
} }
catch ( SQLException e ) { catch ( SQLException e ) {
@ -157,8 +157,8 @@ public class ResultSetReturnImpl implements ResultSetReturn {
.getSqlExceptionHelper(); .getSqlExceptionHelper();
} }
private void postExtract(ResultSet rs) { private void postExtract(ResultSet rs, Statement st) {
if ( rs != null ) jdbcCoordinator.register( rs ); if ( rs != null ) jdbcCoordinator.register( rs, st );
} }
} }

View File

@ -170,10 +170,15 @@ public interface JdbcCoordinator extends Serializable {
/** /**
* Register a JDBC result set. * Register a JDBC result set.
* <p/>
* Implementation note: Second parameter has been introduced to prevent
* multiple registrations of the same statement in case {@link ResultSet#getStatement()}
* does not return original {@link Statement} object.
* *
* @param resultSet The result set to register. * @param resultSet The result set to register.
* @param statement Statement from which {@link ResultSet} has been generated.
*/ */
public void register(ResultSet resultSet); public void register(ResultSet resultSet, Statement statement);
/** /**
* Release a previously registered result set. * Release a previously registered result set.

View File

@ -954,8 +954,9 @@ public class QueryTranslatorImpl extends BasicLoader implements FilterTranslator
try { try {
final List<AfterLoadAction> afterLoadActions = new ArrayList<AfterLoadAction>(); final List<AfterLoadAction> afterLoadActions = new ArrayList<AfterLoadAction>();
final ResultSet rs = executeQueryStatement( queryParameters, false, afterLoadActions, session ); final SqlStatementWrapper wrapper = executeQueryStatement( queryParameters, false, afterLoadActions, session );
final PreparedStatement st = (PreparedStatement) rs.getStatement(); final ResultSet rs = wrapper.getResultSet();
final PreparedStatement st = (PreparedStatement) wrapper.getStatement();
HolderInstantiator hi = HolderInstantiator.createClassicHolderInstantiator(holderConstructor, queryParameters.getResultTransformer()); HolderInstantiator hi = HolderInstantiator.createClassicHolderInstantiator(holderConstructor, queryParameters.getResultTransformer());
Iterator result = new IteratorImpl( rs, st, session, queryParameters.isReadOnly( session ), returnTypes, getColumnNames(), hi ); Iterator result = new IteratorImpl( rs, st, session, queryParameters.isReadOnly( session ), returnTypes, getColumnNames(), hi );

View File

@ -896,9 +896,9 @@ public abstract class Loader {
final List<AfterLoadAction> afterLoadActions = new ArrayList<AfterLoadAction>(); final List<AfterLoadAction> afterLoadActions = new ArrayList<AfterLoadAction>();
final ResultSet rs = executeQueryStatement( queryParameters, false, afterLoadActions, session ); final SqlStatementWrapper wrapper = executeQueryStatement( queryParameters, false, afterLoadActions, session );
final Statement st = rs.getStatement(); final ResultSet rs = wrapper.getResultSet();
final int entitySpan = getEntityPersisters().length; final Statement st = wrapper.getStatement();
// would be great to move all this below here into another method that could also be used // would be great to move all this below here into another method that could also be used
// from the new scrolling stuff. // from the new scrolling stuff.
@ -1803,7 +1803,7 @@ public abstract class Loader {
* Process query string by applying filters, LIMIT clause, locks and comments if necessary. * Process query string by applying filters, LIMIT clause, locks and comments if necessary.
* Finally execute SQL statement and advance to the first row. * Finally execute SQL statement and advance to the first row.
*/ */
protected ResultSet executeQueryStatement( protected SqlStatementWrapper executeQueryStatement(
final QueryParameters queryParameters, final QueryParameters queryParameters,
final boolean scroll, final boolean scroll,
List<AfterLoadAction> afterLoadActions, List<AfterLoadAction> afterLoadActions,
@ -1811,10 +1811,10 @@ public abstract class Loader {
return executeQueryStatement( getSQLString(), queryParameters, scroll, afterLoadActions, session ); return executeQueryStatement( getSQLString(), queryParameters, scroll, afterLoadActions, session );
} }
protected ResultSet executeQueryStatement( protected SqlStatementWrapper executeQueryStatement(
final String sqlStatement, String sqlStatement,
final QueryParameters queryParameters, QueryParameters queryParameters,
final boolean scroll, boolean scroll,
List<AfterLoadAction> afterLoadActions, List<AfterLoadAction> afterLoadActions,
final SessionImplementor session) throws SQLException { final SessionImplementor session) throws SQLException {
@ -1829,7 +1829,7 @@ public abstract class Loader {
sql = preprocessSQL( sql, queryParameters, getFactory().getDialect(), afterLoadActions ); sql = preprocessSQL( sql, queryParameters, getFactory().getDialect(), afterLoadActions );
final PreparedStatement st = prepareQueryStatement( sql, queryParameters, limitHandler, scroll, session ); final PreparedStatement st = prepareQueryStatement( sql, queryParameters, limitHandler, scroll, session );
return getResultSet( st, queryParameters.getRowSelection(), limitHandler, queryParameters.hasAutoDiscoverScalarTypes(), session ); return new SqlStatementWrapper( st, getResultSet( st, queryParameters.getRowSelection(), limitHandler, queryParameters.hasAutoDiscoverScalarTypes(), session ) );
} }
/** /**
@ -2583,8 +2583,9 @@ public abstract class Loader {
if ( stats ) startTime = System.currentTimeMillis(); if ( stats ) startTime = System.currentTimeMillis();
try { try {
final ResultSet rs = executeQueryStatement( queryParameters, true, Collections.<AfterLoadAction>emptyList(), session ); final SqlStatementWrapper wrapper = executeQueryStatement( queryParameters, true, Collections.<AfterLoadAction>emptyList(), session );
final PreparedStatement st = (PreparedStatement) rs.getStatement(); final ResultSet rs = wrapper.getResultSet();
final PreparedStatement st = (PreparedStatement) wrapper.getStatement();
if ( stats ) { if ( stats ) {
getFactory().getStatisticsImplementor().queryExecuted( getFactory().getStatisticsImplementor().queryExecuted(
@ -2657,4 +2658,25 @@ public abstract class Loader {
public String toString() { public String toString() {
return getClass().getName() + '(' + getSQLString() + ')'; return getClass().getName() + '(' + getSQLString() + ')';
} }
/**
* Wrapper class for {@link Statement} and associated {@link ResultSet}.
*/
protected static class SqlStatementWrapper {
private final Statement statement;
private final ResultSet resultSet;
private SqlStatementWrapper(Statement statement, ResultSet resultSet) {
this.resultSet = resultSet;
this.statement = statement;
}
public ResultSet getResultSet() {
return resultSet;
}
public Statement getStatement() {
return statement;
}
}
} }

View File

@ -253,8 +253,9 @@ public class DynamicBatchingCollectionInitializerBuilder extends BatchingCollect
Integer.MAX_VALUE; Integer.MAX_VALUE;
final List<AfterLoadAction> afterLoadActions = Collections.emptyList(); final List<AfterLoadAction> afterLoadActions = Collections.emptyList();
final ResultSet rs = executeQueryStatement( sql, queryParameters, false, afterLoadActions, session ); final SqlStatementWrapper wrapper = executeQueryStatement( sql, queryParameters, false, afterLoadActions, session );
final Statement st = rs.getStatement(); final ResultSet rs = wrapper.getResultSet();
final Statement st = wrapper.getStatement();
try { try {
processResultSet( rs, queryParameters, session, true, null, maxRows, afterLoadActions ); processResultSet( rs, queryParameters, session, true, null, maxRows, afterLoadActions );
} }

View File

@ -27,11 +27,9 @@ import java.io.Serializable;
import java.sql.ResultSet; import java.sql.ResultSet;
import java.sql.SQLException; import java.sql.SQLException;
import java.sql.Statement; import java.sql.Statement;
import java.util.Collections; import java.util.ArrayList;
import java.util.List; import java.util.List;
import org.jboss.logging.Logger;
import org.hibernate.LockMode; import org.hibernate.LockMode;
import org.hibernate.LockOptions; import org.hibernate.LockOptions;
import org.hibernate.dialect.pagination.LimitHelper; import org.hibernate.dialect.pagination.LimitHelper;
@ -45,6 +43,7 @@ import org.hibernate.internal.util.StringHelper;
import org.hibernate.internal.util.collections.ArrayHelper; import org.hibernate.internal.util.collections.ArrayHelper;
import org.hibernate.persister.entity.OuterJoinLoadable; import org.hibernate.persister.entity.OuterJoinLoadable;
import org.hibernate.pretty.MessageHelper; import org.hibernate.pretty.MessageHelper;
import org.jboss.logging.Logger;
/** /**
* A BatchingEntityLoaderBuilder that builds UniqueEntityLoader instances capable of dynamically building * A BatchingEntityLoaderBuilder that builds UniqueEntityLoader instances capable of dynamically building
@ -254,9 +253,10 @@ public class DynamicBatchingEntityLoaderBuilder extends BatchingEntityLoaderBuil
selection.getMaxRows() : selection.getMaxRows() :
Integer.MAX_VALUE; Integer.MAX_VALUE;
final List<AfterLoadAction> afterLoadActions = Collections.emptyList(); final List<AfterLoadAction> afterLoadActions = new ArrayList<AfterLoadAction>();
final ResultSet rs = executeQueryStatement( sql, queryParameters, false, afterLoadActions, session ); final SqlStatementWrapper wrapper = executeQueryStatement( sql, queryParameters, false, afterLoadActions, session );
final Statement st = rs.getStatement(); final ResultSet rs = wrapper.getResultSet();
final Statement st = wrapper.getStatement();
try { try {
return processResultSet( rs, queryParameters, session, false, null, maxRows, afterLoadActions ); return processResultSet( rs, queryParameters, session, false, null, maxRows, afterLoadActions );
} }

View File

@ -510,8 +510,9 @@ public class QueryLoader extends BasicLoader {
if ( queryParameters.isCallable() ) { if ( queryParameters.isCallable() ) {
throw new QueryException("iterate() not supported for callable statements"); throw new QueryException("iterate() not supported for callable statements");
} }
final ResultSet rs = executeQueryStatement( queryParameters, false, Collections.<AfterLoadAction>emptyList(), session ); final SqlStatementWrapper wrapper = executeQueryStatement( queryParameters, false, Collections.<AfterLoadAction>emptyList(), session );
final PreparedStatement st = (PreparedStatement) rs.getStatement(); final ResultSet rs = wrapper.getResultSet();
final PreparedStatement st = (PreparedStatement) wrapper.getStatement();
final Iterator result = new IteratorImpl( final Iterator result = new IteratorImpl(
rs, rs,
st, st,

View File

@ -129,7 +129,7 @@ public class BasicOperationsTest extends BaseCoreFunctionalTestCase {
String columnNamePattern = generateFinalNamePattern( meta, columnName ); String columnNamePattern = generateFinalNamePattern( meta, columnName );
ResultSet columnInfo = meta.getColumns( null, null, tableNamePattern, columnNamePattern ); ResultSet columnInfo = meta.getColumns( null, null, tableNamePattern, columnNamePattern );
s.getTransactionCoordinator().getJdbcCoordinator().register(columnInfo); s.getTransactionCoordinator().getJdbcCoordinator().register(columnInfo, columnInfo.getStatement());
assertTrue( columnInfo.next() ); assertTrue( columnInfo.next() );
int dataType = columnInfo.getInt( "DATA_TYPE" ); int dataType = columnInfo.getInt( "DATA_TYPE" );
s.getTransactionCoordinator().getJdbcCoordinator().release( columnInfo ); s.getTransactionCoordinator().getJdbcCoordinator().release( columnInfo );

View File

@ -89,6 +89,28 @@ public class CursorFromCallableTest extends BaseCoreFunctionalTestCase {
session.close(); session.close();
} }
@Test
@TestForIssue( jiraKey = "HHH-7984" )
public void testStatementClosing() {
Session session = openSession();
session.getTransaction().begin();
// Reading maximum number of opened cursors requires SYS privileges.
// 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 }
Assert.assertEquals(
Arrays.asList( new NumValue( 1, "Line 1" ), new NumValue( 2, "Line 2" ) ),
session.getNamedQuery( "NumValue.getSomeValues" ).list()
);
JdbcCoordinator jdbcCoordinator = ( (SessionImplementor) session ).getTransactionCoordinator().getJdbcCoordinator();
Assert.assertFalse(
"Prepared statement and result set should be released after query execution.",
jdbcCoordinator.hasRegisteredResources()
);
session.getTransaction().commit();
session.close();
}
private void executeStatement(final String sql) { private void executeStatement(final String sql) {
final Session session = openSession(); final Session session = openSession();
session.getTransaction().begin(); session.getTransaction().begin();