Skip to content

Commit

Permalink
HHH-11617 - Statement leak in case of 'SQLGrammarException: could not…
Browse files Browse the repository at this point in the history
… extract ResultSet'
  • Loading branch information
dreab8 authored and vladmihalcea committed Apr 11, 2017
1 parent f857001 commit eef8a48
Show file tree
Hide file tree
Showing 3 changed files with 143 additions and 9 deletions.
Expand Up @@ -443,10 +443,10 @@ protected final ResultSet getResultSet(
}
return rs;
}
catch ( SQLException sqle ) {
catch (SQLException | HibernateException ex) {
session.getJdbcCoordinator().getResourceRegistry().release( st );
session.getJdbcCoordinator().afterStatementExecution();
throw sqle;
throw ex;
}
}

Expand Down
@@ -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
};
}
}
Expand Up @@ -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;
Expand Down Expand Up @@ -445,32 +447,43 @@ public static void doInHibernateSessionBuilder(
}
}
}

/**
* 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();
Expand Down

0 comments on commit eef8a48

Please sign in to comment.