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;
}
catch ( SQLException sqle ) {
catch (SQLException | HibernateException ex) {
session.getJdbcCoordinator().getResourceRegistry().release( st );
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;
import java.sql.PreparedStatement;
import java.sql.Statement;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
@ -445,32 +447,43 @@ public class TransactionUtil {
}
}
}
/**
* Set Session or Statement timeout
* @param session Hibernate 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 -> {
if ( Dialect.getDialect() instanceof PostgreSQL81Dialect ) {
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 ) {
try (Statement st = connection.createStatement()) {
st.execute( "SET GLOBAL innodb_lock_wait_timeout = 1" );
try (PreparedStatement st = connection.prepareStatement("SET GLOBAL innodb_lock_wait_timeout = ?")) {
st.setLong( 1, TimeUnit.MILLISECONDS.toSeconds( millis ) );
st.execute();
}
}
else if( Dialect.getDialect() instanceof H2Dialect ) {
try (Statement st = connection.createStatement()) {
st.execute( "SET LOCK_TIMEOUT 100" );
try (PreparedStatement st = connection.prepareStatement("SET LOCK_TIMEOUT ?")) {
st.setLong( 1, millis / 10 );
st.execute();
}
}
else {
try {
connection.setNetworkTimeout( Executors.newSingleThreadExecutor(), 1000 );
connection.setNetworkTimeout( Executors.newSingleThreadExecutor(), (int) millis );
}
catch (Throwable ignore) {
ignore.fillInStackTrace();