diff --git a/hibernate-agroal/src/main/java/org/hibernate/agroal/internal/AgroalConnectionProvider.java b/hibernate-agroal/src/main/java/org/hibernate/agroal/internal/AgroalConnectionProvider.java index 4db6f88c813d..6e7331b53b19 100644 --- a/hibernate-agroal/src/main/java/org/hibernate/agroal/internal/AgroalConnectionProvider.java +++ b/hibernate-agroal/src/main/java/org/hibernate/agroal/internal/AgroalConnectionProvider.java @@ -117,6 +117,11 @@ public boolean supportsAggressiveRelease() { return false; } + @Override + public boolean connectionWarningsResetCanBeSkippedOnClose() { + return true; + } + @Override @SuppressWarnings( "rawtypes" ) public boolean isUnwrappableAs(Class unwrapType) { diff --git a/hibernate-agroal/src/test/java/org/hibernate/test/agroal/skipwarnings/AgroalSkipClearWarningsByDefaultTest.java b/hibernate-agroal/src/test/java/org/hibernate/test/agroal/skipwarnings/AgroalSkipClearWarningsByDefaultTest.java new file mode 100644 index 000000000000..3050682ca358 --- /dev/null +++ b/hibernate-agroal/src/test/java/org/hibernate/test/agroal/skipwarnings/AgroalSkipClearWarningsByDefaultTest.java @@ -0,0 +1,89 @@ +/* + * 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 . + */ +package org.hibernate.test.agroal.skipwarnings; + +import java.util.Properties; +import javax.persistence.Entity; +import javax.persistence.Id; + +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.cfg.Configuration; + +import org.hibernate.testing.DialectChecks; +import org.hibernate.testing.RequiresDialectFeature; +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.hibernate.test.agroal.util.PreparedStatementSpyConnectionProvider; +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; +import static org.junit.Assert.assertEquals; + +/** + * Testing the impact of org.hibernate.agroal.internal.AgroalConnectionProvider#connectionWarningsResetCanBeSkippedOnClose() + * returning true, and without setting a configuration option explicitly for LOG_JDBC_WARNINGS. + * + * @author Sanne Grinovero + */ +@RequiresDialectFeature(DialectChecks.SupportsJdbcDriverProxying.class) +public class AgroalSkipClearWarningsByDefaultTest extends BaseCoreFunctionalTestCase { + + private PreparedStatementSpyConnectionProvider connectionProvider = new PreparedStatementSpyConnectionProvider(true ); + + @Override + protected void configure(Configuration configuration) { + Properties properties = configuration.getProperties(); + properties.put( AvailableSettings.CONNECTION_PROVIDER, connectionProvider ); + } + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[]{ City.class }; + } + + @Test + public void test() { + connectionProvider.clear(); + doInHibernate( this::sessionFactory, session -> { + City city = new City(); + city.setId( 1L ); + city.setName( "London" ); + session.persist( city ); + assertEquals( 0, connectionProvider.getCountInvocationsToClearWarnings() ); + } ); + assertEquals( 0, connectionProvider.getCountInvocationsToClearWarnings() ); + doInHibernate( this::sessionFactory, session -> { + City city = session.find( City.class, 1L ); + assertEquals( "London", city.getName() ); + } ); + assertEquals( 0, connectionProvider.getCountInvocationsToClearWarnings() ); + } + + @Entity(name = "City" ) + public static class City { + + @Id + private Long id; + + private String name; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + } +} diff --git a/hibernate-agroal/src/test/java/org/hibernate/test/agroal/skipwarnings/AgroalSkipClearWarningsTest.java b/hibernate-agroal/src/test/java/org/hibernate/test/agroal/skipwarnings/AgroalSkipClearWarningsTest.java new file mode 100644 index 000000000000..04e70fc0aeeb --- /dev/null +++ b/hibernate-agroal/src/test/java/org/hibernate/test/agroal/skipwarnings/AgroalSkipClearWarningsTest.java @@ -0,0 +1,91 @@ +/* + * 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 . + */ +package org.hibernate.test.agroal.skipwarnings; + +import java.util.Properties; +import javax.persistence.Entity; +import javax.persistence.Id; + +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.cfg.Configuration; + +import org.hibernate.testing.DialectChecks; +import org.hibernate.testing.RequiresDialectFeature; +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.hibernate.test.agroal.util.PreparedStatementSpyConnectionProvider; +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; +import static org.junit.Assert.assertEquals; + +/** + * Testing the impact of org.hibernate.agroal.internal.AgroalConnectionProvider#connectionWarningsResetCanBeSkippedOnClose() + * returning true. + * + * @author Sanne Grinovero + */ +@RequiresDialectFeature(DialectChecks.SupportsJdbcDriverProxying.class) +public class AgroalSkipClearWarningsTest extends BaseCoreFunctionalTestCase { + + private PreparedStatementSpyConnectionProvider connectionProvider = new PreparedStatementSpyConnectionProvider(true ); + + @Override + protected void configure(Configuration configuration) { + Properties properties = configuration.getProperties(); + //Disable logging of JDBC warnings, as that has an impact: + properties.put( AvailableSettings.LOG_JDBC_WARNINGS, false ); + properties.put( AvailableSettings.CONNECTION_PROVIDER, connectionProvider ); + } + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[]{ City.class }; + } + + @Test + public void test() { + connectionProvider.clear(); + doInHibernate( this::sessionFactory, session -> { + City city = new City(); + city.setId( 1L ); + city.setName( "London" ); + session.persist( city ); + assertEquals( 0, connectionProvider.getCountInvocationsToClearWarnings() ); + } ); + assertEquals( 0, connectionProvider.getCountInvocationsToClearWarnings() ); + doInHibernate( this::sessionFactory, session -> { + City city = session.find( City.class, 1L ); + assertEquals( "London", city.getName() ); + } ); + assertEquals( 0, connectionProvider.getCountInvocationsToClearWarnings() ); + } + + @Entity(name = "City" ) + public static class City { + + @Id + private Long id; + + private String name; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + } +} diff --git a/hibernate-agroal/src/test/java/org/hibernate/test/agroal/skipwarnings/SkipClearWarningsDisabledTest.java b/hibernate-agroal/src/test/java/org/hibernate/test/agroal/skipwarnings/SkipClearWarningsDisabledTest.java new file mode 100644 index 000000000000..f7639e9e8d84 --- /dev/null +++ b/hibernate-agroal/src/test/java/org/hibernate/test/agroal/skipwarnings/SkipClearWarningsDisabledTest.java @@ -0,0 +1,91 @@ +/* + * 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 . + */ +package org.hibernate.test.agroal.skipwarnings; + +import java.util.Properties; +import javax.persistence.Entity; +import javax.persistence.Id; + +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.cfg.Configuration; + +import org.hibernate.testing.DialectChecks; +import org.hibernate.testing.RequiresDialectFeature; +import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase; +import org.hibernate.test.agroal.util.PreparedStatementSpyConnectionProvider; +import org.junit.Test; + +import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; +import static org.junit.Assert.assertEquals; + +/** + * Testing the impact of org.hibernate.agroal.internal.AgroalConnectionProvider#connectionWarningsResetCanBeSkippedOnClose() + * returning false. + * + * @author Sanne Grinovero + */ +@RequiresDialectFeature(DialectChecks.SupportsJdbcDriverProxying.class) +public class SkipClearWarningsDisabledTest extends BaseCoreFunctionalTestCase { + + private PreparedStatementSpyConnectionProvider connectionProvider = new PreparedStatementSpyConnectionProvider( false ); + + @Override + protected void configure(Configuration configuration) { + Properties properties = configuration.getProperties(); + //Disable logging of JDBC warnings, as that has an impact: + properties.put( AvailableSettings.LOG_JDBC_WARNINGS, false ); + properties.put( AvailableSettings.CONNECTION_PROVIDER, connectionProvider ); + } + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[]{ City.class }; + } + + @Test + public void test() { + connectionProvider.clear(); + doInHibernate( this::sessionFactory, session -> { + City city = new City(); + city.setId( 1L ); + city.setName( "London" ); + session.persist( city ); + assertEquals( 0, connectionProvider.getCountInvocationsToClearWarnings() ); + } ); + assertEquals( 1, connectionProvider.getCountInvocationsToClearWarnings() ); + doInHibernate( this::sessionFactory, session -> { + City city = session.find( City.class, 1L ); + assertEquals( "London", city.getName() ); + } ); + assertEquals( 2, connectionProvider.getCountInvocationsToClearWarnings() ); + } + + @Entity(name = "City" ) + public static class City { + + @Id + private Long id; + + private String name; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + } +} diff --git a/hibernate-agroal/src/test/java/org/hibernate/test/agroal/util/PreparedStatementSpyConnectionProvider.java b/hibernate-agroal/src/test/java/org/hibernate/test/agroal/util/PreparedStatementSpyConnectionProvider.java index 43b8ba31e892..1d2e0435688b 100644 --- a/hibernate-agroal/src/test/java/org/hibernate/test/agroal/util/PreparedStatementSpyConnectionProvider.java +++ b/hibernate-agroal/src/test/java/org/hibernate/test/agroal/util/PreparedStatementSpyConnectionProvider.java @@ -14,6 +14,7 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; import org.hibernate.agroal.internal.AgroalConnectionProvider; import org.hibernate.engine.jdbc.connections.spi.ConnectionProvider; @@ -34,8 +35,15 @@ public class PreparedStatementSpyConnectionProvider extends AgroalConnectionProv private final List acquiredConnections = new ArrayList<>( ); private final List releasedConnections = new ArrayList<>( ); + private final AtomicInteger clearWarningsCallCounter = new AtomicInteger( 0 ); + private final boolean skipWarningsOnClose; public PreparedStatementSpyConnectionProvider() { + this(true); + } + + public PreparedStatementSpyConnectionProvider(boolean skipWarningsOnClose) { + this.skipWarningsOnClose = skipWarningsOnClose; } protected Connection actualConnection() throws SQLException { @@ -62,6 +70,11 @@ public void stop() { super.stop(); } + @Override + public boolean connectionWarningsResetCanBeSkippedOnClose() { + return skipWarningsOnClose; + } + private Connection spy(Connection connection) { if ( MockUtil.isMock( connection ) ) { return connection; @@ -76,6 +89,12 @@ private Connection spy(Connection connection) { return statementSpy; } ).when( connectionSpy ).prepareStatement( ArgumentMatchers.anyString() ); + Mockito.doAnswer( invocation -> { + invocation.callRealMethod(); + clearWarningsCallCounter.incrementAndGet(); + return null; + } ).when( connectionSpy ).clearWarnings(); + Mockito.doAnswer( invocation -> { Statement statement = (Statement) invocation.callRealMethod(); Statement statementSpy = Mockito.spy( statement ); @@ -96,6 +115,7 @@ public void clear() { releasedConnections.clear(); preparedStatementMap.keySet().forEach( Mockito::reset ); preparedStatementMap.clear(); + clearWarningsCallCounter.set( 0 ); } /** @@ -113,4 +133,8 @@ public List getAcquiredConnections() { public List getReleasedConnections() { return releasedConnections; } + + public int getCountInvocationsToClearWarnings() { + return clearWarningsCallCounter.get(); + } } diff --git a/hibernate-core/src/main/java/org/hibernate/boot/internal/SessionFactoryOptionsBuilder.java b/hibernate-core/src/main/java/org/hibernate/boot/internal/SessionFactoryOptionsBuilder.java index 52cf2f8ce56e..73912ccd020e 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/internal/SessionFactoryOptionsBuilder.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/internal/SessionFactoryOptionsBuilder.java @@ -252,6 +252,7 @@ public class SessionFactoryOptionsBuilder implements SessionFactoryOptions { private boolean nativeExceptionHandling51Compliance; private int queryStatisticsMaxSize; + private final boolean connectionWarningsResetCanBeSkippedOnClose; @SuppressWarnings({"WeakerAccess", "deprecation"}) @@ -534,6 +535,8 @@ else if ( jdbcTimeZoneValue != null ) { log.nativeExceptionHandling51ComplianceJpaBootstrapping(); this.nativeExceptionHandling51Compliance = false; } + + this.connectionWarningsResetCanBeSkippedOnClose = jdbcServices.getBootstrapJdbcConnectionAccess().connectionWarningsResetCanBeSkippedOnClose(); } @SuppressWarnings("deprecation") @@ -1072,6 +1075,11 @@ public boolean isOmitJoinOfSuperclassTablesEnabled() { return omitJoinOfSuperclassTablesEnabled; } + @Override + public boolean isConnectionWarningsResetCanBeSkippedOnClose() { + return connectionWarningsResetCanBeSkippedOnClose; + } + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // In-flight mutation access diff --git a/hibernate-core/src/main/java/org/hibernate/boot/spi/AbstractDelegatingSessionFactoryOptions.java b/hibernate-core/src/main/java/org/hibernate/boot/spi/AbstractDelegatingSessionFactoryOptions.java index a3de3a79630e..ff15bb2d4800 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/spi/AbstractDelegatingSessionFactoryOptions.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/spi/AbstractDelegatingSessionFactoryOptions.java @@ -452,4 +452,9 @@ public boolean isEnhancementAsProxyEnabled() { public boolean isOmitJoinOfSuperclassTablesEnabled() { return delegate.isOmitJoinOfSuperclassTablesEnabled(); } + + @Override + public boolean isConnectionWarningsResetCanBeSkippedOnClose() { + return delegate.isConnectionWarningsResetCanBeSkippedOnClose(); + } } diff --git a/hibernate-core/src/main/java/org/hibernate/boot/spi/SessionFactoryOptions.java b/hibernate-core/src/main/java/org/hibernate/boot/spi/SessionFactoryOptions.java index 929a33c4a272..d9a04162ab04 100644 --- a/hibernate-core/src/main/java/org/hibernate/boot/spi/SessionFactoryOptions.java +++ b/hibernate-core/src/main/java/org/hibernate/boot/spi/SessionFactoryOptions.java @@ -316,4 +316,13 @@ default boolean isEnhancementAsProxyEnabled() { } boolean isOmitJoinOfSuperclassTablesEnabled(); + + /** + * On connection release, we normally reset all JDBC warnings on it, as the connection is + * possibly reused by some external party. + * Many pools will perform this operation as well; if you are sure that Hibernate ORM may + * safely skip this operation, set this to true to avoid performing this operation + * unnecessarily: on some JDBC drivers it's not very efficient. + */ + boolean isConnectionWarningsResetCanBeSkippedOnClose(); } diff --git a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/spi/ConnectionProvider.java b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/spi/ConnectionProvider.java index 07c0f2393d45..4e4f7cc7fdbc 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/spi/ConnectionProvider.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/spi/ConnectionProvider.java @@ -59,4 +59,21 @@ public interface ConnectionProvider extends Service, Wrapped { * @return {@code true} if aggressive releasing is supported; {@code false} otherwise. */ public boolean supportsAggressiveRelease(); + + /** + * Hibernate ORM will normally clear all warnings on the JDBC Connection before + * closing it, this is one aspect of the connection release process. + * When the Connection Pool implementation provides the same functionality + * of clearing all warnings on close, it might be desirable to delegate + * this responsibility to the pool as with some JDBC drivers the operation + * is not very efficient. + * Return true to allow Hibernate to skip the #clearWarnings operation in most + * cases; this is not a strict guarantee that Hibernate will never invoke it. + * @return This returns false by default as it's safe and backwards compatible. + * Return true if you trust the Connection Pool to clear warnings on close. + * @see Connection#clearWarnings() + */ + public default boolean connectionWarningsResetCanBeSkippedOnClose() { + return false; + } } diff --git a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/spi/JdbcConnectionAccess.java b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/spi/JdbcConnectionAccess.java index 8efc9c188ed5..660805f4a5e9 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/spi/JdbcConnectionAccess.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/spi/JdbcConnectionAccess.java @@ -45,4 +45,17 @@ public interface JdbcConnectionAccess extends Serializable { * @see org.hibernate.engine.jdbc.connections.spi.MultiTenantConnectionProvider#supportsAggressiveRelease() */ boolean supportsAggressiveRelease(); + + /** + * If the underlying connection provider takes care of clearing any warnings on the JDBC Connection + * when it's closed (returned to the pool), return true so that Hibernate ORM may skip this operation. + * Setting this to true does not guarantee that Hibernate ORM will never clear the warnings: + * consider this only as a possible performance optimisation. + * + * @since 5.4.13 Introduced as backwards compatible micro-optimisation + * @return + */ + public default boolean connectionWarningsResetCanBeSkippedOnClose() { + return false; + } } diff --git a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/spi/MultiTenantConnectionProvider.java b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/spi/MultiTenantConnectionProvider.java index 54a9cb2b2e6a..47acdd009a70 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/spi/MultiTenantConnectionProvider.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/spi/MultiTenantConnectionProvider.java @@ -78,4 +78,8 @@ public interface MultiTenantConnectionProvider extends Service, Wrapped { * @return {@code true} if aggressive releasing is supported; {@code false} otherwise. */ public boolean supportsAggressiveRelease(); + + public default boolean needsWarningsClearedOnClose() { + return true; + } } diff --git a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/env/internal/JdbcEnvironmentImpl.java b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/env/internal/JdbcEnvironmentImpl.java index b236dd6da16a..2b30c05679b7 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/env/internal/JdbcEnvironmentImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/env/internal/JdbcEnvironmentImpl.java @@ -9,12 +9,9 @@ import java.sql.Connection; import java.sql.DatabaseMetaData; import java.sql.SQLException; -import java.util.Arrays; import java.util.Collections; -import java.util.HashSet; import java.util.LinkedHashSet; import java.util.List; -import java.util.Set; import java.util.stream.Collectors; import java.util.stream.StreamSupport; @@ -24,6 +21,7 @@ import org.hibernate.dialect.Dialect; import org.hibernate.engine.config.spi.ConfigurationService; import org.hibernate.engine.config.spi.StandardConverters; +import org.hibernate.engine.jdbc.connections.spi.ConnectionProvider; import org.hibernate.engine.jdbc.env.spi.ExtractedDatabaseMetaData; import org.hibernate.engine.jdbc.env.spi.IdentifierHelper; import org.hibernate.engine.jdbc.env.spi.IdentifierHelperBuilder; @@ -81,7 +79,7 @@ public JdbcEnvironmentImpl(final ServiceRegistryImplementor serviceRegistry, Dia } this.nameQualifierSupport = nameQualifierSupport; - this.sqlExceptionHelper = buildSqlExceptionHelper( dialect, logWarnings( cfgService, dialect ) ); + this.sqlExceptionHelper = buildSqlExceptionHelper( dialect, logWarnings( serviceRegistry, cfgService, dialect ) ); final IdentifierHelperBuilder identifierHelperBuilder = IdentifierHelperBuilder.from( this ); identifierHelperBuilder.setGloballyQuoteIdentifiers( globalQuoting( cfgService ) ); @@ -119,11 +117,22 @@ public JdbcEnvironmentImpl(final ServiceRegistryImplementor serviceRegistry, Dia this.lobCreatorBuilder = LobCreatorBuilderImpl.makeLobCreatorBuilder(); } - private static boolean logWarnings(ConfigurationService cfgService, Dialect dialect) { + private static boolean logWarnings( + final ServiceRegistryImplementor registry, + ConfigurationService cfgService, + Dialect dialect) { + //We will have the default depend on two conditions: + // - the Dialect to not recommend against the feature + // - and still disable it if the ConnectionProvider implements the optimisation org.hibernate.engine.jdbc.connections.spi.ConnectionProvider.connectionWarningsResetCanBeSkippedOnClose + //Why: otherwise having to check for warnings to be logged works against the optimisation. + + ConnectionProvider connectionProvider = registry.getService( ConnectionProvider.class ); + boolean optimisationEnabled = connectionProvider.connectionWarningsResetCanBeSkippedOnClose(); + return cfgService.getSetting( AvailableSettings.LOG_JDBC_WARNINGS, StandardConverters.BOOLEAN, - dialect.isJdbcLogWarningsEnabledByDefault() + dialect.isJdbcLogWarningsEnabledByDefault() && !optimisationEnabled ); } @@ -234,7 +243,7 @@ public JdbcEnvironmentImpl( final ConfigurationService cfgService = serviceRegistry.getService( ConfigurationService.class ); - this.sqlExceptionHelper = buildSqlExceptionHelper( dialect, logWarnings( cfgService, dialect ) ); + this.sqlExceptionHelper = buildSqlExceptionHelper( dialect, logWarnings( serviceRegistry, cfgService, dialect ) ); NameQualifierSupport nameQualifierSupport = dialect.getNameQualifierSupport(); if ( nameQualifierSupport == null ) { @@ -326,14 +335,6 @@ private SqlExceptionHelper buildSqlExceptionHelper(Dialect dialect, boolean logW return new SqlExceptionHelper( sqlExceptionConverter, logWarnings ); } - private Set buildMergedReservedWords(Dialect dialect, DatabaseMetaData dbmd) throws SQLException { - Set reservedWords = new HashSet(); - reservedWords.addAll( dialect.getKeywords() ); - // todo : do we need to explicitly handle SQL:2003 keywords? - reservedWords.addAll( Arrays.asList( dbmd.getSQLKeywords().split( "," ) ) ); - return reservedWords; - } - @Override public Dialect getDialect() { return dialect; diff --git a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/env/internal/JdbcEnvironmentInitiator.java b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/env/internal/JdbcEnvironmentInitiator.java index 70c44e310f6b..52e12f04daea 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/env/internal/JdbcEnvironmentInitiator.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/env/internal/JdbcEnvironmentInitiator.java @@ -189,6 +189,11 @@ public void releaseConnection(Connection connection) throws SQLException { public boolean supportsAggressiveRelease() { return connectionProvider.supportsAggressiveRelease(); } + + @Override + public boolean connectionWarningsResetCanBeSkippedOnClose() { + return connectionProvider.connectionWarningsResetCanBeSkippedOnClose(); + } } public static class MultiTenantConnectionProviderJdbcConnectionAccess implements JdbcConnectionAccess { diff --git a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/spi/SqlExceptionHelper.java b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/spi/SqlExceptionHelper.java index daac437a4a57..c2846193b644 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/spi/SqlExceptionHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/spi/SqlExceptionHelper.java @@ -51,7 +51,7 @@ public String extractConstraintName(SQLException e) { /** * Create an exception helper with a default exception converter. */ - public SqlExceptionHelper( boolean logWarnings) { + public SqlExceptionHelper(boolean logWarnings) { this( DEFAULT_CONVERTER, logWarnings ); } @@ -266,13 +266,25 @@ public void walkWarnings( * @param connection The JDBC connection potentially containing warnings */ public void logAndClearWarnings(Connection connection) { - handleAndClearWarnings( connection, STANDARD_WARNING_HANDLER ); + handleAndClearWarnings( connection, STANDARD_WARNING_HANDLER, true ); } public void logAndClearWarnings(Statement statement) { handleAndClearWarnings( statement, STANDARD_WARNING_HANDLER ); } + /** + * If logging of warnings has been enabled, extract them from the driver and log them. + * + * @param connection + * @param requiresWarningsReset when set to false, we might skip invoking {@link Connection#clearWarnings()} as we trust the connection pool to do it. + */ + public void logAndClearWarnings( + final Connection connection, + final boolean requiresWarningsReset) { + handleAndClearWarnings( connection, STANDARD_WARNING_HANDLER, requiresWarningsReset ); + } + /** * General purpose handling of warnings associated with a JDBC {@link Connection}. * @@ -285,21 +297,55 @@ public void logAndClearWarnings(Statement statement) { public void handleAndClearWarnings( Connection connection, WarningHandler handler) { - try { - if ( logWarnings ) { - walkWarnings( connection.getWarnings(), handler ); + handleAndClearWarnings( connection, handler, true ); + } + + /** + * General purpose handling of warnings associated with a JDBC {@link Connection}. + * + * @param connection The JDBC connection potentially containing warnings + * @param handler The handler for each individual warning in the stack. + * @param requiresWarningsReset when set to false, we might skip invoking {@link Connection#clearWarnings()} as we trust the connection pool to do it. + * + * @see #walkWarnings + */ + @SuppressWarnings({"ThrowableResultOfMethodCallIgnored"}) + public void handleAndClearWarnings( + Connection connection, + WarningHandler handler, + final boolean requiresWarningsReset) { + //Start with a pessimistic assumption, but allow to skip if we happen to learn more + //when (and if) we actually do retrieve the warnings: + boolean thereMightBeWarnings = true; + boolean thereDefinitelyAreWarnigs = false; + if ( logWarnings ) { + try { + SQLWarning warnings = connection.getWarnings(); + if ( warnings == null ) { + thereMightBeWarnings = false; + } + else { + walkWarnings( warnings, handler ); + thereDefinitelyAreWarnigs = true; + } + } + catch (SQLException sqle) { + // workaround for WebLogic + LOG.debug( "could not log warnings", sqle ); } } - catch (SQLException sqle) { - // workaround for WebLogic - LOG.debug( "could not log warnings", sqle ); - } - try { - // Sybase fail if we don't do that, sigh... - connection.clearWarnings(); - } - catch (SQLException sqle) { - LOG.debug( "could not clear warnings", sqle ); + //If there are warnings for sure, we prefer to clear them even when requiresWarningsReset is set + //so to avoid duplicate warnings being logged (as the connection pool would likely do it as well) + //Also consider: flag requiresWarningsReset is a performance tool, but if there are known warnings + //we're not operating at peak performance anyway. + if ( thereDefinitelyAreWarnigs || ( thereMightBeWarnings && requiresWarningsReset ) ) { + try { + // Sybase fail if we don't do that, sigh... + connection.clearWarnings(); + } + catch (SQLException sqle) { + LOG.debug( "could not clear warnings", sqle ); + } } } @@ -319,13 +365,21 @@ public void handleAndClearWarnings( // the log level would actually allow a warning to be logged. if ( logWarnings ) { try { - walkWarnings( statement.getWarnings(), handler ); + SQLWarning warnings = statement.getWarnings(); + if ( warnings == null ) { + } + else { + walkWarnings( warnings, handler ); + } } catch (SQLException sqlException) { // workaround for WebLogic LOG.debug( "could not log warnings", sqlException ); } } + //N.B. don't do the same optimisations as we do for handleAndClearWarnings(Connection, Handler): + //clearing warnings on a statement is much cheaper than on a connection, and we can't delegate + //this responsibility to the connection pool. try { // Sybase fail if we don't do that, sigh... statement.clearWarnings(); diff --git a/hibernate-core/src/main/java/org/hibernate/internal/ContextualJdbcConnectionAccess.java b/hibernate-core/src/main/java/org/hibernate/internal/ContextualJdbcConnectionAccess.java index c908aae792fe..ec4ae954cb02 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/ContextualJdbcConnectionAccess.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/ContextualJdbcConnectionAccess.java @@ -66,4 +66,9 @@ public void releaseConnection(Connection connection) throws SQLException { public boolean supportsAggressiveRelease() { return connectionProvider.supportsAggressiveRelease(); } + + @Override + public boolean connectionWarningsResetCanBeSkippedOnClose() { + return connectionProvider.needsWarningsClearedOnClose(); + } } diff --git a/hibernate-core/src/main/java/org/hibernate/internal/JdbcSessionContextImpl.java b/hibernate-core/src/main/java/org/hibernate/internal/JdbcSessionContextImpl.java index 954593d6d55a..36edf667979f 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/JdbcSessionContextImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/JdbcSessionContextImpl.java @@ -98,6 +98,11 @@ public ServiceRegistry getServiceRegistry() { return this.serviceRegistry; } + @Override + public boolean isConnectionWarningsResetCanBeSkippedOnClose() { + return settings().isConnectionWarningsResetCanBeSkippedOnClose(); + } + private SessionFactoryOptions settings() { return this.sessionFactory.getSessionFactoryOptions(); } diff --git a/hibernate-core/src/main/java/org/hibernate/resource/jdbc/internal/AbstractLogicalConnectionImplementor.java b/hibernate-core/src/main/java/org/hibernate/resource/jdbc/internal/AbstractLogicalConnectionImplementor.java index 0202fa637d30..9466faa71f0d 100644 --- a/hibernate-core/src/main/java/org/hibernate/resource/jdbc/internal/AbstractLogicalConnectionImplementor.java +++ b/hibernate-core/src/main/java/org/hibernate/resource/jdbc/internal/AbstractLogicalConnectionImplementor.java @@ -24,7 +24,11 @@ public abstract class AbstractLogicalConnectionImplementor implements LogicalCon private static final Logger log = Logger.getLogger( AbstractLogicalConnectionImplementor.class ); private TransactionStatus status = TransactionStatus.NOT_ACTIVE; - protected ResourceRegistry resourceRegistry; + protected final ResourceRegistry resourceRegistry; + + protected AbstractLogicalConnectionImplementor(ResourceRegistry resourceRegistry) { + this.resourceRegistry = resourceRegistry; + } @Override public PhysicalJdbcTransaction getPhysicalJdbcTransaction() { diff --git a/hibernate-core/src/main/java/org/hibernate/resource/jdbc/internal/LogicalConnectionManagedImpl.java b/hibernate-core/src/main/java/org/hibernate/resource/jdbc/internal/LogicalConnectionManagedImpl.java index 3486a39fc260..a43bbea6ee5e 100644 --- a/hibernate-core/src/main/java/org/hibernate/resource/jdbc/internal/LogicalConnectionManagedImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/resource/jdbc/internal/LogicalConnectionManagedImpl.java @@ -39,6 +39,7 @@ public class LogicalConnectionManagedImpl extends AbstractLogicalConnectionImple private final transient SqlExceptionHelper sqlExceptionHelper; private final transient PhysicalConnectionHandlingMode connectionHandlingMode; + private final boolean requiresWarningsResetOnClose; private transient Connection physicalConnection; private boolean closed; @@ -50,9 +51,9 @@ public LogicalConnectionManagedImpl( JdbcSessionContext jdbcSessionContext, ResourceRegistry resourceRegistry, JdbcServices jdbcServices) { + super( resourceRegistry ); this.jdbcConnectionAccess = jdbcConnectionAccess; this.observer = jdbcSessionContext.getObserver(); - this.resourceRegistry = resourceRegistry; this.connectionHandlingMode = determineConnectionHandlingMode( jdbcSessionContext.getPhysicalConnectionHandlingMode(), @@ -64,8 +65,9 @@ public LogicalConnectionManagedImpl( acquireConnectionIfNeeded(); } + this.requiresWarningsResetOnClose = !jdbcSessionContext.isConnectionWarningsResetCanBeSkippedOnClose(); this.providerDisablesAutoCommit = jdbcSessionContext.doesConnectionProviderDisableAutoCommit(); - if ( providerDisablesAutoCommit ) { + if ( providerDisablesAutoCommit && log.isDebugEnabled() ) { log.debug( "`hibernate.connection.provider_disables_autocommit` was enabled. This setting should only be " + "enabled when you are certain that the Connections given to Hibernate by the " + @@ -139,7 +141,7 @@ public void afterStatement() { super.afterStatement(); if ( connectionHandlingMode.getReleaseMode() == ConnectionReleaseMode.AFTER_STATEMENT ) { - if ( getResourceRegistry().hasRegisteredResources() ) { + if ( this.resourceRegistry.hasRegisteredResources() ) { log.debug( "Skipping aggressive release of JDBC Connection after-statement due to held resources" ); } else { @@ -186,8 +188,8 @@ private void releaseConnection() { } try { - if ( !physicalConnection.isClosed() ) { - sqlExceptionHelper.logAndClearWarnings( physicalConnection ); + if ( !physicalConnection.isClosed()) { + sqlExceptionHelper.logAndClearWarnings( physicalConnection, requiresWarningsResetOnClose ); } jdbcConnectionAccess.releaseConnection( physicalConnection ); } @@ -197,7 +199,7 @@ private void releaseConnection() { finally { observer.jdbcConnectionReleaseEnd(); physicalConnection = null; - getResourceRegistry().releaseResources(); + this.resourceRegistry.releaseResources(); } } @@ -228,7 +230,7 @@ public Connection close() { return null; } - getResourceRegistry().releaseResources(); + this.resourceRegistry.releaseResources(); log.trace( "Closing logical connection" ); try { diff --git a/hibernate-core/src/main/java/org/hibernate/resource/jdbc/internal/LogicalConnectionProvidedImpl.java b/hibernate-core/src/main/java/org/hibernate/resource/jdbc/internal/LogicalConnectionProvidedImpl.java index 9e6ba5d32970..5987b4571492 100644 --- a/hibernate-core/src/main/java/org/hibernate/resource/jdbc/internal/LogicalConnectionProvidedImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/resource/jdbc/internal/LogicalConnectionProvidedImpl.java @@ -29,7 +29,7 @@ public class LogicalConnectionProvidedImpl extends AbstractLogicalConnectionImpl private boolean closed; public LogicalConnectionProvidedImpl(Connection providedConnection, ResourceRegistry resourceRegistry) { - this.resourceRegistry = resourceRegistry; + super( resourceRegistry ); if ( providedConnection == null ) { throw new IllegalArgumentException( "Provided Connection cannot be null" ); } @@ -39,7 +39,7 @@ public LogicalConnectionProvidedImpl(Connection providedConnection, ResourceRegi } private LogicalConnectionProvidedImpl(boolean closed, boolean initiallyAutoCommit) { - this.resourceRegistry = new ResourceRegistryStandardImpl(); + super( new ResourceRegistryStandardImpl() ); this.closed = closed; this.initiallyAutoCommit = initiallyAutoCommit; } @@ -58,7 +58,7 @@ public boolean isOpen() { public Connection close() { log.trace( "Closing logical connection" ); - getResourceRegistry().releaseResources(); + this.resourceRegistry.releaseResources(); try { return providedConnection; @@ -105,7 +105,7 @@ public static LogicalConnectionProvidedImpl deserialize( public Connection manualDisconnect() { errorIfClosed(); try { - resourceRegistry.releaseResources(); + this.resourceRegistry.releaseResources(); return providedConnection; } finally { diff --git a/hibernate-core/src/main/java/org/hibernate/resource/jdbc/spi/JdbcSessionContext.java b/hibernate-core/src/main/java/org/hibernate/resource/jdbc/spi/JdbcSessionContext.java index 8116dd04ecab..47379475ead0 100644 --- a/hibernate-core/src/main/java/org/hibernate/resource/jdbc/spi/JdbcSessionContext.java +++ b/hibernate-core/src/main/java/org/hibernate/resource/jdbc/spi/JdbcSessionContext.java @@ -49,4 +49,6 @@ public interface JdbcSessionContext { SessionFactoryImplementor getSessionFactory(); ServiceRegistry getServiceRegistry(); + + boolean isConnectionWarningsResetCanBeSkippedOnClose(); }