HHH-14326 Test JDBC resources are released before closing the connection

Signed-off-by: Yoann Rodière <yoann@hibernate.org>
This commit is contained in:
Yoann Rodière 2021-01-14 13:36:51 +01:00 committed by Sanne Grinovero
parent e5c830da19
commit d726dcb394
2 changed files with 69 additions and 82 deletions

View File

@ -7,33 +7,45 @@
package org.hibernate.test.connections; package org.hibernate.test.connections;
import org.hibernate.cfg.AvailableSettings; import java.sql.Connection;
import org.hibernate.dialect.H2Dialect; import java.sql.SQLException;
import org.hibernate.engine.jdbc.connections.internal.UserSuppliedConnectionProviderImpl; import java.sql.Statement;
import org.hibernate.engine.jdbc.connections.spi.ConnectionProvider; import java.util.Map;
import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase;
import org.hibernate.resource.jdbc.spi.PhysicalConnectionHandlingMode;
import org.hibernate.testing.RequiresDialect;
import org.hibernate.testing.env.ConnectionProviderBuilder;
import org.hibernate.testing.jta.TestingJtaBootstrap;
import org.hibernate.testing.jta.TestingJtaPlatformImpl;
import org.hibernate.testing.transaction.TransactionUtil;
import org.junit.Test;
import javax.persistence.Entity; import javax.persistence.Entity;
import javax.persistence.Id; import javax.persistence.Id;
import javax.persistence.Table; import javax.persistence.Table;
import javax.transaction.RollbackException; import javax.transaction.RollbackException;
import javax.transaction.SystemException; import javax.transaction.SystemException;
import javax.transaction.Transaction; import javax.transaction.xa.XAException;
import javax.transaction.xa.XAResource; import javax.transaction.xa.XAResource;
import javax.transaction.xa.Xid;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Map;
import static org.junit.Assert.assertTrue; import org.hibernate.cfg.AvailableSettings;
import static org.junit.Assert.fail; import org.hibernate.dialect.H2Dialect;
import org.hibernate.engine.jdbc.connections.internal.UserSuppliedConnectionProviderImpl;
import org.hibernate.engine.jdbc.connections.spi.ConnectionProvider;
import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase;
import org.hibernate.resource.jdbc.spi.LogicalConnectionImplementor;
import org.hibernate.resource.jdbc.spi.PhysicalConnectionHandlingMode;
import org.hibernate.testing.RequiresDialect;
import org.hibernate.testing.TestForIssue;
import org.hibernate.testing.env.ConnectionProviderBuilder;
import org.hibernate.testing.jta.TestingJtaBootstrap;
import org.hibernate.testing.jta.TestingJtaPlatformImpl;
import org.hibernate.testing.transaction.TransactionUtil2;
import org.junit.Rule;
import org.junit.Test;
import org.mockito.InOrder;
import org.mockito.Mockito;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
import org.mockito.quality.Strictness;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
/** /**
* @author Luis Barreiro * @author Luis Barreiro
@ -41,6 +53,9 @@ import static org.junit.Assert.fail;
@RequiresDialect( H2Dialect.class ) @RequiresDialect( H2Dialect.class )
public class BeforeCompletionReleaseTest extends BaseEntityManagerFunctionalTestCase { public class BeforeCompletionReleaseTest extends BaseEntityManagerFunctionalTestCase {
@Rule
public MockitoRule mockito = MockitoJUnit.rule().strictness( Strictness.STRICT_STUBS );
@Override @Override
protected Map getConfig() { protected Map getConfig() {
Map config = super.getConfig(); Map config = super.getConfig();
@ -56,12 +71,40 @@ public class BeforeCompletionReleaseTest extends BaseEntityManagerFunctionalTest
} }
@Test @Test
public void testConnectionAcquisitionCount() { @TestForIssue(jiraKey = {"HHH-13976", "HHH-14326"})
TransactionUtil.doInJPA( this::entityManagerFactory, entityManager -> { public void testResourcesReleasedThenConnectionClosedThenCommit() throws SQLException, XAException {
XAResource transactionSpy = mock( XAResource.class );
Connection[] connectionSpies = new Connection[1];
Statement statementMock = Mockito.mock( Statement.class );
TransactionUtil2.inTransaction( entityManagerFactory(), session -> {
spyOnTransaction( transactionSpy );
Thing thing = new Thing(); Thing thing = new Thing();
thing.setId( 1 ); thing.setId( 1 );
entityManager.persist( thing ); session.persist( thing );
LogicalConnectionImplementor logicalConnection = session.getJdbcCoordinator().getLogicalConnection();
logicalConnection.getResourceRegistry().register( statementMock, true );
connectionSpies[0] = logicalConnection.getPhysicalConnection();
} ); } );
Connection connectionSpy = connectionSpies[0];
// Must close the resources, then the connection, then commit
InOrder inOrder = inOrder( statementMock, connectionSpy, transactionSpy );
inOrder.verify( statementMock ).close();
inOrder.verify( connectionSpy ).close();
inOrder.verify( transactionSpy ).commit( any(), anyBoolean() );
}
private void spyOnTransaction(XAResource xaResource) {
try {
TestingJtaPlatformImpl.transactionManager().getTransaction().enlistResource( xaResource );
}
catch (RollbackException | SystemException e) {
throw new IllegalStateException( e );
}
} }
// --- // // --- //
@ -94,63 +137,7 @@ public class BeforeCompletionReleaseTest extends BaseEntityManagerFunctionalTest
@Override @Override
public Connection getConnection() throws SQLException { public Connection getConnection() throws SQLException {
Connection connection = dataSource.getConnection(); return spy( dataSource.getConnection() );
try {
Transaction tx = TestingJtaPlatformImpl.transactionManager().getTransaction();
if ( tx != null) {
tx.enlistResource( new XAResource() {
@Override public void commit(Xid xid, boolean onePhase) {
try {
assertTrue( "Connection should be closed prior to commit", connection.isClosed() );
} catch ( SQLException e ) {
fail( "Unexpected SQLException: " + e.getMessage() );
}
}
@Override public void end(Xid xid, int flags) {
}
@Override public void forget(Xid xid) {
}
@Override public int getTransactionTimeout() {
return 0;
}
@Override public boolean isSameRM(XAResource xares) {
return false;
}
@Override public int prepare(Xid xid) {
return 0;
}
@Override public Xid[] recover(int flag) {
return new Xid[0];
}
@Override public void rollback(Xid xid) {
try {
assertTrue( "Connection should be closed prior to rollback", connection.isClosed() );
} catch ( SQLException e ) {
fail( "Unexpected SQLException: " + e.getMessage() );
}
}
@Override public boolean setTransactionTimeout(int seconds) {
return false;
}
@Override public void start(Xid xid, int flags) {
}
});
}
} catch ( SystemException | RollbackException e ) {
fail( e.getMessage() );
}
return connection;
} }
@Override @Override

View File

@ -23,5 +23,5 @@ public @interface TestForIssue {
* The key of a JIRA issue tested. * The key of a JIRA issue tested.
* @return The jira issue key * @return The jira issue key
*/ */
String jiraKey(); String[] jiraKey();
} }