HHH-12285 - DB connection exception on rollback causes connection leak
This commit is contained in:
parent
cd86045598
commit
f1a31b61d2
|
@ -118,6 +118,7 @@ public abstract class AbstractLogicalConnectionImplementor implements LogicalCon
|
|||
log.trace( "Transaction rolled-back via JDBC Connection.rollback()" );
|
||||
}
|
||||
catch( SQLException e ) {
|
||||
status = TransactionStatus.FAILED_ROLLBACK;
|
||||
throw new TransactionException( "Unable to rollback against JDBC Connection", e );
|
||||
}
|
||||
|
||||
|
|
|
@ -36,6 +36,10 @@ public enum TransactionStatus {
|
|||
* The transaction attempted to commit, but failed.
|
||||
*/
|
||||
FAILED_COMMIT,
|
||||
/**
|
||||
* The transaction attempted to rollback, but failed.
|
||||
*/
|
||||
FAILED_ROLLBACK,
|
||||
/**
|
||||
* Status code indicating a transaction that has begun the second
|
||||
* phase of the two-phase commit protocol, but not yet completed
|
||||
|
|
|
@ -15,96 +15,145 @@ import java.util.Map;
|
|||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import javax.persistence.EntityManager;
|
||||
import javax.persistence.EntityManagerFactory;
|
||||
import javax.persistence.PersistenceException;
|
||||
import javax.persistence.RollbackException;
|
||||
|
||||
import org.hibernate.cfg.Environment;
|
||||
import org.hibernate.dialect.Dialect;
|
||||
import org.hibernate.engine.jdbc.connections.internal.DriverManagerConnectionProviderImpl;
|
||||
import org.hibernate.jpa.boot.spi.Bootstrap;
|
||||
import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase;
|
||||
import org.hibernate.jpa.test.PersistenceUnitDescriptorAdapter;
|
||||
import org.hibernate.jpa.test.SettingsGenerator;
|
||||
|
||||
import org.hibernate.testing.TestForIssue;
|
||||
import org.hibernate.testing.junit4.BaseUnitTestCase;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
/**
|
||||
* @author Vlad Mihalcea
|
||||
*/
|
||||
public class TransactionCommitFailureTest {
|
||||
public class TransactionCommitFailureTest extends BaseUnitTestCase {
|
||||
|
||||
public static final String COMMIT_FAILURE = "Commit failed!";
|
||||
public static final String COMMIT_FAILURE = "Error while committing the transaction";
|
||||
|
||||
private static final AtomicBoolean transactionFailureTrigger = new AtomicBoolean( false );
|
||||
private static AtomicBoolean transactionFailureTrigger;
|
||||
private static AtomicBoolean connectionIsOpen;
|
||||
|
||||
@Test
|
||||
public void testConfiguredInterceptor() {
|
||||
Map settings = basicSettings();
|
||||
EntityManagerFactory emf = Bootstrap.getEntityManagerFactoryBuilder( new PersistenceUnitDescriptorAdapter(), settings ).build();
|
||||
EntityManager em = emf.createEntityManager();
|
||||
private EntityManagerFactory emf;
|
||||
|
||||
try {
|
||||
em.getTransaction().begin();
|
||||
transactionFailureTrigger.set( true );
|
||||
em.getTransaction().commit();
|
||||
}
|
||||
@Before
|
||||
public void setUp() {
|
||||
final Map settings = basicSettings();
|
||||
emf = Bootstrap.getEntityManagerFactoryBuilder( new PersistenceUnitDescriptorAdapter(), settings ).build();
|
||||
transactionFailureTrigger = new AtomicBoolean();
|
||||
connectionIsOpen = new AtomicBoolean();
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() {
|
||||
emf.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void assertConnectionIsReleasedIfCommitFails() {
|
||||
EntityManager em = emf.createEntityManager();
|
||||
|
||||
try {
|
||||
em.getTransaction().begin();
|
||||
transactionFailureTrigger.set( true );
|
||||
em.getTransaction().commit();
|
||||
}
|
||||
catch (RollbackException e) {
|
||||
assertEquals( COMMIT_FAILURE, e.getCause().getMessage() );
|
||||
assertEquals( COMMIT_FAILURE, e.getLocalizedMessage());
|
||||
}
|
||||
finally {
|
||||
if ( em.getTransaction() != null && em.getTransaction().isActive() ) {
|
||||
em.getTransaction().rollback();
|
||||
}
|
||||
em.close();
|
||||
emf.close();
|
||||
}
|
||||
}
|
||||
if ( em.getTransaction() != null && em.getTransaction().isActive() ) {
|
||||
em.getTransaction().rollback();
|
||||
}
|
||||
em.close();
|
||||
}
|
||||
|
||||
protected Map basicSettings() {
|
||||
assertEquals( "The connection was not released", false, connectionIsOpen.get() );
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestForIssue(jiraKey = "HHH-12285")
|
||||
public void assertConnectionIsReleasedIfRollbackFails() {
|
||||
EntityManager em = emf.createEntityManager();
|
||||
try {
|
||||
em.getTransaction().begin();
|
||||
assertEquals( true, connectionIsOpen.get() );
|
||||
transactionFailureTrigger.set( true );
|
||||
em.getTransaction().rollback();
|
||||
fail( "Rollback failure, Exception expected" );
|
||||
}
|
||||
catch (Exception pe) {
|
||||
//expected
|
||||
}
|
||||
finally {
|
||||
em.close();
|
||||
}
|
||||
|
||||
assertEquals( "The connection was not released", false, connectionIsOpen.get() );
|
||||
}
|
||||
|
||||
private Map basicSettings() {
|
||||
return SettingsGenerator.generateSettings(
|
||||
Environment.HBM2DDL_AUTO, "create-drop",
|
||||
Environment.USE_NEW_ID_GENERATOR_MAPPINGS, "true",
|
||||
Environment.DIALECT, Dialect.getDialect().getClass().getName(),
|
||||
Environment.CONNECTION_PROVIDER, ProxyConnectionProvider.class.getName()
|
||||
Environment.CONNECTION_PROVIDER, ProxyConnectionProvider.class.getName()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public static class ProxyConnectionProvider extends DriverManagerConnectionProviderImpl {
|
||||
|
||||
@Override
|
||||
public Connection getConnection() throws SQLException {
|
||||
Connection delegate = super.getConnection();
|
||||
connectionIsOpen.set( true );
|
||||
return (Connection) Proxy.newProxyInstance(
|
||||
this.getClass().getClassLoader(),
|
||||
new Class[]{Connection.class},
|
||||
new ConnectionInvocationHandler(delegate));
|
||||
new Class[] { Connection.class },
|
||||
new ConnectionInvocationHandler( delegate )
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void closeConnection(Connection conn) throws SQLException {
|
||||
super.closeConnection( conn );
|
||||
connectionIsOpen.set( false );
|
||||
}
|
||||
}
|
||||
|
||||
private static class ConnectionInvocationHandler implements InvocationHandler {
|
||||
private static class ConnectionInvocationHandler implements InvocationHandler {
|
||||
|
||||
private final Connection delegate;
|
||||
private final Connection delegate;
|
||||
|
||||
public ConnectionInvocationHandler(Connection delegate) {
|
||||
this.delegate = delegate;
|
||||
}
|
||||
public ConnectionInvocationHandler(Connection delegate) {
|
||||
this.delegate = delegate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
|
||||
if("commit".equals( method.getName() )) {
|
||||
if ( transactionFailureTrigger.get() ) {
|
||||
throw new PersistenceException( COMMIT_FAILURE );
|
||||
}
|
||||
}
|
||||
else if("rollback".equals( method.getName() )) {
|
||||
if ( transactionFailureTrigger.get() ) {
|
||||
transactionFailureTrigger.set( false );
|
||||
throw new PersistenceException( "Rollback failed!" );
|
||||
}
|
||||
}
|
||||
return method.invoke(delegate, args);
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
|
||||
if ( "commit".equals( method.getName() ) ) {
|
||||
if ( transactionFailureTrigger.get() ) {
|
||||
throw new SQLException( COMMIT_FAILURE );
|
||||
}
|
||||
}
|
||||
else if ( "rollback".equals( method.getName() ) ) {
|
||||
if ( transactionFailureTrigger.get() ) {
|
||||
transactionFailureTrigger.set( false );
|
||||
throw new SQLException( "Rollback failed!" );
|
||||
}
|
||||
}
|
||||
return method.invoke( delegate, args );
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue