HHH-11617 - Statement leak in case of 'SQLGrammarException: could not extract ResultSet'

This commit is contained in:
Andrea Boriero 2017-04-06 18:37:00 +01:00 committed by Vlad Mihalcea
parent f8570017df
commit eef8a48ce4
3 changed files with 143 additions and 9 deletions

View File

@ -443,10 +443,10 @@ public abstract class AbstractLoadPlanBasedLoader {
} }
return rs; return rs;
} }
catch ( SQLException sqle ) { catch (SQLException | HibernateException ex) {
session.getJdbcCoordinator().getResourceRegistry().release( st ); session.getJdbcCoordinator().getResourceRegistry().release( st );
session.getJdbcCoordinator().afterStatementExecution(); session.getJdbcCoordinator().afterStatementExecution();
throw sqle; throw ex;
} }
} }

View File

@ -0,0 +1,121 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later.
* See the lgpl.txt file in the root directory or <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
package org.hibernate.jpa.test.lock;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import javax.persistence.LockModeType;
import org.hibernate.Session;
import org.hibernate.jpa.AvailableSettings;
import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase;
import org.hibernate.testing.DialectChecks;
import org.hibernate.testing.RequiresDialectFeature;
import org.hibernate.testing.TestForIssue;
import org.hibernate.testing.jdbc.PreparedStatementSpyConnectionProvider;
import org.hibernate.testing.transaction.TransactionUtil;
import org.hibernate.testing.util.ExceptionUtil;
import org.junit.Before;
import org.junit.Test;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.fail;
/**
* @author Andrea Boriero
*/
public class StatementIsClosedAfterALockExceptionTest extends BaseEntityManagerFunctionalTestCase {
private static final PreparedStatementSpyConnectionProvider CONNECTION_PROVIDER = new PreparedStatementSpyConnectionProvider();
private Integer lockId;
@Override
protected Map getConfig() {
Map config = super.getConfig();
config.put(
org.hibernate.cfg.AvailableSettings.CONNECTION_PROVIDER,
CONNECTION_PROVIDER
);
return config;
}
@Before
public void setUp() {
lockId = TransactionUtil.doInJPA( this::entityManagerFactory, entityManager -> {
Lock lock = new Lock();
lock.setName( "name" );
entityManager.persist( lock );
return lock.getId();
} );
}
@Override
public void releaseResources() {
super.releaseResources();
CONNECTION_PROVIDER.stop();
}
@Test(timeout = 2500) //2.5 seconds
@TestForIssue(jiraKey = "HHH-11617")
public void testStatementIsClosed() {
TransactionUtil.doInJPA( this::entityManagerFactory, em1 -> {
Map<String, Object> properties = new HashMap<>();
properties.put( org.hibernate.cfg.AvailableSettings.JPA_LOCK_TIMEOUT, 0L );
Lock lock2 = em1.find( Lock.class, lockId, LockModeType.PESSIMISTIC_WRITE, properties );
assertEquals(
"lock mode should be PESSIMISTIC_WRITE ",
LockModeType.PESSIMISTIC_WRITE,
em1.getLockMode( lock2 )
);
TransactionUtil.doInJPA( this::entityManagerFactory, em2 -> {
TransactionUtil.setJdbcTimeout( em2.unwrap( Session.class ) );
try {
em2.find( Lock.class, lockId, LockModeType.PESSIMISTIC_WRITE, properties );
fail( "Exception should be thrown" );
}
catch (Exception lte) {
if( !ExceptionUtil.isSqlLockTimeout( lte )) {
fail("Should have thrown a Lock timeout exception");
}
}
finally {
try {
for ( PreparedStatement statement : CONNECTION_PROVIDER.getPreparedStatements() ) {
assertThat(
"A SQL Statement was not closed : " + statement.toString(),
statement.isClosed(),
is( true )
);
}
}
catch (SQLException e) {
fail( e.getMessage() );
}
}
} );
} );
}
@Override
public Class[] getAnnotatedClasses() {
return new Class[] {
Lock.class,
UnversionedLock.class
};
}
}

View File

@ -6,10 +6,12 @@
*/ */
package org.hibernate.testing.transaction; package org.hibernate.testing.transaction;
import java.sql.PreparedStatement;
import java.sql.Statement; import java.sql.Statement;
import java.util.Map; import java.util.Map;
import java.util.Properties; import java.util.Properties;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer; import java.util.function.Consumer;
import java.util.function.Function; import java.util.function.Function;
import java.util.function.Supplier; import java.util.function.Supplier;
@ -445,32 +447,43 @@ public class TransactionUtil {
} }
} }
} }
/** /**
* Set Session or Statement timeout * Set Session or Statement timeout
* @param session Hibernate Session * @param session Hibernate Session
*/ */
public static void setJdbcTimeout(Session session) { public static void setJdbcTimeout(Session session) {
setJdbcTimeout( session, TimeUnit.SECONDS.toMillis( 1 ) );
}
/**
* Set Session or Statement timeout
* @param session Hibernate Session
*/
public static void setJdbcTimeout(Session session, long millis) {
session.doWork( connection -> { session.doWork( connection -> {
if ( Dialect.getDialect() instanceof PostgreSQL81Dialect ) { if ( Dialect.getDialect() instanceof PostgreSQL81Dialect ) {
try (Statement st = connection.createStatement()) { try (Statement st = connection.createStatement()) {
st.execute( "SET statement_timeout TO 1000" ); //Prepared Statements fail for SET commands
st.execute(String.format( "SET statement_timeout TO %d", millis / 10));
} }
} }
else if( Dialect.getDialect() instanceof MySQLDialect ) { else if( Dialect.getDialect() instanceof MySQLDialect ) {
try (Statement st = connection.createStatement()) { try (PreparedStatement st = connection.prepareStatement("SET GLOBAL innodb_lock_wait_timeout = ?")) {
st.execute( "SET GLOBAL innodb_lock_wait_timeout = 1" ); st.setLong( 1, TimeUnit.MILLISECONDS.toSeconds( millis ) );
st.execute();
} }
} }
else if( Dialect.getDialect() instanceof H2Dialect ) { else if( Dialect.getDialect() instanceof H2Dialect ) {
try (Statement st = connection.createStatement()) { try (PreparedStatement st = connection.prepareStatement("SET LOCK_TIMEOUT ?")) {
st.execute( "SET LOCK_TIMEOUT 100" ); st.setLong( 1, millis / 10 );
st.execute();
} }
} }
else { else {
try { try {
connection.setNetworkTimeout( Executors.newSingleThreadExecutor(), 1000 ); connection.setNetworkTimeout( Executors.newSingleThreadExecutor(), (int) millis );
} }
catch (Throwable ignore) { catch (Throwable ignore) {
ignore.fillInStackTrace(); ignore.fillInStackTrace();