diff --git a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/JdbcLogging.java b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/JdbcLogging.java index 5064e9568efa..6b10d9976b52 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/JdbcLogging.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/JdbcLogging.java @@ -142,11 +142,11 @@ public interface JdbcLogging extends BasicLogger { @LogMessage(level = TRACE) @Message(value = "Unable to reset connection back to auto-commit", id = 100019) - void unableToResetAutoCommit(); + void unableToResetAutoCommit(@Cause Exception ignored); @LogMessage(level = INFO) @Message(value = "Unable to release isolated connection", id = 100020) - void unableToReleaseIsolatedConnection(@Cause Throwable ignored); + void unableToReleaseIsolatedConnection(@Cause Exception ignored); @LogMessage(level = INFO) @Message(value = "Unable to roll back isolated connection on exception ", id = 100021) diff --git a/hibernate-core/src/main/java/org/hibernate/resource/transaction/backend/jdbc/internal/JdbcIsolationDelegate.java b/hibernate-core/src/main/java/org/hibernate/resource/transaction/backend/jdbc/internal/JdbcIsolationDelegate.java index a9288850c531..036cba7d8918 100644 --- a/hibernate-core/src/main/java/org/hibernate/resource/transaction/backend/jdbc/internal/JdbcIsolationDelegate.java +++ b/hibernate-core/src/main/java/org/hibernate/resource/transaction/backend/jdbc/internal/JdbcIsolationDelegate.java @@ -22,7 +22,7 @@ /** * @author Andrea Boriero */ -public class JdbcIsolationDelegate implements IsolationDelegate { +public final class JdbcIsolationDelegate implements IsolationDelegate { private final JdbcConnectionAccess connectionAccess; private final SqlExceptionHelper sqlExceptionHelper; @@ -40,79 +40,108 @@ public JdbcIsolationDelegate(JdbcConnectionAccess connectionAccess, SqlException this.sqlExceptionHelper = sqlExceptionHelper; } - protected JdbcConnectionAccess jdbcConnectionAccess() { - return this.connectionAccess; - } - - protected SqlExceptionHelper sqlExceptionHelper() { - return this.sqlExceptionHelper; - } - @Override - public T delegateWork(WorkExecutorVisitable work, boolean transacted) throws HibernateException { - boolean wasAutoCommit = false; + public T delegateWork(WorkExecutorVisitable work, boolean transacted) + throws HibernateException { + final Connection connection; try { - final Connection connection = jdbcConnectionAccess().obtainConnection(); - try { - if ( transacted ) { - if ( connection.getAutoCommit() ) { - wasAutoCommit = true; - connection.setAutoCommit( false ); - } - } - - T result = work.accept( new WorkExecutor<>(), connection ); - - if ( transacted ) { - connection.commit(); - } + connection = connectionAccess.obtainConnection(); + } + catch ( SQLException sqle ) { + throw sqlExceptionHelper.convert( sqle, "Unable to obtain isolated JDBC connection" ); + } - return result; + try { + final boolean wasAutoCommit; + try { + wasAutoCommit = disableAutoCommit( transacted, connection ); + } + catch (SQLException sqle) { + throw sqlExceptionHelper.convert( sqle, "Unable to manage autocommit on isolated JDBC connection" ); } - catch ( Exception e ) { - try { - if ( transacted && !connection.isClosed() ) { - connection.rollback(); - } - } - catch ( Exception exception ) { - JDBC_MESSAGE_LOGGER.unableToRollBackIsolatedConnection( exception ); - } - if ( e instanceof HibernateException ) { - throw e; + try { + return doWorkAndCommit( work, transacted, connection ); + } + catch (Exception exception) { + rollBack( transacted, connection, exception ); + if ( exception instanceof HibernateException he ) { + throw he; } - else if ( e instanceof SQLException sqle ) { - throw sqlExceptionHelper().convert( sqle, "Error performing isolated work" ); + else if ( exception instanceof SQLException sqle ) { + throw sqlExceptionHelper.convert( sqle, "Error performing isolated work" ); } else { - throw new HibernateException( "Error performing isolated work", e ); + throw new HibernateException( "Error performing isolated work", exception ); } } finally { - if ( transacted && wasAutoCommit ) { - try { - connection.setAutoCommit( true ); - } - catch ( Exception ignore ) { - JDBC_MESSAGE_LOGGER.unableToResetAutoCommit(); - } - } - try { - jdbcConnectionAccess().releaseConnection( connection ); - } - catch ( Exception ignored ) { - JDBC_MESSAGE_LOGGER.unableToReleaseIsolatedConnection( ignored ); - } + resetAutoCommit( transacted, wasAutoCommit, connection ); } } - catch ( SQLException sqle ) { - throw sqlExceptionHelper().convert( sqle, "Unable to obtain isolated JDBC connection" ); + finally { + releaseConnection( connection ); + } + } + + private static T doWorkAndCommit(WorkExecutorVisitable work, boolean transacted, Connection connection) + throws SQLException { + T result = work.accept( new WorkExecutor<>(), connection ); + if ( transacted ) { + connection.commit(); + } + return result; + } + + private void releaseConnection(Connection connection) { + try { + connectionAccess.releaseConnection( connection ); + } + catch ( Exception exception ) { + JDBC_MESSAGE_LOGGER.unableToReleaseIsolatedConnection( exception ); + } + } + + private static void rollBack(boolean transacted, Connection connection, Exception original) { + try { + if ( transacted && !connection.isClosed() ) { + connection.rollback(); + } + } + catch ( Exception exception ) { + JDBC_MESSAGE_LOGGER.unableToRollBackIsolatedConnection( exception ); + original.addSuppressed( exception ); + } + } + + private static void resetAutoCommit(boolean transacted, boolean wasAutoCommit, Connection connection) { + if ( transacted && wasAutoCommit ) { + try { + connection.setAutoCommit( true ); + } + catch ( Exception exception ) { + JDBC_MESSAGE_LOGGER.unableToResetAutoCommit( exception ); + } + } + } + + private static boolean disableAutoCommit(boolean transacted, Connection connection) + throws SQLException { + if ( transacted ) { + final boolean wasAutoCommit = connection.getAutoCommit(); + if ( wasAutoCommit ) { + connection.setAutoCommit( false ); + } + return wasAutoCommit; + } + else { + return false; } } @Override - public T delegateCallable(Callable callable, boolean transacted) throws HibernateException { + public T delegateCallable(Callable callable, boolean transacted) + throws HibernateException { // No connection, nothing to be suspended try { return callable.call(); @@ -120,8 +149,8 @@ public T delegateCallable(Callable callable, boolean transacted) throws H catch ( HibernateException e ) { throw e; } - catch ( Exception e ) { - throw new HibernateException( e ); + catch ( Exception exception ) { + throw new HibernateException( exception ); } } } diff --git a/hibernate-core/src/main/java/org/hibernate/resource/transaction/backend/jta/internal/JtaIsolationDelegate.java b/hibernate-core/src/main/java/org/hibernate/resource/transaction/backend/jta/internal/JtaIsolationDelegate.java index cca98b50b978..2431ec427b72 100644 --- a/hibernate-core/src/main/java/org/hibernate/resource/transaction/backend/jta/internal/JtaIsolationDelegate.java +++ b/hibernate-core/src/main/java/org/hibernate/resource/transaction/backend/jta/internal/JtaIsolationDelegate.java @@ -4,6 +4,7 @@ */ package org.hibernate.resource.transaction.backend.jta.internal; +import jakarta.transaction.InvalidTransactionException; import jakarta.transaction.NotSupportedException; import jakarta.transaction.SystemException; import jakarta.transaction.Transaction; @@ -16,12 +17,12 @@ import org.hibernate.AssertionFailure; import org.hibernate.HibernateException; import org.hibernate.JDBCException; +import org.hibernate.TransactionException; import org.hibernate.engine.jdbc.connections.spi.JdbcConnectionAccess; import org.hibernate.engine.jdbc.spi.SqlExceptionHelper; import org.hibernate.exception.internal.SQLStateConversionDelegate; import org.hibernate.resource.jdbc.spi.JdbcSessionOwner; import org.hibernate.resource.transaction.spi.IsolationDelegate; -import org.hibernate.internal.util.ExceptionHelper; import org.hibernate.jdbc.WorkExecutor; import org.hibernate.jdbc.WorkExecutorVisitable; import org.hibernate.resource.transaction.spi.TransactionCoordinatorOwner; @@ -33,7 +34,7 @@ * * @author Andrea Boriero */ -public class JtaIsolationDelegate implements IsolationDelegate { +public final class JtaIsolationDelegate implements IsolationDelegate { private final JdbcConnectionAccess connectionAccess; private final BiFunction sqlExceptionConverter; @@ -44,11 +45,7 @@ public JtaIsolationDelegate(TransactionCoordinatorOwner transactionCoordinatorOw } public JtaIsolationDelegate(JdbcSessionOwner jdbcSessionOwner, TransactionManager transactionManager) { - this( - jdbcSessionOwner.getJdbcConnectionAccess(), - jdbcSessionOwner.getSqlExceptionHelper(), - transactionManager - ); + this( jdbcSessionOwner.getJdbcConnectionAccess(), jdbcSessionOwner.getSqlExceptionHelper(), transactionManager ); } public JtaIsolationDelegate( @@ -71,14 +68,6 @@ public JtaIsolationDelegate( } } - private JdbcConnectionAccess jdbcConnectionAccess() { - return connectionAccess; - } - - private BiFunction sqlExceptionConverter() { - return sqlExceptionConverter; - } - @Override public T delegateWork(final WorkExecutorVisitable work, final boolean transacted) throws HibernateException { return doInSuspendedTransaction( @@ -96,7 +85,7 @@ public T delegateCallable(final Callable callable, final boolean transact : call( callable )); } - private static T call(final Callable callable) { + private static T call(final Callable callable) { try { return callable.call(); } @@ -109,107 +98,138 @@ private static T call(final Callable callable) { } private T doInSuspendedTransaction(HibernateCallable callable) { - Throwable originalException = null; + final Transaction surroundingTransaction; try { - // First we suspend any current JTA transaction - final Transaction surroundingTransaction = transactionManager.suspend(); - if ( surroundingTransaction != null ) { - JTA_LOGGER.transactionSuspended( surroundingTransaction ); - } + // suspend current JTA transaction, if any + surroundingTransaction = suspend(); + } + catch ( SystemException systemException ) { + throw new TransactionException( "Unable to suspend current JTA transaction", systemException ); + } + Throwable exception = null; + try { + return callable.call(); + } + catch ( HibernateException he ) { + exception = he; + throw he; + } + catch ( Throwable throwable ) { + exception = throwable; + throw new HibernateException( "Unable to perform isolated work", throwable ); + } + finally { try { - return callable.call(); - } - catch ( Throwable t1 ) { - originalException = t1; + // resume the JTA transaction we suspended + resume( surroundingTransaction ); } - finally { - try { - if ( surroundingTransaction != null ) { - transactionManager.resume( surroundingTransaction ); - JTA_LOGGER.transactionResumed( surroundingTransaction ); - } + catch ( Throwable throwable ) { + // if the actual work had an error, use that; otherwise throw this error + if ( exception == null ) { + throw new TransactionException( "Unable to resume suspended transaction", throwable ); } - catch ( Throwable t2 ) { - // if the actually work had an error use that, otherwise error based on t - if ( originalException == null ) { - originalException = new HibernateException( "Unable to resume previously suspended transaction", t2 ); - } - else { - originalException.addSuppressed( t2 ); // No extra nesting, directly t2 - } + else { + exception.addSuppressed( throwable ); } } } - catch ( SystemException e ) { - originalException = new HibernateException( "Unable to suspend current JTA transaction", e ); + } + + private void resume(Transaction surroundingTransaction) throws InvalidTransactionException, SystemException { + if ( surroundingTransaction != null ) { + transactionManager.resume( surroundingTransaction ); + JTA_LOGGER.transactionResumed( surroundingTransaction ); } + } - ExceptionHelper.doThrow( originalException ); - return null; + private Transaction suspend() throws SystemException { + final Transaction surroundingTransaction = transactionManager.suspend(); + if ( surroundingTransaction != null ) { + JTA_LOGGER.transactionSuspended( surroundingTransaction ); + } + return surroundingTransaction; } private T doInNewTransaction(HibernateCallable callable, TransactionManager transactionManager) { try { // start the new isolated transaction transactionManager.begin(); - try { - T result = callable.call(); - // if everything went ok, commit the isolated transaction - transactionManager.commit(); - return result; + } + catch ( SystemException | NotSupportedException exception ) { + throw new TransactionException( "Unable to start isolated transaction", exception ); + } + + try { + T result = callable.call(); + // if everything went ok, commit the isolated transaction + transactionManager.commit(); + return result; + } + catch ( Exception exception ) { //TODO: should this be Throwable + rollBack( transactionManager, exception ); + if ( exception instanceof HibernateException he ) { + throw he; } - catch ( Exception e ) { - try { - transactionManager.rollback(); - } - catch ( Exception exception ) { - JTA_LOGGER.unableToRollbackIsolatedTransaction( e, exception ); - } - throw new HibernateException( "Could not apply work", e ); + else { + throw new HibernateException( "Error performing work", exception ); } } - catch ( SystemException | NotSupportedException e ) { - throw new HibernateException( "Unable to start isolated transaction", e ); + } + + private static void rollBack(TransactionManager transactionManager, Exception original) { + try { + transactionManager.rollback(); + } + catch ( Exception exception ) { + JTA_LOGGER.unableToRollBackIsolatedTransaction( original, exception ); + original.addSuppressed( exception ); } } private T doTheWork(WorkExecutorVisitable work) { + final Connection connection; try { // obtain our isolated connection - Connection connection = jdbcConnectionAccess().obtainConnection(); - try { - // do the actual work - return work.accept( new WorkExecutor<>(), connection ); - } - catch (SQLException sqle) { - throw sqlExceptionConverter().apply( sqle, "Error performing isolated work" ); - } - catch ( HibernateException e ) { - throw e; - } - catch ( Exception e ) { - throw new HibernateException( "Unable to perform isolated work", e ); - } - finally { - try { - // no matter what, release the connection (handle) - jdbcConnectionAccess().releaseConnection( connection ); - } - catch ( Throwable throwable ) { - JTA_LOGGER.unableToReleaseIsolatedConnection( throwable ); - } - } + connection = connectionAccess.obtainConnection(); } - catch (SQLException e) { - final JDBCException jdbcException = sqlExceptionConverter().apply( e, "unable to obtain isolated JDBC connection" ); - if ( jdbcException == null ) { - throw new HibernateException( "Unable to obtain isolated JDBC connection", e ); - } - throw jdbcException; + catch ( SQLException sqle ) { + throw convert( sqle, "Unable to obtain isolated JDBC connection" ); + } + + try { + // do the actual work + return work.accept( new WorkExecutor<>(), connection ); + } + catch ( HibernateException he ) { + throw he; + } + catch (SQLException sqle) { + throw convert( sqle, "Error performing isolated work" ); + } + catch ( Exception e ) { + throw new HibernateException( "Error performing isolated work", e ); + } + finally { + // no matter what, release the connection (handle) + releaseConnection( connection ); } } + private void releaseConnection(Connection connection) { + try { + connectionAccess.releaseConnection( connection ); + } + catch ( Throwable throwable ) { + JTA_LOGGER.unableToReleaseIsolatedConnection( throwable ); + } + } + + private HibernateException convert(SQLException sqle, String message) { + final JDBCException jdbcException = sqlExceptionConverter.apply( sqle, message ); + return jdbcException == null ? new HibernateException( message, sqle ) : jdbcException; + } + // Callable that does not throw Exception; in Java <8 there's no Supplier private interface HibernateCallable { T call() throws HibernateException; diff --git a/hibernate-core/src/main/java/org/hibernate/resource/transaction/backend/jta/internal/JtaLogging.java b/hibernate-core/src/main/java/org/hibernate/resource/transaction/backend/jta/internal/JtaLogging.java index 324585c6abd1..a931a90d20b8 100644 --- a/hibernate-core/src/main/java/org/hibernate/resource/transaction/backend/jta/internal/JtaLogging.java +++ b/hibernate-core/src/main/java/org/hibernate/resource/transaction/backend/jta/internal/JtaLogging.java @@ -157,10 +157,10 @@ public interface JtaLogging extends BasicLogger { @LogMessage(level = Logger.Level.INFO) @Message( - value = "Unable to roll back isolated transaction on error [%s] : [%s]", + value = "Unable to roll back isolated transaction on error [%s]", id = NAMESPACE + 17 ) - void unableToRollbackIsolatedTransaction(Exception e, Exception ignore); + void unableToRollBackIsolatedTransaction(Exception original, @Cause Exception ignore); @LogMessage(level = Logger.Level.INFO) @Message(