From 9ebece05f821baae7c3483f73306148315ba6617 Mon Sep 17 00:00:00 2001 From: Gavin King Date: Sun, 3 Aug 2025 20:37:54 +1000 Subject: [PATCH 01/11] HHH-19653 replace lost test --- .../c3p0/C3P0DefaultIsolationLevelTest.java | 7 +- .../c3p0/C3P0DifferentIsolationLevelTest.java | 7 +- .../c3p0/C3P0EmptyIsolationLevelTest.java | 104 ++++++++++++++++++ 3 files changed, 112 insertions(+), 6 deletions(-) create mode 100644 hibernate-c3p0/src/test/java/org/hibernate/test/c3p0/C3P0EmptyIsolationLevelTest.java diff --git a/hibernate-c3p0/src/test/java/org/hibernate/test/c3p0/C3P0DefaultIsolationLevelTest.java b/hibernate-c3p0/src/test/java/org/hibernate/test/c3p0/C3P0DefaultIsolationLevelTest.java index d23f95e714a4..ab5f7019bfc1 100644 --- a/hibernate-c3p0/src/test/java/org/hibernate/test/c3p0/C3P0DefaultIsolationLevelTest.java +++ b/hibernate-c3p0/src/test/java/org/hibernate/test/c3p0/C3P0DefaultIsolationLevelTest.java @@ -11,7 +11,6 @@ import jakarta.persistence.Id; import org.hibernate.boot.SessionFactoryBuilder; -import org.hibernate.cfg.AvailableSettings; import org.hibernate.dialect.H2Dialect; import org.hibernate.testing.RequiresDialect; @@ -20,6 +19,8 @@ import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; import org.junit.Test; +import static org.hibernate.cfg.JdbcSettings.CONNECTION_PROVIDER; +import static org.hibernate.cfg.JdbcSettings.ISOLATION; import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; @@ -52,8 +53,8 @@ protected Class[] getAnnotatedClasses() { @Override protected void addSettings(Map settings) { connectionProvider = new C3P0ProxyConnectionProvider(); - settings.put( AvailableSettings.CONNECTION_PROVIDER, connectionProvider ); - settings.put( AvailableSettings.ISOLATION, "READ_COMMITTED" ); + settings.put( CONNECTION_PROVIDER, connectionProvider ); + settings.put( ISOLATION, "READ_COMMITTED" ); } @Test diff --git a/hibernate-c3p0/src/test/java/org/hibernate/test/c3p0/C3P0DifferentIsolationLevelTest.java b/hibernate-c3p0/src/test/java/org/hibernate/test/c3p0/C3P0DifferentIsolationLevelTest.java index 4121026650cb..abad38b9a664 100644 --- a/hibernate-c3p0/src/test/java/org/hibernate/test/c3p0/C3P0DifferentIsolationLevelTest.java +++ b/hibernate-c3p0/src/test/java/org/hibernate/test/c3p0/C3P0DifferentIsolationLevelTest.java @@ -11,7 +11,6 @@ import jakarta.persistence.Id; import org.hibernate.boot.SessionFactoryBuilder; -import org.hibernate.cfg.AvailableSettings; import org.hibernate.dialect.H2Dialect; import org.hibernate.testing.RequiresDialect; @@ -20,6 +19,8 @@ import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; import org.junit.Test; +import static org.hibernate.cfg.JdbcSettings.CONNECTION_PROVIDER; +import static org.hibernate.cfg.JdbcSettings.ISOLATION; import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; @@ -52,8 +53,8 @@ protected Class[] getAnnotatedClasses() { @Override protected void addSettings(Map settings) { connectionProvider = new C3P0ProxyConnectionProvider(); - settings.put( AvailableSettings.CONNECTION_PROVIDER, connectionProvider ); - settings.put( AvailableSettings.ISOLATION, "REPEATABLE_READ" ); + settings.put( CONNECTION_PROVIDER, connectionProvider ); + settings.put( ISOLATION, "REPEATABLE_READ" ); } @Test diff --git a/hibernate-c3p0/src/test/java/org/hibernate/test/c3p0/C3P0EmptyIsolationLevelTest.java b/hibernate-c3p0/src/test/java/org/hibernate/test/c3p0/C3P0EmptyIsolationLevelTest.java new file mode 100644 index 000000000000..a7708b6273b7 --- /dev/null +++ b/hibernate-c3p0/src/test/java/org/hibernate/test/c3p0/C3P0EmptyIsolationLevelTest.java @@ -0,0 +1,104 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.test.c3p0; + +import jakarta.persistence.Entity; +import jakarta.persistence.Id; +import org.hibernate.boot.SessionFactoryBuilder; +import org.hibernate.dialect.H2Dialect; +import org.hibernate.testing.RequiresDialect; +import org.hibernate.testing.jdbc.SQLStatementInterceptor; +import org.hibernate.testing.junit4.BaseNonConfigCoreFunctionalTestCase; +import org.hibernate.testing.orm.junit.JiraKey; +import org.junit.Test; + +import java.sql.Connection; +import java.sql.SQLException; +import java.util.Map; + +import static org.hibernate.cfg.JdbcSettings.CONNECTION_PROVIDER; +import static org.hibernate.cfg.JdbcSettings.ISOLATION; +import static org.hibernate.testing.transaction.TransactionUtil.doInHibernate; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; + +/** + * @author Vlad Mihalcea + */ +@JiraKey(value = "HHH-12749") +@RequiresDialect(H2Dialect.class) +public class C3P0EmptyIsolationLevelTest extends + BaseNonConfigCoreFunctionalTestCase { + + private C3P0ProxyConnectionProvider connectionProvider; + private SQLStatementInterceptor sqlStatementInterceptor; + + @Override + protected void configureSessionFactoryBuilder(SessionFactoryBuilder sfb) { + sqlStatementInterceptor = new SQLStatementInterceptor( sfb ); + } + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + Person.class, + }; + } + + @Override + protected void addSettings(Map settings) { + connectionProvider = new C3P0ProxyConnectionProvider(); + settings.put( CONNECTION_PROVIDER, connectionProvider ); + settings.put( ISOLATION, "" ); + } + + @Test + public void testStoredProcedureOutParameter() throws SQLException { + clearSpies(); + + doInHibernate( this::sessionFactory, session -> { + Person person = new Person(); + person.id = 1L; + person.name = "Vlad Mihalcea"; + + session.persist( person ); + } ); + + assertEquals( 1, sqlStatementInterceptor.getSqlQueries().size() ); + assertTrue( sqlStatementInterceptor.getSqlQueries().get( 0 ).toLowerCase().startsWith( "insert into" ) ); + Connection connectionSpy = connectionProvider.getConnectionSpyMap().keySet().iterator().next(); + verify( connectionSpy, never() ).setTransactionIsolation( Connection.TRANSACTION_READ_COMMITTED ); + + clearSpies(); + + doInHibernate( this::sessionFactory, session -> { + Person person = session.find( Person.class, 1L ); + + assertEquals( "Vlad Mihalcea", person.name ); + } ); + + assertEquals( 1, sqlStatementInterceptor.getSqlQueries().size() ); + assertTrue( sqlStatementInterceptor.getSqlQueries().get( 0 ).toLowerCase().startsWith( "select" ) ); + connectionSpy = connectionProvider.getConnectionSpyMap().keySet().iterator().next(); + verify( connectionSpy, never() ).setTransactionIsolation( Connection.TRANSACTION_READ_COMMITTED ); + } + + private void clearSpies() { + sqlStatementInterceptor.getSqlQueries().clear(); + connectionProvider.clear(); + } + + @Entity(name = "Person") + public static class Person { + + @Id + private Long id; + + private String name; + } + +} From 6048fbd3b4378b616005103482317976520c5a6a Mon Sep 17 00:00:00 2001 From: Gavin King Date: Sun, 3 Aug 2025 20:58:20 +1000 Subject: [PATCH 02/11] HHH-19653 more improvements to JDBC connection log info --- .../internal/AgroalConnectionProvider.java | 22 +++++------ .../DatasourceConnectionProviderImpl.java | 38 +++++++------------ 2 files changed, 25 insertions(+), 35 deletions(-) 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 498cfd1affaf..8ea5c4a810ad 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 @@ -166,25 +166,25 @@ public boolean supportsAggressiveRelease() { @Override public DatabaseConnectionInfo getDatabaseConnectionInfo(Dialect dialect) { - final AgroalConnectionPoolConfiguration acpc = - agroalDataSource.getConfiguration().connectionPoolConfiguration(); - final AgroalConnectionFactoryConfiguration acfc = acpc.connectionFactoryConfiguration(); + final var poolConfig = agroalDataSource.getConfiguration().connectionPoolConfiguration(); + final var connectionConfig = poolConfig.connectionFactoryConfiguration(); return new DatabaseConnectionInfoImpl( AgroalConnectionProvider.class, - acfc.jdbcUrl(), + connectionConfig.jdbcUrl(), // Attempt to resolve the driver name from the dialect, // in case it wasn't explicitly set and access to the // database metadata is allowed - acfc.connectionProviderClass() != null - ? acfc.connectionProviderClass().toString() + connectionConfig.connectionProviderClass() != null + ? connectionConfig.connectionProviderClass().toString() : extractDriverNameFromMetadata(), dialect.getVersion(), - Boolean.toString( acfc.autoCommit() ), - acfc.jdbcTransactionIsolation() != null && acfc.jdbcTransactionIsolation().isDefined() - ? toIsolationNiceName( acfc.jdbcTransactionIsolation().level() ) + Boolean.toString( connectionConfig.autoCommit() ), + connectionConfig.jdbcTransactionIsolation() != null + && connectionConfig.jdbcTransactionIsolation().isDefined() + ? toIsolationNiceName( connectionConfig.jdbcTransactionIsolation().level() ) : toIsolationNiceName( getIsolation( agroalDataSource ) ), - acpc.minSize(), - acpc.maxSize(), + poolConfig.minSize(), + poolConfig.maxSize(), getFetchSize( agroalDataSource ) ); } diff --git a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/internal/DatasourceConnectionProviderImpl.java b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/internal/DatasourceConnectionProviderImpl.java index f85299f20b37..9800870edd88 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/internal/DatasourceConnectionProviderImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/internal/DatasourceConnectionProviderImpl.java @@ -157,36 +157,16 @@ public DatabaseConnectionInfo getDatabaseConnectionInfo(Dialect dialect) { @Override public DatabaseConnectionInfo getDatabaseConnectionInfo(Dialect dialect, ExtractedDatabaseMetaData metaData) { - final String url; - final String driver; - final String isolationLevel; - final Integer fetchSize; - if ( metaData != null ) { - url = metaData.getUrl(); - driver = metaData.getDriver(); - isolationLevel = - toIsolationNiceName( metaData.getTransactionIsolation() ) - + " [default " + toIsolationNiceName( metaData.getDefaultTransactionIsolation() ) + "]"; - final int defaultFetchSize = metaData.getDefaultFetchSize(); - fetchSize = defaultFetchSize == -1 ? null : defaultFetchSize; - } - else { - url = null; - driver = null; - isolationLevel = null; - fetchSize = null; - } - return new DatabaseConnectionInfoImpl( DatasourceConnectionProviderImpl.class, - url, - driver, + metaData == null ? null : metaData.getUrl(), + metaData == null ? null : metaData.getDriver(), dialect.getVersion(), null, - isolationLevel, + metaData == null ? null : isolationString( metaData ), null, null, - fetchSize + metaData != null ? fetchSize( metaData ) : null ) { @Override public String toInfoString() { @@ -196,4 +176,14 @@ public String toInfoString() { } }; } + + private static Integer fetchSize(ExtractedDatabaseMetaData metaData) { + final int defaultFetchSize = metaData.getDefaultFetchSize(); + return defaultFetchSize == -1 ? null : defaultFetchSize; + } + + private String isolationString(ExtractedDatabaseMetaData metaData) { + return toIsolationNiceName( metaData.getTransactionIsolation() ) + + " [default " + toIsolationNiceName( metaData.getDefaultTransactionIsolation() ) + "]"; + } } From e8f56df566a76b70269455f1efb3cb06be6cde8f Mon Sep 17 00:00:00 2001 From: Gavin King Date: Sun, 3 Aug 2025 21:17:56 +1000 Subject: [PATCH 03/11] HHH-19653 log schema/catalog --- .../internal/AgroalConnectionProvider.java | 4 ++ .../c3p0/internal/C3P0ConnectionProvider.java | 6 ++ .../internal/DatabaseConnectionInfoImpl.java | 59 ++++++++++++++++++- .../DatasourceConnectionProviderImpl.java | 4 +- .../DriverManagerConnectionProviderImpl.java | 11 +++- ...asedMultiTenantConnectionProviderImpl.java | 2 + .../spi/DatabaseConnectionInfo.java | 12 ++++ .../internal/HikariCPConnectionProvider.java | 2 + 8 files changed, 96 insertions(+), 4 deletions(-) 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 8ea5c4a810ad..c6fc9ff83158 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 @@ -40,8 +40,10 @@ import static java.util.stream.Collectors.toMap; import static org.hibernate.cfg.AgroalSettings.AGROAL_CONFIG_PREFIX; import static org.hibernate.engine.jdbc.connections.internal.ConnectionProviderInitiator.toIsolationNiceName; +import static org.hibernate.engine.jdbc.connections.internal.DatabaseConnectionInfoImpl.getCatalog; import static org.hibernate.engine.jdbc.connections.internal.DatabaseConnectionInfoImpl.getFetchSize; import static org.hibernate.engine.jdbc.connections.internal.DatabaseConnectionInfoImpl.getIsolation; +import static org.hibernate.engine.jdbc.connections.internal.DatabaseConnectionInfoImpl.getSchema; import static org.hibernate.engine.jdbc.env.internal.JdbcEnvironmentInitiator.allowJdbcMetadataAccess; /** @@ -178,6 +180,8 @@ public DatabaseConnectionInfo getDatabaseConnectionInfo(Dialect dialect) { ? connectionConfig.connectionProviderClass().toString() : extractDriverNameFromMetadata(), dialect.getVersion(), + getSchema( agroalDataSource ), + getCatalog( agroalDataSource ), Boolean.toString( connectionConfig.autoCommit() ), connectionConfig.jdbcTransactionIsolation() != null && connectionConfig.jdbcTransactionIsolation().isDefined() diff --git a/hibernate-c3p0/src/main/java/org/hibernate/c3p0/internal/C3P0ConnectionProvider.java b/hibernate-c3p0/src/main/java/org/hibernate/c3p0/internal/C3P0ConnectionProvider.java index a3eb44e2f418..58aa3656749e 100644 --- a/hibernate-c3p0/src/main/java/org/hibernate/c3p0/internal/C3P0ConnectionProvider.java +++ b/hibernate-c3p0/src/main/java/org/hibernate/c3p0/internal/C3P0ConnectionProvider.java @@ -43,8 +43,10 @@ import static org.hibernate.engine.jdbc.connections.internal.ConnectionProviderInitiator.extractSetting; import static org.hibernate.engine.jdbc.connections.internal.ConnectionProviderInitiator.getConnectionProperties; import static org.hibernate.engine.jdbc.connections.internal.ConnectionProviderInitiator.toIsolationNiceName; +import static org.hibernate.engine.jdbc.connections.internal.DatabaseConnectionInfoImpl.getCatalog; import static org.hibernate.engine.jdbc.connections.internal.DatabaseConnectionInfoImpl.getFetchSize; import static org.hibernate.engine.jdbc.connections.internal.DatabaseConnectionInfoImpl.getIsolation; +import static org.hibernate.engine.jdbc.connections.internal.DatabaseConnectionInfoImpl.getSchema; import static org.hibernate.internal.util.config.ConfigurationHelper.getBoolean; import static org.hibernate.internal.util.config.ConfigurationHelper.getInteger; @@ -160,6 +162,8 @@ public void configure(Map properties) { dataSource = createDataSource( jdbcUrl, connectionProps, poolSettings ); final Integer fetchSize = getFetchSize( dataSource ); + final String schema = getSchema( dataSource ); + final String catalog = getCatalog( dataSource ); if ( isolation == null ) { isolation = getIsolation( dataSource ); } @@ -168,6 +172,8 @@ public void configure(Map properties) { jdbcUrl, jdbcDriverClass, dialect.getVersion(), + schema, + catalog, Boolean.toString( autocommit ), isolation == null ? null : toIsolationNiceName( isolation ), requireNonNullElse( getInteger( C3P0_STYLE_MIN_POOL_SIZE.substring( 5 ), poolSettings ), diff --git a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/internal/DatabaseConnectionInfoImpl.java b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/internal/DatabaseConnectionInfoImpl.java index 5ea084c1347a..71b496a4a4f8 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/internal/DatabaseConnectionInfoImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/internal/DatabaseConnectionInfoImpl.java @@ -37,6 +37,8 @@ public class DatabaseConnectionInfoImpl implements DatabaseConnectionInfo { protected final String jdbcUrl; protected final String jdbcDriver; protected final DatabaseVersion dialectVersion; + protected final String schema; + protected final String catalog; protected final String autoCommitMode; protected final String isolationLevel; protected final Integer poolMinSize; @@ -48,6 +50,8 @@ public DatabaseConnectionInfoImpl( String jdbcUrl, String jdbcDriver, DatabaseVersion dialectVersion, + String schema, + String catalog, String autoCommitMode, String isolationLevel, Integer poolMinSize, @@ -57,6 +61,8 @@ public DatabaseConnectionInfoImpl( this.jdbcUrl = nullIfEmpty( jdbcUrl ); this.jdbcDriver = nullIfEmpty( jdbcDriver ); this.dialectVersion = dialectVersion; + this.schema = schema; + this.catalog = catalog; this.autoCommitMode = nullIfEmpty( autoCommitMode ); this.isolationLevel = nullIfEmpty( isolationLevel ); this.poolMinSize = poolMinSize; @@ -70,6 +76,8 @@ public DatabaseConnectionInfoImpl(Map settings, Dialect dialect) determineUrl( settings ), determineDriver( settings ), dialect.getVersion(), + null, + null, determineAutoCommitMode( settings ), determineIsolationLevel( settings ), // No setting for min. pool size @@ -80,7 +88,43 @@ public DatabaseConnectionInfoImpl(Map settings, Dialect dialect) } public DatabaseConnectionInfoImpl(Dialect dialect) { - this( null, null, null, dialect.getVersion(), null, null, null, null, null ); + this( null, null, null, dialect.getVersion(), null, null, null, null, null, null, null ); + } + + public static String getSchema(DataSource dataSource) { + try ( var conn = dataSource.getConnection() ) { + return conn.getSchema(); + } + catch ( SQLException ignored ) { + return null; + } + } + + public static String getCatalog(DataSource dataSource) { + try ( var conn = dataSource.getConnection() ) { + return conn.getCatalog(); + } + catch ( SQLException ignored ) { + return null; + } + } + + static String getSchema(ConnectionCreator creator) { + try ( var conn = creator.createConnection() ) { + return conn.getSchema(); + } + catch ( SQLException ignored ) { + return null; + } + } + + static String getCatalog(ConnectionCreator creator) { + try ( var conn = creator.createConnection() ) { + return conn.getCatalog(); + } + catch ( SQLException ignored ) { + return null; + } } public static Integer getFetchSize(DataSource dataSource) { @@ -163,12 +207,23 @@ public Integer getPoolMaxSize() { return fetchSize; } + @Override + public @Nullable String getSchema() { + return schema; + } + + @Override + public @Nullable String getCatalog() { + return catalog; + } + @Override public String toInfoString() { return """ \tDatabase JDBC URL [%s] \tDatabase driver: %s \tDatabase version: %s + \tDefault catalog/schema: %s/%s \tAutocommit mode: %s \tIsolation level: %s \tJDBC fetch size: %s @@ -179,6 +234,8 @@ public String toInfoString() { handleEmpty( jdbcUrl ), handleEmpty( jdbcDriver ), handleEmpty( dialectVersion ), + handleEmpty( catalog ), + handleEmpty( schema ), handleEmpty( autoCommitMode ), handleEmpty( isolationLevel ), handleEmpty( fetchSize ), diff --git a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/internal/DatasourceConnectionProviderImpl.java b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/internal/DatasourceConnectionProviderImpl.java index 9800870edd88..f01779d9d01e 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/internal/DatasourceConnectionProviderImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/internal/DatasourceConnectionProviderImpl.java @@ -162,6 +162,8 @@ public DatabaseConnectionInfo getDatabaseConnectionInfo(Dialect dialect, Extract metaData == null ? null : metaData.getUrl(), metaData == null ? null : metaData.getDriver(), dialect.getVersion(), + metaData == null ? null : metaData.getConnectionSchemaName(), + metaData == null ? null : metaData.getConnectionCatalogName(), null, metaData == null ? null : isolationString( metaData ), null, @@ -184,6 +186,6 @@ private static Integer fetchSize(ExtractedDatabaseMetaData metaData) { private String isolationString(ExtractedDatabaseMetaData metaData) { return toIsolationNiceName( metaData.getTransactionIsolation() ) - + " [default " + toIsolationNiceName( metaData.getDefaultTransactionIsolation() ) + "]"; + + " [default " + toIsolationNiceName( metaData.getDefaultTransactionIsolation() ) + "]"; } } diff --git a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/internal/DriverManagerConnectionProviderImpl.java b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/internal/DriverManagerConnectionProviderImpl.java index 6097620194fb..e5c974e17967 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/internal/DriverManagerConnectionProviderImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/internal/DriverManagerConnectionProviderImpl.java @@ -36,11 +36,14 @@ import org.hibernate.internal.log.ConnectionInfoLogger; import static org.hibernate.cfg.JdbcSettings.JAKARTA_JDBC_URL; +import static org.hibernate.cfg.JdbcSettings.POOL_SIZE; import static org.hibernate.engine.jdbc.connections.internal.ConnectionProviderInitiator.extractIsolation; import static org.hibernate.engine.jdbc.connections.internal.ConnectionProviderInitiator.getConnectionProperties; import static org.hibernate.engine.jdbc.connections.internal.ConnectionProviderInitiator.toIsolationNiceName; +import static org.hibernate.engine.jdbc.connections.internal.DatabaseConnectionInfoImpl.getCatalog; import static org.hibernate.engine.jdbc.connections.internal.DatabaseConnectionInfoImpl.getFetchSize; import static org.hibernate.engine.jdbc.connections.internal.DatabaseConnectionInfoImpl.getIsolation; +import static org.hibernate.engine.jdbc.connections.internal.DatabaseConnectionInfoImpl.getSchema; import static org.hibernate.internal.util.config.ConfigurationHelper.getBoolean; import static org.hibernate.internal.util.config.ConfigurationHelper.getInt; import static org.hibernate.internal.util.config.ConfigurationHelper.getLong; @@ -92,7 +95,7 @@ public void configure(Map configurationValues) { private PooledConnections buildPool(Map configurationValues, ServiceRegistryImplementor serviceRegistry) { final boolean autoCommit = getBoolean( AvailableSettings.AUTOCOMMIT, configurationValues ); // default to false final int minSize = getInt( MIN_SIZE, configurationValues, 1 ); - final int maxSize = getInt( AvailableSettings.POOL_SIZE, configurationValues, 20 ); + final int maxSize = getInt( POOL_SIZE, configurationValues, 20 ); final int initialSize = getInt( INITIAL_SIZE, configurationValues, minSize ); final ConnectionCreator creator = buildCreator( configurationValues, serviceRegistry ); @@ -163,12 +166,14 @@ private static ConnectionCreator buildCreator( url, driverList, SimpleDatabaseVersion.ZERO_VERSION, + getSchema( connectionCreator ), + getCatalog( connectionCreator ), Boolean.toString( autoCommit ), isolation != null ? toIsolationNiceName( isolation ) : toIsolationNiceName( getIsolation( connectionCreator ) ), getInt( MIN_SIZE, configurationValues, 1 ), - getInt( AvailableSettings.POOL_SIZE, configurationValues, 20 ), + getInt( POOL_SIZE, configurationValues, 20 ), getFetchSize( connectionCreator ) ); @@ -299,6 +304,8 @@ public DatabaseConnectionInfo getDatabaseConnectionInfo(Dialect dialect) { dbInfo.getJdbcUrl(), dbInfo.getJdbcDriver(), dialect.getVersion(), + dbInfo.getSchema(), + dbInfo.getCatalog(), dbInfo.getAutoCommitMode(), dbInfo.getIsolationLevel(), dbInfo.getPoolMinSize(), diff --git a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/spi/DataSourceBasedMultiTenantConnectionProviderImpl.java b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/spi/DataSourceBasedMultiTenantConnectionProviderImpl.java index 788f8f5a906d..a22a9a1565aa 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/spi/DataSourceBasedMultiTenantConnectionProviderImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/spi/DataSourceBasedMultiTenantConnectionProviderImpl.java @@ -126,6 +126,8 @@ public DatabaseConnectionInfo getDatabaseConnectionInfo(Dialect dialect) { null, null, null, + null, + null, null ) { @Override diff --git a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/spi/DatabaseConnectionInfo.java b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/spi/DatabaseConnectionInfo.java index 191a4e964810..18baef4b9634 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/spi/DatabaseConnectionInfo.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/spi/DatabaseConnectionInfo.java @@ -37,6 +37,18 @@ public interface DatabaseConnectionInfo { @Nullable DatabaseVersion getDialectVersion(); + /** + * The default schema + */ + @Nullable + String getSchema(); + + /** + * The default catalog + */ + @Nullable + String getCatalog(); + /** * The transaction auto-commit mode in effect. */ diff --git a/hibernate-hikaricp/src/main/java/org/hibernate/hikaricp/internal/HikariCPConnectionProvider.java b/hibernate-hikaricp/src/main/java/org/hibernate/hikaricp/internal/HikariCPConnectionProvider.java index 8ff2c89a2194..591c603e6b33 100644 --- a/hibernate-hikaricp/src/main/java/org/hibernate/hikaricp/internal/HikariCPConnectionProvider.java +++ b/hibernate-hikaricp/src/main/java/org/hibernate/hikaricp/internal/HikariCPConnectionProvider.java @@ -107,6 +107,8 @@ public DatabaseConnectionInfo getDatabaseConnectionInfo(Dialect dialect) { ? extractDriverNameFromMetadata() : hikariConfig.getDriverClassName(), dialect.getVersion(), + hikariConfig.getSchema(), + hikariConfig.getCatalog(), Boolean.toString( hikariConfig.isAutoCommit() ), hikariConfig.getTransactionIsolation() != null ? hikariConfig.getTransactionIsolation() From 85fe5267d8efc3067945cd584f7a328c717d6c76 Mon Sep 17 00:00:00 2001 From: Gavin King Date: Sun, 3 Aug 2025 21:33:02 +1000 Subject: [PATCH 04/11] HHH-19653 log dialect --- .../internal/AgroalConnectionProvider.java | 3 +-- .../c3p0/internal/C3P0ConnectionProvider.java | 1 + .../internal/DatabaseConnectionInfoImpl.java | 15 ++++++++++----- .../DatasourceConnectionProviderImpl.java | 1 + .../DriverManagerConnectionProviderImpl.java | 18 ++++++++++-------- ...BasedMultiTenantConnectionProviderImpl.java | 1 + .../internal/HikariCPConnectionProvider.java | 1 + 7 files changed, 25 insertions(+), 15 deletions(-) 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 c6fc9ff83158..6e7671c542bc 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 @@ -29,8 +29,6 @@ import org.hibernate.service.spi.Stoppable; import io.agroal.api.AgroalDataSource; -import io.agroal.api.configuration.AgroalConnectionFactoryConfiguration; -import io.agroal.api.configuration.AgroalConnectionPoolConfiguration; import io.agroal.api.configuration.supplier.AgroalConnectionFactoryConfigurationSupplier; import io.agroal.api.configuration.supplier.AgroalPropertiesReader; import io.agroal.api.security.NamePrincipal; @@ -179,6 +177,7 @@ public DatabaseConnectionInfo getDatabaseConnectionInfo(Dialect dialect) { connectionConfig.connectionProviderClass() != null ? connectionConfig.connectionProviderClass().toString() : extractDriverNameFromMetadata(), + dialect.getClass(), dialect.getVersion(), getSchema( agroalDataSource ), getCatalog( agroalDataSource ), diff --git a/hibernate-c3p0/src/main/java/org/hibernate/c3p0/internal/C3P0ConnectionProvider.java b/hibernate-c3p0/src/main/java/org/hibernate/c3p0/internal/C3P0ConnectionProvider.java index 58aa3656749e..6a382973cb39 100644 --- a/hibernate-c3p0/src/main/java/org/hibernate/c3p0/internal/C3P0ConnectionProvider.java +++ b/hibernate-c3p0/src/main/java/org/hibernate/c3p0/internal/C3P0ConnectionProvider.java @@ -171,6 +171,7 @@ public void configure(Map properties) { C3P0ConnectionProvider.class, jdbcUrl, jdbcDriverClass, + dialect.getClass(), dialect.getVersion(), schema, catalog, diff --git a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/internal/DatabaseConnectionInfoImpl.java b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/internal/DatabaseConnectionInfoImpl.java index 71b496a4a4f8..695318dab491 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/internal/DatabaseConnectionInfoImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/internal/DatabaseConnectionInfoImpl.java @@ -36,6 +36,7 @@ public class DatabaseConnectionInfoImpl implements DatabaseConnectionInfo { private final Class connectionProviderClass; protected final String jdbcUrl; protected final String jdbcDriver; + private final Class dialectClass; protected final DatabaseVersion dialectVersion; protected final String schema; protected final String catalog; @@ -49,7 +50,7 @@ public DatabaseConnectionInfoImpl( Class connectionProviderClass, String jdbcUrl, String jdbcDriver, - DatabaseVersion dialectVersion, + Class dialectClass, DatabaseVersion dialectVersion, String schema, String catalog, String autoCommitMode, @@ -68,6 +69,7 @@ public DatabaseConnectionInfoImpl( this.poolMinSize = poolMinSize; this.poolMaxSize = poolMaxSize; this.fetchSize = fetchSize; + this.dialectClass = dialectClass; } public DatabaseConnectionInfoImpl(Map settings, Dialect dialect) { @@ -75,6 +77,7 @@ public DatabaseConnectionInfoImpl(Map settings, Dialect dialect) null, determineUrl( settings ), determineDriver( settings ), + dialect.getClass(), dialect.getVersion(), null, null, @@ -88,7 +91,7 @@ public DatabaseConnectionInfoImpl(Map settings, Dialect dialect) } public DatabaseConnectionInfoImpl(Dialect dialect) { - this( null, null, null, dialect.getVersion(), null, null, null, null, null, null, null ); + this( null, null, null, dialect.getClass(), dialect.getVersion(), null, null, null, null, null, null, null ); } public static String getSchema(DataSource dataSource) { @@ -222,6 +225,7 @@ public String toInfoString() { return """ \tDatabase JDBC URL [%s] \tDatabase driver: %s + \tDatabase dialect: %s \tDatabase version: %s \tDefault catalog/schema: %s/%s \tAutocommit mode: %s @@ -233,12 +237,13 @@ public String toInfoString() { .formatted( handleEmpty( jdbcUrl ), handleEmpty( jdbcDriver ), + handleEmpty( dialectClass ), handleEmpty( dialectVersion ), handleEmpty( catalog ), handleEmpty( schema ), handleEmpty( autoCommitMode ), handleEmpty( isolationLevel ), - handleEmpty( fetchSize ), + handleFetchSize( fetchSize ), handleEmpty( connectionProviderClass ), handleEmpty( poolMinSize ), handleEmpty( poolMaxSize ) @@ -254,11 +259,11 @@ private static String handleEmpty(DatabaseVersion dialectVersion) { } private static String handleEmpty(Integer value) { - return value != null ? ( value == 0 ? "none" : value.toString() ) : DEFAULT; + return value != null ? value.toString() : DEFAULT; } private static String handleFetchSize(Integer value) { - return value != null ? value.toString() : DEFAULT; + return value != null ? ( value == 0 ? "none" : value.toString() ) : DEFAULT; } private static String handleEmpty(Class value) { diff --git a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/internal/DatasourceConnectionProviderImpl.java b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/internal/DatasourceConnectionProviderImpl.java index f01779d9d01e..6fc57927f20c 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/internal/DatasourceConnectionProviderImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/internal/DatasourceConnectionProviderImpl.java @@ -161,6 +161,7 @@ public DatabaseConnectionInfo getDatabaseConnectionInfo(Dialect dialect, Extract DatasourceConnectionProviderImpl.class, metaData == null ? null : metaData.getUrl(), metaData == null ? null : metaData.getDriver(), + dialect.getClass(), dialect.getVersion(), metaData == null ? null : metaData.getConnectionSchemaName(), metaData == null ? null : metaData.getConnectionCatalogName(), diff --git a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/internal/DriverManagerConnectionProviderImpl.java b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/internal/DriverManagerConnectionProviderImpl.java index e5c974e17967..a6f922bfc3d2 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/internal/DriverManagerConnectionProviderImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/internal/DriverManagerConnectionProviderImpl.java @@ -20,7 +20,6 @@ import org.hibernate.HibernateException; import org.hibernate.boot.registry.classloading.spi.ClassLoaderService; -import org.hibernate.cfg.AvailableSettings; import org.hibernate.dialect.Database; import org.hibernate.dialect.Dialect; import org.hibernate.dialect.SimpleDatabaseVersion; @@ -35,8 +34,11 @@ import org.hibernate.service.spi.Stoppable; import org.hibernate.internal.log.ConnectionInfoLogger; +import static org.hibernate.cfg.JdbcSettings.AUTOCOMMIT; +import static org.hibernate.cfg.JdbcSettings.DRIVER; import static org.hibernate.cfg.JdbcSettings.JAKARTA_JDBC_URL; import static org.hibernate.cfg.JdbcSettings.POOL_SIZE; +import static org.hibernate.cfg.JdbcSettings.URL; import static org.hibernate.engine.jdbc.connections.internal.ConnectionProviderInitiator.extractIsolation; import static org.hibernate.engine.jdbc.connections.internal.ConnectionProviderInitiator.getConnectionProperties; import static org.hibernate.engine.jdbc.connections.internal.ConnectionProviderInitiator.toIsolationNiceName; @@ -93,7 +95,7 @@ public void configure(Map configurationValues) { } private PooledConnections buildPool(Map configurationValues, ServiceRegistryImplementor serviceRegistry) { - final boolean autoCommit = getBoolean( AvailableSettings.AUTOCOMMIT, configurationValues ); // default to false + final boolean autoCommit = getBoolean( AUTOCOMMIT, configurationValues ); // default to false final int minSize = getInt( MIN_SIZE, configurationValues, 1 ); final int maxSize = getInt( POOL_SIZE, configurationValues, 20 ); final int initialSize = getInt( INITIAL_SIZE, configurationValues, minSize ); @@ -111,7 +113,7 @@ private static ConnectionCreator buildCreator( Map configurationValues, ServiceRegistryImplementor serviceRegistry) { final String url = jdbcUrl( configurationValues ); - String driverClassName = (String) configurationValues.get( AvailableSettings.DRIVER ); + String driverClassName = (String) configurationValues.get( DRIVER ); boolean success = false; Driver driver = null; if ( driverClassName != null ) { @@ -142,7 +144,7 @@ private static ConnectionCreator buildCreator( final Properties connectionProps = getConnectionProperties( configurationValues ); - final boolean autoCommit = getBoolean( AvailableSettings.AUTOCOMMIT, configurationValues ); + final boolean autoCommit = getBoolean( AUTOCOMMIT, configurationValues ); final Integer isolation = extractIsolation( configurationValues ); final String initSql = (String) configurationValues.get( INIT_SQL ); @@ -159,12 +161,11 @@ private static ConnectionCreator buildCreator( configurationValues ); - ; - dbInfo = new DatabaseConnectionInfoImpl( DriverManagerConnectionProviderImpl.class, url, driverList, + null, SimpleDatabaseVersion.ZERO_VERSION, getSchema( connectionCreator ), getCatalog( connectionCreator ), @@ -195,7 +196,7 @@ private static String driverList() { } private static String jdbcUrl(Map configurationValues) { - final String url = (String) configurationValues.get( AvailableSettings.URL ); + final String url = (String) configurationValues.get( URL ); if ( url == null ) { throw new ConnectionProviderConfigurationException( "No JDBC URL specified by property '" + JAKARTA_JDBC_URL + "'" ); } @@ -303,6 +304,7 @@ public DatabaseConnectionInfo getDatabaseConnectionInfo(Dialect dialect) { DriverManagerConnectionProviderImpl.class, dbInfo.getJdbcUrl(), dbInfo.getJdbcDriver(), + dialect.getClass(), dialect.getVersion(), dbInfo.getSchema(), dbInfo.getCatalog(), @@ -400,7 +402,7 @@ private void validate() { final int size = size(); if ( !primed && size >= minSize ) { - // IMPL NOTE : the purpose of primed is to allow the pool to lazily reach its + // IMPL NOTE: the purpose of primed is to allow the pool to lazily reach its // defined min-size. ConnectionInfoLogger.INSTANCE.debug( "Connection pool now considered primed; min-size will be maintained" ); primed = true; diff --git a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/spi/DataSourceBasedMultiTenantConnectionProviderImpl.java b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/spi/DataSourceBasedMultiTenantConnectionProviderImpl.java index a22a9a1565aa..1e2622edc488 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/spi/DataSourceBasedMultiTenantConnectionProviderImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/spi/DataSourceBasedMultiTenantConnectionProviderImpl.java @@ -121,6 +121,7 @@ public DatabaseConnectionInfo getDatabaseConnectionInfo(Dialect dialect) { null, null, null, + dialect.getClass(), dialect.getVersion(), null, null, diff --git a/hibernate-hikaricp/src/main/java/org/hibernate/hikaricp/internal/HikariCPConnectionProvider.java b/hibernate-hikaricp/src/main/java/org/hibernate/hikaricp/internal/HikariCPConnectionProvider.java index 591c603e6b33..f126934228e1 100644 --- a/hibernate-hikaricp/src/main/java/org/hibernate/hikaricp/internal/HikariCPConnectionProvider.java +++ b/hibernate-hikaricp/src/main/java/org/hibernate/hikaricp/internal/HikariCPConnectionProvider.java @@ -106,6 +106,7 @@ public DatabaseConnectionInfo getDatabaseConnectionInfo(Dialect dialect) { isBlank( hikariConfig.getDriverClassName() ) ? extractDriverNameFromMetadata() : hikariConfig.getDriverClassName(), + dialect.getClass(), dialect.getVersion(), hikariConfig.getSchema(), hikariConfig.getCatalog(), From 8d15eadec6130649f6e006c6798ae096051c67e3 Mon Sep 17 00:00:00 2001 From: Gavin King Date: Sun, 3 Aug 2025 21:45:45 +1000 Subject: [PATCH 05/11] add some more info (currently unused) to the ExtractedDatabaseMetaData --- .../ExtractedDatabaseMetaDataImpl.java | 26 ++++++++++++++++++- .../env/spi/ExtractedDatabaseMetaData.java | 4 +++ .../TestDataSourceConnectionProvider.java | 1 + 3 files changed, 30 insertions(+), 1 deletion(-) diff --git a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/env/internal/ExtractedDatabaseMetaDataImpl.java b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/env/internal/ExtractedDatabaseMetaDataImpl.java index 61a81c6735c4..58217e18460e 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/env/internal/ExtractedDatabaseMetaDataImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/env/internal/ExtractedDatabaseMetaDataImpl.java @@ -37,6 +37,9 @@ public class ExtractedDatabaseMetaDataImpl implements ExtractedDatabaseMetaData private final String connectionCatalogName; private final String connectionSchemaName; + private final String databaseProductName; + private final String databaseProductVersion; + private final boolean supportsRefCursors; private final boolean supportsNamedParameters; private final boolean supportsScrollableResults; @@ -62,6 +65,8 @@ private ExtractedDatabaseMetaDataImpl( JdbcConnectionAccess connectionAccess, String connectionCatalogName, String connectionSchemaName, + String databaseProductName, + String databaseProductVersion, boolean supportsRefCursors, boolean supportsNamedParameters, boolean supportsScrollableResults, @@ -80,6 +85,8 @@ private ExtractedDatabaseMetaDataImpl( this.connectionAccess = connectionAccess; this.connectionCatalogName = connectionCatalogName; this.connectionSchemaName = connectionSchemaName; + this.databaseProductName = databaseProductName; + this.databaseProductVersion = databaseProductVersion; this.supportsRefCursors = supportsRefCursors; this.supportsNamedParameters = supportsNamedParameters; this.supportsScrollableResults = supportsScrollableResults; @@ -151,6 +158,16 @@ public String getConnectionSchemaName() { return connectionSchemaName; } + @Override + public String getDatabaseProductName() { + return databaseProductName; + } + + @Override + public String getDatabaseProductVersion() { + return databaseProductVersion; + } + @Override public String getUrl() { return url; @@ -206,6 +223,9 @@ public static class Builder { private String connectionSchemaName; private String connectionCatalogName; + private String databaseProductName; + private String databaseProductVersion; + private boolean supportsRefCursors; private boolean supportsNamedParameters; private boolean supportsScrollableResults; @@ -229,7 +249,9 @@ public Builder(JdbcEnvironment jdbcEnvironment, boolean jdbcMetadataIsAccessible public Builder apply(DatabaseMetaData databaseMetaData) throws SQLException { connectionCatalogName = databaseMetaData.getConnection().getCatalog(); - // NOTE : databaseMetaData.getConnection().getSchema() would require java 1.7 as baseline + connectionSchemaName = databaseMetaData.getConnection().getSchema(); + databaseProductName = databaseMetaData.getDatabaseProductName(); + databaseProductVersion = databaseMetaData.getDatabaseProductVersion(); supportsRefCursors = StandardRefCursorSupport.supportsRefCursors( databaseMetaData ); supportsNamedParameters = databaseMetaData.supportsNamedParameters(); supportsScrollableResults = databaseMetaData.supportsResultSetType( ResultSet.TYPE_SCROLL_INSENSITIVE ); @@ -307,6 +329,8 @@ public ExtractedDatabaseMetaDataImpl build() { connectionAccess, connectionCatalogName, connectionSchemaName, + databaseProductName, + databaseProductVersion, supportsRefCursors, supportsNamedParameters, supportsScrollableResults, diff --git a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/env/spi/ExtractedDatabaseMetaData.java b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/env/spi/ExtractedDatabaseMetaData.java index dad55f55dce0..c862c1d39324 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/env/spi/ExtractedDatabaseMetaData.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/env/spi/ExtractedDatabaseMetaData.java @@ -25,6 +25,10 @@ public interface ExtractedDatabaseMetaData { */ JdbcEnvironment getJdbcEnvironment(); + String getDatabaseProductName(); + + String getDatabaseProductVersion(); + /** * Retrieve the name of the catalog in effect when we connected to the database. * diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/datasource/TestDataSourceConnectionProvider.java b/hibernate-core/src/test/java/org/hibernate/orm/test/datasource/TestDataSourceConnectionProvider.java index 98b88915898b..c563b1291ff1 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/datasource/TestDataSourceConnectionProvider.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/datasource/TestDataSourceConnectionProvider.java @@ -17,6 +17,7 @@ import java.util.Map; import java.util.logging.Logger; +@SuppressWarnings( "unused" ) // used by DatasourceTest in this package public class TestDataSourceConnectionProvider extends DatasourceConnectionProviderImpl implements ServiceRegistryAwareService { From 4d6361e0ff2079e076d93a2f46b5a47f9eae6b28 Mon Sep 17 00:00:00 2001 From: Gavin King Date: Mon, 4 Aug 2025 10:28:25 +1000 Subject: [PATCH 06/11] HSQLDialect does not seem to need to override supportsNamedParameters() anymore --- .../org/hibernate/community/dialect/HSQLLegacyDialect.java | 5 ----- .../src/main/java/org/hibernate/dialect/HSQLDialect.java | 5 ----- 2 files changed, 10 deletions(-) diff --git a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/HSQLLegacyDialect.java b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/HSQLLegacyDialect.java index 737bf0a109e6..36bc428ee2d8 100644 --- a/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/HSQLLegacyDialect.java +++ b/hibernate-community-dialects/src/main/java/org/hibernate/community/dialect/HSQLLegacyDialect.java @@ -833,11 +833,6 @@ public NameQualifierSupport getNameQualifierSupport() { return NameQualifierSupport.SCHEMA; } - @Override - public boolean supportsNamedParameters(DatabaseMetaData databaseMetaData) { - return false; - } - @Override public FunctionalDependencyAnalysisSupport getFunctionalDependencyAnalysisSupport() { return FunctionalDependencyAnalysisSupportImpl.TABLE_REFERENCE; diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/HSQLDialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/HSQLDialect.java index a97bd8778da0..b30dcdc651c9 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/HSQLDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/HSQLDialect.java @@ -662,11 +662,6 @@ public NameQualifierSupport getNameQualifierSupport() { return NameQualifierSupport.SCHEMA; } - @Override - public boolean supportsNamedParameters(DatabaseMetaData databaseMetaData) { - return false; - } - @Override public FunctionalDependencyAnalysisSupport getFunctionalDependencyAnalysisSupport() { return FunctionalDependencyAnalysisSupportImpl.TABLE_REFERENCE; From 652b43008891ef38a751669be0a96431c7d94231 Mon Sep 17 00:00:00 2001 From: Gavin King Date: Mon, 4 Aug 2025 10:25:51 +1000 Subject: [PATCH 07/11] simplify construction of ExtractedDatabaseMetaData --- .../java/org/hibernate/dialect/Dialect.java | 6 +- .../org/hibernate/dialect/SybaseDialect.java | 5 +- .../ExtractedDatabaseMetaDataImpl.java | 233 +++++------------- .../env/internal/JdbcEnvironmentImpl.java | 112 +++------ .../env/spi/ExtractedDatabaseMetaData.java | 5 +- .../jdbc/env/spi/IdentifierHelperBuilder.java | 89 ++++--- .../engine/jdbc/env/spi/SQLStateType.java | 16 +- .../id/enhanced/SequenceStyleGenerator.java | 3 +- .../test/jdbc/env/NoDatabaseMetaDataTest.java | 7 +- 9 files changed, 150 insertions(+), 326 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/Dialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/Dialect.java index a1186ec8c1fa..3e81ac3fc5a3 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/Dialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/Dialect.java @@ -3694,7 +3694,7 @@ public Set getKeywords() { *
  • Call {@link IdentifierHelperBuilder#applyIdentifierCasing(DatabaseMetaData)} *
  • Call {@link IdentifierHelperBuilder#applyReservedWords(DatabaseMetaData)} *
  • Applies {@link AnsiSqlKeywords#sql2003()} as reserved words
  • - *
  • Applies the {#link #sqlKeywords} collected here as reserved words
  • + *
  • Applies the {@link #sqlKeywords} collected here as reserved words
  • *
  • Applies the Dialect's {@link NameQualifierSupport}, if it defines one
  • * * @@ -4083,7 +4083,7 @@ public String[] getDropSchemaCommand(String schemaName) { * {@link Connection#getSchema()} is always available directly. * Never used internally. */ - @Deprecated + @Deprecated(since = "7.0") public String getCurrentSchemaCommand() { return null; } @@ -4891,7 +4891,7 @@ public boolean addPartitionKeyToPrimaryKey() { * an exception. Just rethrow and Hibernate will * handle it. */ - public boolean supportsNamedParameters(@Nullable DatabaseMetaData databaseMetaData) throws SQLException { + public boolean supportsNamedParameters(DatabaseMetaData databaseMetaData) throws SQLException { return databaseMetaData != null && databaseMetaData.supportsNamedParameters(); } diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/SybaseDialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/SybaseDialect.java index 12d72e9d3f43..d23b2ac11dbc 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/SybaseDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/SybaseDialect.java @@ -65,7 +65,6 @@ import java.sql.Connection; import java.sql.DatabaseMetaData; -import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Types; import java.time.temporal.TemporalAccessor; @@ -605,8 +604,8 @@ public String resolveSchemaName(Connection connection, Dialect dialect) throws S ); } - try (final java.sql.Statement statement = connection.createStatement()) { - try (ResultSet resultSet = statement.executeQuery( command )) { + try ( var statement = connection.createStatement() ) { + try ( var resultSet = statement.executeQuery( command ) ) { return resultSet.next() ? resultSet.getString( 1 ) : null; } } diff --git a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/env/internal/ExtractedDatabaseMetaDataImpl.java b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/env/internal/ExtractedDatabaseMetaDataImpl.java index 58217e18460e..fd82a4f05ac8 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/env/internal/ExtractedDatabaseMetaDataImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/env/internal/ExtractedDatabaseMetaDataImpl.java @@ -6,12 +6,11 @@ import java.sql.Connection; import java.sql.DatabaseMetaData; -import java.sql.ResultSet; import java.sql.SQLException; -import java.sql.Statement; import java.util.List; import org.hibernate.HibernateException; +import org.hibernate.dialect.Dialect; import org.hibernate.engine.jdbc.connections.spi.JdbcConnectionAccess; import org.hibernate.engine.jdbc.cursor.internal.StandardRefCursorSupport; import org.hibernate.engine.jdbc.env.spi.ExtractedDatabaseMetaData; @@ -20,9 +19,11 @@ import org.hibernate.tool.schema.extract.spi.ExtractionContext; import org.hibernate.tool.schema.extract.spi.SequenceInformation; +import static java.sql.ResultSet.TYPE_SCROLL_INSENSITIVE; import static java.util.Collections.emptyList; import static java.util.stream.StreamSupport.stream; import static org.hibernate.engine.jdbc.JdbcLogging.JDBC_MESSAGE_LOGGER; +import static org.hibernate.engine.jdbc.env.spi.SQLStateType.interpretReportedSQLStateType; /** * Standard implementation of {@link ExtractedDatabaseMetaData} @@ -57,50 +58,59 @@ public class ExtractedDatabaseMetaDataImpl implements ExtractedDatabaseMetaData //Lazily initialized: loading all sequence information upfront has been //shown to be too slow in some cases. In this way we only load it - //when there is actual need for these details. + //when there is an actual need for these details. private List sequenceInformationList; - private ExtractedDatabaseMetaDataImpl( - JdbcEnvironment jdbcEnvironment, - JdbcConnectionAccess connectionAccess, - String connectionCatalogName, - String connectionSchemaName, - String databaseProductName, - String databaseProductVersion, - boolean supportsRefCursors, - boolean supportsNamedParameters, - boolean supportsScrollableResults, - boolean supportsGetGeneratedKeys, - boolean supportsBatchUpdates, - boolean supportsDataDefinitionInTransaction, - boolean doesDataDefinitionCauseTransactionCommit, - SQLStateType sqlStateType, - int transactionIsolation, - int defaultTransactionIsolation, - int defaultFetchSize, - String url, - String driver, - boolean jdbcMetadataIsAccessible) { - this.jdbcEnvironment = jdbcEnvironment; - this.connectionAccess = connectionAccess; - this.connectionCatalogName = connectionCatalogName; - this.connectionSchemaName = connectionSchemaName; - this.databaseProductName = databaseProductName; - this.databaseProductVersion = databaseProductVersion; - this.supportsRefCursors = supportsRefCursors; - this.supportsNamedParameters = supportsNamedParameters; - this.supportsScrollableResults = supportsScrollableResults; - this.supportsGetGeneratedKeys = supportsGetGeneratedKeys; - this.supportsBatchUpdates = supportsBatchUpdates; - this.supportsDataDefinitionInTransaction = supportsDataDefinitionInTransaction; - this.doesDataDefinitionCauseTransactionCommit = doesDataDefinitionCauseTransactionCommit; - this.sqlStateType = sqlStateType; - this.transactionIsolation = transactionIsolation; - this.defaultTransactionIsolation = defaultTransactionIsolation; - this.defaultFetchSize = defaultFetchSize; - this.url = url; - this.driver = driver; - this.jdbcMetadataAccessible = jdbcMetadataIsAccessible; + ExtractedDatabaseMetaDataImpl(JdbcEnvironment environment) { + jdbcEnvironment = environment; + connectionAccess = null; + jdbcMetadataAccessible = false; + connectionSchemaName = null; + connectionCatalogName = null; + databaseProductName = null; + databaseProductVersion = null; + supportsRefCursors = false; + supportsNamedParameters = false; + supportsScrollableResults = false; + supportsGetGeneratedKeys = false; + supportsBatchUpdates = true; + supportsDataDefinitionInTransaction = false; + doesDataDefinitionCauseTransactionCommit = false; + sqlStateType = null; + url = null; + driver = null; + defaultTransactionIsolation = 0; + transactionIsolation = 0; + defaultFetchSize = -1; + } + + ExtractedDatabaseMetaDataImpl( + JdbcEnvironment environment, + JdbcConnectionAccess connections, + DatabaseMetaData metaData) + throws SQLException { + jdbcEnvironment = environment; + connectionAccess = connections; + jdbcMetadataAccessible = true; + final Dialect dialect = environment.getDialect(); + final Connection connection = metaData.getConnection(); + connectionSchemaName = dialect.getSchemaNameResolver().resolveSchemaName( connection, dialect ); + connectionCatalogName = connection.getCatalog(); + databaseProductName = metaData.getDatabaseProductName(); + databaseProductVersion = metaData.getDatabaseProductVersion(); + supportsRefCursors = StandardRefCursorSupport.supportsRefCursors( metaData ); + supportsNamedParameters = dialect.supportsNamedParameters( metaData ); + supportsScrollableResults = metaData.supportsResultSetType( TYPE_SCROLL_INSENSITIVE ); + supportsGetGeneratedKeys = metaData.supportsGetGeneratedKeys(); + supportsBatchUpdates = metaData.supportsBatchUpdates(); + supportsDataDefinitionInTransaction = !metaData.dataDefinitionIgnoredInTransactions(); + doesDataDefinitionCauseTransactionCommit = metaData.dataDefinitionCausesTransactionCommit(); + sqlStateType = interpretReportedSQLStateType( metaData.getSQLStateType() ); + url = metaData.getURL(); + driver = metaData.getDriverName(); + defaultTransactionIsolation = metaData.getDefaultTransactionIsolation(); + transactionIsolation = connection.getTransactionIsolation(); + defaultFetchSize = defaultFetchSize( connection ); } @Override @@ -215,137 +225,12 @@ public boolean isJdbcMetadataAccessible() { return jdbcMetadataAccessible; } - public static class Builder { - private final JdbcEnvironment jdbcEnvironment; - private final boolean jdbcMetadataIsAccessible; - private final JdbcConnectionAccess connectionAccess; - - private String connectionSchemaName; - private String connectionCatalogName; - - private String databaseProductName; - private String databaseProductVersion; - - private boolean supportsRefCursors; - private boolean supportsNamedParameters; - private boolean supportsScrollableResults; - private boolean supportsGetGeneratedKeys; - // In absence of DatabaseMetaData batching updates is assumed to be supported - private boolean supportsBatchUpdates = true; - private boolean supportsDataDefinitionInTransaction; - private boolean doesDataDefinitionCauseTransactionCommit; - private SQLStateType sqlStateType; - private String url; - private String driver; - private int defaultTransactionIsolation; - private int transactionIsolation; - private int defaultFetchSize; - - public Builder(JdbcEnvironment jdbcEnvironment, boolean jdbcMetadataIsAccessible, JdbcConnectionAccess connectionAccess) { - this.jdbcEnvironment = jdbcEnvironment; - this.jdbcMetadataIsAccessible = jdbcMetadataIsAccessible; - this.connectionAccess = connectionAccess; - } - - public Builder apply(DatabaseMetaData databaseMetaData) throws SQLException { - connectionCatalogName = databaseMetaData.getConnection().getCatalog(); - connectionSchemaName = databaseMetaData.getConnection().getSchema(); - databaseProductName = databaseMetaData.getDatabaseProductName(); - databaseProductVersion = databaseMetaData.getDatabaseProductVersion(); - supportsRefCursors = StandardRefCursorSupport.supportsRefCursors( databaseMetaData ); - supportsNamedParameters = databaseMetaData.supportsNamedParameters(); - supportsScrollableResults = databaseMetaData.supportsResultSetType( ResultSet.TYPE_SCROLL_INSENSITIVE ); - supportsGetGeneratedKeys = databaseMetaData.supportsGetGeneratedKeys(); - supportsBatchUpdates = databaseMetaData.supportsBatchUpdates(); - supportsDataDefinitionInTransaction = !databaseMetaData.dataDefinitionIgnoredInTransactions(); - doesDataDefinitionCauseTransactionCommit = databaseMetaData.dataDefinitionCausesTransactionCommit(); - sqlStateType = SQLStateType.interpretReportedSQLStateType( databaseMetaData.getSQLStateType() ); - url = databaseMetaData.getURL(); - driver = databaseMetaData.getDriverName(); - defaultTransactionIsolation = databaseMetaData.getDefaultTransactionIsolation(); - transactionIsolation = databaseMetaData.getConnection().getTransactionIsolation(); - try ( Statement statement = databaseMetaData.getConnection().createStatement() ) { - defaultFetchSize = statement.getFetchSize(); - } - catch (SQLException sqle) { - defaultFetchSize = -1; - } - return this; - } - - public Builder setConnectionSchemaName(String connectionSchemaName) { - this.connectionSchemaName = connectionSchemaName; - return this; - } - - public Builder setConnectionCatalogName(String connectionCatalogName) { - this.connectionCatalogName = connectionCatalogName; - return this; - } - - public Builder setSupportsRefCursors(boolean supportsRefCursors) { - this.supportsRefCursors = supportsRefCursors; - return this; - } - - public Builder setSupportsNamedParameters(boolean supportsNamedParameters) { - this.supportsNamedParameters = supportsNamedParameters; - return this; - } - - public Builder setSupportsScrollableResults(boolean supportsScrollableResults) { - this.supportsScrollableResults = supportsScrollableResults; - return this; - } - - public Builder setSupportsGetGeneratedKeys(boolean supportsGetGeneratedKeys) { - this.supportsGetGeneratedKeys = supportsGetGeneratedKeys; - return this; - } - - public Builder setSupportsBatchUpdates(boolean supportsBatchUpdates) { - this.supportsBatchUpdates = supportsBatchUpdates; - return this; + private static int defaultFetchSize(Connection connection) { + try ( var statement = connection.createStatement() ) { + return statement.getFetchSize(); } - - public Builder setSupportsDataDefinitionInTransaction(boolean supportsDataDefinitionInTransaction) { - this.supportsDataDefinitionInTransaction = supportsDataDefinitionInTransaction; - return this; - } - - public Builder setDoesDataDefinitionCauseTransactionCommit(boolean doesDataDefinitionCauseTransactionCommit) { - this.doesDataDefinitionCauseTransactionCommit = doesDataDefinitionCauseTransactionCommit; - return this; - } - - public Builder setSqlStateType(SQLStateType sqlStateType) { - this.sqlStateType = sqlStateType; - return this; - } - - public ExtractedDatabaseMetaDataImpl build() { - return new ExtractedDatabaseMetaDataImpl( - jdbcEnvironment, - connectionAccess, - connectionCatalogName, - connectionSchemaName, - databaseProductName, - databaseProductVersion, - supportsRefCursors, - supportsNamedParameters, - supportsScrollableResults, - supportsGetGeneratedKeys, - supportsBatchUpdates, - supportsDataDefinitionInTransaction, - doesDataDefinitionCauseTransactionCommit, - sqlStateType, - transactionIsolation, - defaultTransactionIsolation, - defaultFetchSize, - url, - driver, - jdbcMetadataIsAccessible - ); + catch (SQLException ignore) { + return -1; } } 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 deb84a7860c4..98f2d3877b4f 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 @@ -8,9 +8,8 @@ import java.sql.SQLException; import org.hibernate.boot.model.naming.Identifier; -import org.hibernate.boot.registry.selector.spi.StrategySelector; -import org.hibernate.cfg.AvailableSettings; import org.hibernate.cfg.JdbcSettings; +import org.hibernate.cfg.MappingSettings; import org.hibernate.dialect.Dialect; import org.hibernate.engine.config.spi.ConfigurationService; import org.hibernate.engine.config.spi.StandardConverters; @@ -23,7 +22,6 @@ import org.hibernate.engine.jdbc.env.spi.LobCreatorBuilder; import org.hibernate.engine.jdbc.env.spi.NameQualifierSupport; import org.hibernate.engine.jdbc.env.spi.QualifiedObjectNameFormatter; -import org.hibernate.engine.jdbc.env.spi.SchemaNameResolver; import org.hibernate.engine.jdbc.spi.SqlExceptionHelper; import org.hibernate.exception.internal.SQLExceptionTypeDelegate; import org.hibernate.exception.internal.SQLStateConversionDelegate; @@ -77,9 +75,9 @@ public JdbcEnvironmentImpl(final ServiceRegistryImplementor serviceRegistry, fin sqlAstTranslatorFactory = resolveSqlAstTranslatorFactory( dialect ); - final ConfigurationService cfgService = configurationService( serviceRegistry ); + final var cfgService = configurationService( serviceRegistry ); - final NameQualifierSupport dialectNameQualifierSupport = dialect.getNameQualifierSupport(); + final var dialectNameQualifierSupport = dialect.getNameQualifierSupport(); nameQualifierSupport = dialectNameQualifierSupport == null ? NameQualifierSupport.BOTH // assume both catalogs and schemas are supported @@ -90,15 +88,9 @@ public JdbcEnvironmentImpl(final ServiceRegistryImplementor serviceRegistry, fin logWarnings( cfgService, dialect ), logErrors( cfgService ) ); - final IdentifierHelperBuilder identifierHelperBuilder = - identifierHelperBuilder( cfgService, nameQualifierSupport ); - - final ExtractedDatabaseMetaDataImpl.Builder metaDataBuilder = - new ExtractedDatabaseMetaDataImpl.Builder( this, false, null ); - - identifierHelper = identifierHelper( dialect, identifierHelperBuilder, metaDataBuilder ); + identifierHelper = identifierHelper( dialect, identifierHelperBuilder( cfgService, nameQualifierSupport ) ); - extractedMetaDataSupport = metaDataBuilder.build(); + extractedMetaDataSupport = new ExtractedDatabaseMetaDataImpl( this ); currentCatalog = identifierHelper.toIdentifier( cfgService.getSetting( DEFAULT_CATALOG, STRING ) ); currentSchema = Identifier.toIdentifier( cfgService.getSetting( DEFAULT_SCHEMA, STRING ) ); @@ -117,7 +109,7 @@ private static ConfigurationService configurationService(ServiceRegistryImplemen private IdentifierHelperBuilder identifierHelperBuilder( ConfigurationService cfgService, NameQualifierSupport nameQualifierSupport) { - final IdentifierHelperBuilder builder = IdentifierHelperBuilder.from( this ); + final var builder = IdentifierHelperBuilder.from( this ); builder.setGloballyQuoteIdentifiers( globalQuoting( cfgService ) ); builder.setSkipGlobalQuotingForColumnDefinitions( globalQuotingSkippedForColumnDefinitions( cfgService ) ); builder.setAutoQuoteKeywords( autoKeywordQuoting( cfgService ) ); @@ -125,15 +117,11 @@ private IdentifierHelperBuilder identifierHelperBuilder( return builder; } - private static IdentifierHelper identifierHelper( - Dialect dialect, - IdentifierHelperBuilder builder, - ExtractedDatabaseMetaDataImpl.Builder dbMetaDataBuilder) { + private static IdentifierHelper identifierHelper(Dialect dialect, IdentifierHelperBuilder builder) { try { - final IdentifierHelper helper = dialect.buildIdentifierHelper( builder, null ); - dbMetaDataBuilder.setSupportsNamedParameters( dialect.supportsNamedParameters( null ) ); - if ( helper != null ) { - return helper; + final var identifierHelper = dialect.buildIdentifierHelper( builder, null ); + if ( identifierHelper != null ) { + return identifierHelper; } } catch (SQLException sqle) { @@ -144,13 +132,13 @@ private static IdentifierHelper identifierHelper( } private static SqlAstTranslatorFactory resolveSqlAstTranslatorFactory(Dialect dialect) { - final SqlAstTranslatorFactory sqlAstTranslatorFactory = dialect.getSqlAstTranslatorFactory(); + final var sqlAstTranslatorFactory = dialect.getSqlAstTranslatorFactory(); return sqlAstTranslatorFactory == null ? new StandardSqlAstTranslatorFactory() : sqlAstTranslatorFactory; } private static boolean logWarnings(ConfigurationService cfgService, Dialect dialect) { return cfgService.getSetting( - AvailableSettings.LOG_JDBC_WARNINGS, + JdbcSettings.LOG_JDBC_WARNINGS, StandardConverters.BOOLEAN, dialect.isJdbcLogWarningsEnabledByDefault() ); @@ -158,7 +146,7 @@ private static boolean logWarnings(ConfigurationService cfgService, Dialect dial private static boolean logErrors(ConfigurationService cfgService) { return cfgService.getSetting( - AvailableSettings.LOG_JDBC_ERRORS, + JdbcSettings.LOG_JDBC_ERRORS, StandardConverters.BOOLEAN, true ); @@ -166,15 +154,15 @@ private static boolean logErrors(ConfigurationService cfgService) { private static boolean globalQuoting(ConfigurationService cfgService) { return cfgService.getSetting( - AvailableSettings.GLOBALLY_QUOTED_IDENTIFIERS, + MappingSettings.GLOBALLY_QUOTED_IDENTIFIERS, StandardConverters.BOOLEAN, false ); } - private boolean globalQuotingSkippedForColumnDefinitions(ConfigurationService cfgService) { + private static boolean globalQuotingSkippedForColumnDefinitions(ConfigurationService cfgService) { return cfgService.getSetting( - AvailableSettings.GLOBALLY_QUOTED_IDENTIFIERS_SKIP_COLUMN_DEFINITIONS, + MappingSettings.GLOBALLY_QUOTED_IDENTIFIERS_SKIP_COLUMN_DEFINITIONS, StandardConverters.BOOLEAN, false ); @@ -182,7 +170,7 @@ private boolean globalQuotingSkippedForColumnDefinitions(ConfigurationService cf private static boolean autoKeywordQuoting(ConfigurationService cfgService) { return cfgService.getSetting( - AvailableSettings.KEYWORD_AUTO_QUOTING_ENABLED, + MappingSettings.KEYWORD_AUTO_QUOTING_ENABLED, StandardConverters.BOOLEAN, false ); @@ -206,10 +194,7 @@ public JdbcEnvironmentImpl( identifierHelper = identifierHelper( databaseMetaData, dialect ); extractedMetaDataSupport = - new ExtractedDatabaseMetaDataImpl.Builder( this, true, jdbcConnectionAccess ) - .apply( databaseMetaData ) - .setSupportsNamedParameters( databaseMetaData.supportsNamedParameters() ) - .build(); + new ExtractedDatabaseMetaDataImpl( this, jdbcConnectionAccess, databaseMetaData ); currentCatalog = null; currentSchema = null; @@ -221,11 +206,10 @@ public JdbcEnvironmentImpl( } private IdentifierHelper identifierHelper(DatabaseMetaData databaseMetaData, Dialect dialect) { - final IdentifierHelperBuilder identifierHelperBuilder = IdentifierHelperBuilder.from( this ); + final var identifierHelperBuilder = IdentifierHelperBuilder.from( this ); identifierHelperBuilder.setNameQualifierSupport( nameQualifierSupport ); try { - final IdentifierHelper identifierHelper = - dialect.buildIdentifierHelper( identifierHelperBuilder, databaseMetaData ); + final var identifierHelper = dialect.buildIdentifierHelper( identifierHelperBuilder, databaseMetaData ); if ( identifierHelper != null ) { return identifierHelper; } @@ -239,7 +223,7 @@ private IdentifierHelper identifierHelper(DatabaseMetaData databaseMetaData, Dia private NameQualifierSupport nameQualifierSupport(DatabaseMetaData databaseMetaData, Dialect dialect) throws SQLException { - final NameQualifierSupport nameQualifierSupport = dialect.getNameQualifierSupport(); + final var nameQualifierSupport = dialect.getNameQualifierSupport(); return nameQualifierSupport == null ? determineNameQualifierSupport( databaseMetaData ) : nameQualifierSupport; } @@ -302,16 +286,12 @@ public JdbcEnvironmentImpl( nameQualifierSupport = nameQualifierSupport( databaseMetaData, dialect ); - final IdentifierHelperBuilder identifierHelperBuilder = - identifierHelperBuilder( cfgService, nameQualifierSupport ); - identifierHelper = identifierHelper( dialect, databaseMetaData, identifierHelperBuilder ); + identifierHelper = + identifierHelper( dialect, databaseMetaData, + identifierHelperBuilder( cfgService, nameQualifierSupport ) ); extractedMetaDataSupport = - new ExtractedDatabaseMetaDataImpl.Builder( this, true, jdbcConnectionAccess ) - .apply( databaseMetaData ) - .setConnectionSchemaName( determineCurrentSchemaName( databaseMetaData, serviceRegistry, dialect ) ) - .setSupportsNamedParameters( dialect.supportsNamedParameters( databaseMetaData ) ) - .build(); + new ExtractedDatabaseMetaDataImpl( this, jdbcConnectionAccess, databaseMetaData ); // and that current-catalog and current-schema happen after it currentCatalog = identifierHelper.toIdentifier( extractedMetaDataSupport.getConnectionCatalogName() ); @@ -325,11 +305,12 @@ public JdbcEnvironmentImpl( logJdbcFetchSize( extractedMetaDataSupport.getDefaultFetchSize(), cfgService ); } - private static IdentifierHelper identifierHelper( - Dialect dialect, DatabaseMetaData databaseMetaData, IdentifierHelperBuilder identifierHelperBuilder) { + private IdentifierHelper identifierHelper( + Dialect dialect, + DatabaseMetaData databaseMetaData, + IdentifierHelperBuilder builder) { try { - final IdentifierHelper identifierHelper = - dialect.buildIdentifierHelper( identifierHelperBuilder, databaseMetaData ); + final var identifierHelper = dialect.buildIdentifierHelper( builder, databaseMetaData ); if ( identifierHelper != null ) { return identifierHelper; } @@ -338,39 +319,12 @@ private static IdentifierHelper identifierHelper( // should never ever happen log.debug( "There was a problem accessing DatabaseMetaData in building the JdbcEnvironment", sqle ); } - return identifierHelperBuilder.build(); - } - - public static final String SCHEMA_NAME_RESOLVER = "hibernate.schema_name_resolver"; - - private String determineCurrentSchemaName( - DatabaseMetaData databaseMetaData, - ServiceRegistry serviceRegistry, - Dialect dialect) { - final SchemaNameResolver resolver = getSchemaNameResolver( serviceRegistry, dialect ); - try { - return resolver.resolveSchemaName( databaseMetaData.getConnection(), dialect ); - } - catch (Exception e) { - log.debug( "Unable to resolve connection default schema", e ); - return null; - } - } - - private static SchemaNameResolver getSchemaNameResolver(ServiceRegistry serviceRegistry, Dialect dialect) { - final Object setting = - serviceRegistry.requireService( ConfigurationService.class ) - .getSettings().get( SCHEMA_NAME_RESOLVER ); - return setting == null - ? dialect.getSchemaNameResolver() - : serviceRegistry.requireService( StrategySelector.class ) - .resolveDefaultableStrategy( SchemaNameResolver.class, setting, - dialect.getSchemaNameResolver() ); + return builder.build(); } private static SqlExceptionHelper buildSqlExceptionHelper(Dialect dialect, boolean logWarnings, boolean logErrors) { - final SQLExceptionConversionDelegate dialectDelegate = dialect.buildSQLExceptionConversionDelegate(); - final SQLExceptionConversionDelegate[] delegates = dialectDelegate == null + final var dialectDelegate = dialect.buildSQLExceptionConversionDelegate(); + final var delegates = dialectDelegate == null ? new SQLExceptionConversionDelegate[] { new SQLExceptionTypeDelegate( dialect ), new SQLStateConversionDelegate( dialect ) } : new SQLExceptionConversionDelegate[] { dialectDelegate, new SQLExceptionTypeDelegate( dialect ), new SQLStateConversionDelegate( dialect ) }; return new SqlExceptionHelper( new StandardSQLExceptionConverter( delegates ), logWarnings, logErrors ); diff --git a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/env/spi/ExtractedDatabaseMetaData.java b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/env/spi/ExtractedDatabaseMetaData.java index c862c1d39324..9c3a57c529f5 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/env/spi/ExtractedDatabaseMetaData.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/env/spi/ExtractedDatabaseMetaData.java @@ -4,12 +4,13 @@ */ package org.hibernate.engine.jdbc.env.spi; -import java.util.Collections; import java.util.List; import org.hibernate.cfg.AvailableSettings; import org.hibernate.tool.schema.extract.spi.SequenceInformation; +import static java.util.Collections.emptyList; + /** * Information extracted from {@link java.sql.DatabaseMetaData} regarding what the JDBC driver reports as * being supported or not. Obviously {@link java.sql.DatabaseMetaData} reports many things, these are a few in @@ -165,6 +166,6 @@ public interface ExtractedDatabaseMetaData { * @return {@code SequenceInformation} objects. */ default List getSequenceInformationList() { - return Collections.emptyList(); + return emptyList(); } } diff --git a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/env/spi/IdentifierHelperBuilder.java b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/env/spi/IdentifierHelperBuilder.java index ba2d9f2b5d99..30cb887f34a5 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/env/spi/IdentifierHelperBuilder.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/env/spi/IdentifierHelperBuilder.java @@ -69,60 +69,57 @@ public void applyReservedWords(DatabaseMetaData metaData) throws SQLException { } public void applyIdentifierCasing(DatabaseMetaData metaData) throws SQLException { - if ( metaData == null ) { - return; - } - - final int unquotedAffirmatives = ArrayHelper.countTrue( - metaData.storesLowerCaseIdentifiers(), - metaData.storesUpperCaseIdentifiers(), - metaData.storesMixedCaseIdentifiers() - ); - - if ( unquotedAffirmatives == 0 ) { - log.trace( "JDBC driver metadata reported database stores unquoted identifiers in neither upper, lower nor mixed case" ); - } - else { - // NOTE : still "dodgy" if more than one is true - if ( unquotedAffirmatives > 1 ) { - log.trace( "JDBC driver metadata reported database stores unquoted identifiers in more than one case" ); - } + if ( metaData != null ) { + final int unquotedAffirmatives = ArrayHelper.countTrue( + metaData.storesLowerCaseIdentifiers(), + metaData.storesUpperCaseIdentifiers(), + metaData.storesMixedCaseIdentifiers() + ); - if ( metaData.storesUpperCaseIdentifiers() ) { - unquotedCaseStrategy = IdentifierCaseStrategy.UPPER; - } - else if ( metaData.storesLowerCaseIdentifiers() ) { - unquotedCaseStrategy = IdentifierCaseStrategy.LOWER; + if ( unquotedAffirmatives == 0 ) { + log.trace( "JDBC driver metadata reported database stores unquoted identifiers in neither upper, lower nor mixed case" ); } else { - unquotedCaseStrategy = IdentifierCaseStrategy.MIXED; + // NOTE: still "dodgy" if more than one is true + if ( unquotedAffirmatives > 1 ) { + log.trace( "JDBC driver metadata reported database stores unquoted identifiers in more than one case" ); + } + + if ( metaData.storesUpperCaseIdentifiers() ) { + unquotedCaseStrategy = IdentifierCaseStrategy.UPPER; + } + else if ( metaData.storesLowerCaseIdentifiers() ) { + unquotedCaseStrategy = IdentifierCaseStrategy.LOWER; + } + else { + unquotedCaseStrategy = IdentifierCaseStrategy.MIXED; + } } - } - - - final int quotedAffirmatives = ArrayHelper.countTrue( - metaData.storesLowerCaseQuotedIdentifiers(), - metaData.storesUpperCaseQuotedIdentifiers(), - metaData.storesMixedCaseQuotedIdentifiers() - ); - if ( quotedAffirmatives == 0 ) { - log.trace( "JDBC driver metadata reported database stores quoted identifiers in neither upper, lower nor mixed case" ); - } - else { - // NOTE : still "dodgy" if more than one is true - if ( quotedAffirmatives > 1 ) { - log.trace( "JDBC driver metadata reported database stores quoted identifiers in more than one case" ); - } + final int quotedAffirmatives = ArrayHelper.countTrue( + metaData.storesLowerCaseQuotedIdentifiers(), + metaData.storesUpperCaseQuotedIdentifiers(), + metaData.storesMixedCaseQuotedIdentifiers() + ); - if ( metaData.storesMixedCaseQuotedIdentifiers() ) { - quotedCaseStrategy = IdentifierCaseStrategy.MIXED; - } - else if ( metaData.storesLowerCaseQuotedIdentifiers() ) { - quotedCaseStrategy = IdentifierCaseStrategy.LOWER; + if ( quotedAffirmatives == 0 ) { + log.trace( "JDBC driver metadata reported database stores quoted identifiers in neither upper, lower nor mixed case" ); } else { - quotedCaseStrategy = IdentifierCaseStrategy.UPPER; + // NOTE: still "dodgy" if more than one is true + if ( quotedAffirmatives > 1 ) { + log.trace( "JDBC driver metadata reported database stores quoted identifiers in more than one case" ); + } + + if ( metaData.storesMixedCaseQuotedIdentifiers() ) { + quotedCaseStrategy = IdentifierCaseStrategy.MIXED; + } + else if ( metaData.storesLowerCaseQuotedIdentifiers() ) { + quotedCaseStrategy = IdentifierCaseStrategy.LOWER; + } + else { + quotedCaseStrategy = IdentifierCaseStrategy.UPPER; + } } } } diff --git a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/env/spi/SQLStateType.java b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/env/spi/SQLStateType.java index 0cb491fab327..49e11a26647b 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/env/spi/SQLStateType.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/env/spi/SQLStateType.java @@ -27,16 +27,10 @@ public enum SQLStateType { public static SQLStateType interpretReportedSQLStateType(int sqlStateType) { - switch ( sqlStateType ) { - case DatabaseMetaData.sqlStateSQL99 : { - return SQL99; - } - case DatabaseMetaData.sqlStateXOpen : { - return XOpen; - } - default : { - return UNKNOWN; - } - } + return switch ( sqlStateType ) { + case DatabaseMetaData.sqlStateSQL99 -> SQL99; + case DatabaseMetaData.sqlStateXOpen -> XOpen; + default -> UNKNOWN; + }; } } diff --git a/hibernate-core/src/main/java/org/hibernate/id/enhanced/SequenceStyleGenerator.java b/hibernate-core/src/main/java/org/hibernate/id/enhanced/SequenceStyleGenerator.java index c2503b174d8e..037a8e504281 100644 --- a/hibernate-core/src/main/java/org/hibernate/id/enhanced/SequenceStyleGenerator.java +++ b/hibernate-core/src/main/java/org/hibernate/id/enhanced/SequenceStyleGenerator.java @@ -556,8 +556,7 @@ public String determineBulkInsertionIdentifierGenerationSelectFragment(SqlString * @return sequence increment value */ private Number getSequenceIncrementValue(JdbcEnvironment jdbcEnvironment, String sequenceName) { - for ( SequenceInformation information : - jdbcEnvironment.getExtractedDatabaseMetaData().getSequenceInformationList() ) { + for ( var information : jdbcEnvironment.getExtractedDatabaseMetaData().getSequenceInformationList() ) { final QualifiedSequenceName name = information.getSequenceName(); if ( sequenceName.equalsIgnoreCase( name.getSequenceName().getText() ) && isDefaultSchema( jdbcEnvironment, name.getCatalogName(), name.getSchemaName() ) ) { diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/jdbc/env/NoDatabaseMetaDataTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/jdbc/env/NoDatabaseMetaDataTest.java index 150f07bd7fb1..134935a04fa7 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/jdbc/env/NoDatabaseMetaDataTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/jdbc/env/NoDatabaseMetaDataTest.java @@ -63,7 +63,7 @@ public void testNoJdbcMetadataDialectOverride() { assertNull( extractedDatabaseMetaData.getConnectionCatalogName() ); assertNull( extractedDatabaseMetaData.getConnectionSchemaName() ); - assertTrue( extractedDatabaseMetaData.supportsNamedParameters() ); + assertFalse( extractedDatabaseMetaData.supportsNamedParameters() ); assertFalse( extractedDatabaseMetaData.supportsRefCursors() ); assertFalse( extractedDatabaseMetaData.supportsScrollableResults() ); assertFalse( extractedDatabaseMetaData.supportsGetGeneratedKeys() ); @@ -76,11 +76,6 @@ public void testNoJdbcMetadataDialectOverride() { } public static class TestDialect extends Dialect { - @Override - public boolean supportsNamedParameters(java.sql.DatabaseMetaData databaseMetaData) { - return true; - } - @Override public DatabaseVersion getVersion() { return ZERO_VERSION; From 92dc477a6e4a91a7e75819b9016d1afe4a7153ef Mon Sep 17 00:00:00 2001 From: Gavin King Date: Mon, 4 Aug 2025 11:46:51 +1000 Subject: [PATCH 08/11] improve justification in Javadoc --- .../src/main/java/org/hibernate/dialect/MySQLDialect.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/MySQLDialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/MySQLDialect.java index 2ccd40a23ecf..c6c354fe0f95 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/MySQLDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/MySQLDialect.java @@ -1074,7 +1074,10 @@ public String[] getDropCatalogCommand(String catalogName) { * MySQL does support the {@code create schema} command, but * it's a synonym for {@code create database}. Hibernate has * always treated a MySQL database as a - * {@linkplain #canCreateCatalog catalog}. + * {@linkplain #canCreateCatalog catalog}, which is consistent + * with how the JDBC driver itself views the situation, since + * {@link DatabaseMetaData#supportsSchemasInDataManipulation()} + * returns {@code false} on MySQL. * * @return {@code false} */ From 9730ce4579a72c33804209ad3c5f05157394899b Mon Sep 17 00:00:00 2001 From: Gavin King Date: Mon, 4 Aug 2025 11:46:32 +1000 Subject: [PATCH 09/11] improve rendering of catalog/schema log message use information about whether the database actually supports schemas --- .../internal/AgroalConnectionProvider.java | 4 + .../c3p0/internal/C3P0ConnectionProvider.java | 6 ++ .../internal/DatabaseConnectionInfoImpl.java | 85 +++++++++++++++++-- .../DatasourceConnectionProviderImpl.java | 2 + .../DriverManagerConnectionProviderImpl.java | 6 ++ ...asedMultiTenantConnectionProviderImpl.java | 2 + .../spi/DatabaseConnectionInfo.java | 7 ++ .../ExtractedDatabaseMetaDataImpl.java | 16 ++++ .../env/spi/ExtractedDatabaseMetaData.java | 29 ++++++- .../internal/HikariCPConnectionProvider.java | 4 + 10 files changed, 154 insertions(+), 7 deletions(-) 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 6e7671c542bc..1295af86b18a 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 @@ -42,6 +42,8 @@ import static org.hibernate.engine.jdbc.connections.internal.DatabaseConnectionInfoImpl.getFetchSize; import static org.hibernate.engine.jdbc.connections.internal.DatabaseConnectionInfoImpl.getIsolation; import static org.hibernate.engine.jdbc.connections.internal.DatabaseConnectionInfoImpl.getSchema; +import static org.hibernate.engine.jdbc.connections.internal.DatabaseConnectionInfoImpl.hasCatalog; +import static org.hibernate.engine.jdbc.connections.internal.DatabaseConnectionInfoImpl.hasSchema; import static org.hibernate.engine.jdbc.env.internal.JdbcEnvironmentInitiator.allowJdbcMetadataAccess; /** @@ -179,6 +181,8 @@ public DatabaseConnectionInfo getDatabaseConnectionInfo(Dialect dialect) { : extractDriverNameFromMetadata(), dialect.getClass(), dialect.getVersion(), + hasSchema( agroalDataSource), + hasCatalog( agroalDataSource), getSchema( agroalDataSource ), getCatalog( agroalDataSource ), Boolean.toString( connectionConfig.autoCommit() ), diff --git a/hibernate-c3p0/src/main/java/org/hibernate/c3p0/internal/C3P0ConnectionProvider.java b/hibernate-c3p0/src/main/java/org/hibernate/c3p0/internal/C3P0ConnectionProvider.java index 6a382973cb39..1b22775b11a4 100644 --- a/hibernate-c3p0/src/main/java/org/hibernate/c3p0/internal/C3P0ConnectionProvider.java +++ b/hibernate-c3p0/src/main/java/org/hibernate/c3p0/internal/C3P0ConnectionProvider.java @@ -47,6 +47,8 @@ import static org.hibernate.engine.jdbc.connections.internal.DatabaseConnectionInfoImpl.getFetchSize; import static org.hibernate.engine.jdbc.connections.internal.DatabaseConnectionInfoImpl.getIsolation; import static org.hibernate.engine.jdbc.connections.internal.DatabaseConnectionInfoImpl.getSchema; +import static org.hibernate.engine.jdbc.connections.internal.DatabaseConnectionInfoImpl.hasCatalog; +import static org.hibernate.engine.jdbc.connections.internal.DatabaseConnectionInfoImpl.hasSchema; import static org.hibernate.internal.util.config.ConfigurationHelper.getBoolean; import static org.hibernate.internal.util.config.ConfigurationHelper.getInteger; @@ -162,6 +164,8 @@ public void configure(Map properties) { dataSource = createDataSource( jdbcUrl, connectionProps, poolSettings ); final Integer fetchSize = getFetchSize( dataSource ); + final boolean hasSchema = hasSchema( dataSource ); + final boolean hasCatalog = hasCatalog( dataSource ); final String schema = getSchema( dataSource ); final String catalog = getCatalog( dataSource ); if ( isolation == null ) { @@ -173,6 +177,8 @@ public void configure(Map properties) { jdbcDriverClass, dialect.getClass(), dialect.getVersion(), + hasSchema, + hasCatalog, schema, catalog, Boolean.toString( autocommit ), diff --git a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/internal/DatabaseConnectionInfoImpl.java b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/internal/DatabaseConnectionInfoImpl.java index 695318dab491..bc5789043b81 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/internal/DatabaseConnectionInfoImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/internal/DatabaseConnectionInfoImpl.java @@ -38,6 +38,8 @@ public class DatabaseConnectionInfoImpl implements DatabaseConnectionInfo { protected final String jdbcDriver; private final Class dialectClass; protected final DatabaseVersion dialectVersion; + private final boolean hasSchema; + private final boolean hasCatalog; protected final String schema; protected final String catalog; protected final String autoCommitMode; @@ -51,6 +53,8 @@ public DatabaseConnectionInfoImpl( String jdbcUrl, String jdbcDriver, Class dialectClass, DatabaseVersion dialectVersion, + boolean hasSchema, + boolean hasCatalog, String schema, String catalog, String autoCommitMode, @@ -62,6 +66,8 @@ public DatabaseConnectionInfoImpl( this.jdbcUrl = nullIfEmpty( jdbcUrl ); this.jdbcDriver = nullIfEmpty( jdbcDriver ); this.dialectVersion = dialectVersion; + this.hasSchema = hasSchema; + this.hasCatalog = hasCatalog; this.schema = schema; this.catalog = catalog; this.autoCommitMode = nullIfEmpty( autoCommitMode ); @@ -79,6 +85,8 @@ public DatabaseConnectionInfoImpl(Map settings, Dialect dialect) determineDriver( settings ), dialect.getClass(), dialect.getVersion(), + true, + true, null, null, determineAutoCommitMode( settings ), @@ -91,14 +99,48 @@ public DatabaseConnectionInfoImpl(Map settings, Dialect dialect) } public DatabaseConnectionInfoImpl(Dialect dialect) { - this( null, null, null, dialect.getClass(), dialect.getVersion(), null, null, null, null, null, null, null ); + this( + null, + null, + null, + dialect.getClass(), + dialect.getVersion(), + true, + true, + null, + null, + null, + null, + null, + null, + null + ); + } + + public static boolean hasSchema(DataSource dataSource) { + try ( var conn = dataSource.getConnection() ) { + return conn.getMetaData().supportsSchemasInDataManipulation(); + } + catch ( SQLException ignored ) { + return true; + } + } + + public static boolean hasCatalog(DataSource dataSource) { + try ( var conn = dataSource.getConnection() ) { + return conn.getMetaData().supportsCatalogsInDataManipulation(); + } + catch ( SQLException ignored ) { + return true; + } } public static String getSchema(DataSource dataSource) { try ( var conn = dataSource.getConnection() ) { + // method introduced in 1.7 return conn.getSchema(); } - catch ( SQLException ignored ) { + catch ( SQLException|AbstractMethodError ignored ) { return null; } } @@ -112,11 +154,30 @@ public static String getCatalog(DataSource dataSource) { } } + static boolean hasSchema(ConnectionCreator creator) { + try ( var conn = creator.createConnection() ) { + return conn.getMetaData().supportsSchemasInTableDefinitions(); + } + catch ( SQLException ignored ) { + return true; + } + } + + static boolean hasCatalog(ConnectionCreator creator) { + try ( var conn = creator.createConnection() ) { + return conn.getMetaData().supportsCatalogsInDataManipulation(); + } + catch ( SQLException ignored ) { + return true; + } + } + static String getSchema(ConnectionCreator creator) { try ( var conn = creator.createConnection() ) { + // method introduced in 1.7 return conn.getSchema(); } - catch ( SQLException ignored ) { + catch ( SQLException|AbstractMethodError ignored ) { return null; } } @@ -220,6 +281,16 @@ public Integer getPoolMaxSize() { return catalog; } + @Override + public boolean hasSchema() { + return hasSchema; + } + + @Override + public boolean hasCatalog() { + return hasCatalog; + } + @Override public String toInfoString() { return """ @@ -239,8 +310,8 @@ public String toInfoString() { handleEmpty( jdbcDriver ), handleEmpty( dialectClass ), handleEmpty( dialectVersion ), - handleEmpty( catalog ), - handleEmpty( schema ), + handleEmpty( catalog, hasCatalog ), + handleEmpty( schema, hasSchema ), handleEmpty( autoCommitMode ), handleEmpty( isolationLevel ), handleFetchSize( fetchSize ), @@ -254,6 +325,10 @@ private static String handleEmpty(String value) { return isNotEmpty( value ) ? value : DEFAULT; } + private static String handleEmpty(String value, boolean defined) { + return isNotEmpty( value ) ? value : (defined ? "unknown" : "undefined"); + } + private static String handleEmpty(DatabaseVersion dialectVersion) { return dialectVersion != null ? dialectVersion.toString() : ZERO_VERSION.toString(); } diff --git a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/internal/DatasourceConnectionProviderImpl.java b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/internal/DatasourceConnectionProviderImpl.java index 6fc57927f20c..d08a465cd607 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/internal/DatasourceConnectionProviderImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/internal/DatasourceConnectionProviderImpl.java @@ -163,6 +163,8 @@ public DatabaseConnectionInfo getDatabaseConnectionInfo(Dialect dialect, Extract metaData == null ? null : metaData.getDriver(), dialect.getClass(), dialect.getVersion(), + metaData == null || metaData.supportsSchemas(), + metaData == null || metaData.supportsCatalogs(), metaData == null ? null : metaData.getConnectionSchemaName(), metaData == null ? null : metaData.getConnectionCatalogName(), null, diff --git a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/internal/DriverManagerConnectionProviderImpl.java b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/internal/DriverManagerConnectionProviderImpl.java index a6f922bfc3d2..dc7f467a4da9 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/internal/DriverManagerConnectionProviderImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/internal/DriverManagerConnectionProviderImpl.java @@ -46,6 +46,8 @@ import static org.hibernate.engine.jdbc.connections.internal.DatabaseConnectionInfoImpl.getFetchSize; import static org.hibernate.engine.jdbc.connections.internal.DatabaseConnectionInfoImpl.getIsolation; import static org.hibernate.engine.jdbc.connections.internal.DatabaseConnectionInfoImpl.getSchema; +import static org.hibernate.engine.jdbc.connections.internal.DatabaseConnectionInfoImpl.hasCatalog; +import static org.hibernate.engine.jdbc.connections.internal.DatabaseConnectionInfoImpl.hasSchema; import static org.hibernate.internal.util.config.ConfigurationHelper.getBoolean; import static org.hibernate.internal.util.config.ConfigurationHelper.getInt; import static org.hibernate.internal.util.config.ConfigurationHelper.getLong; @@ -167,6 +169,8 @@ private static ConnectionCreator buildCreator( driverList, null, SimpleDatabaseVersion.ZERO_VERSION, + hasSchema( connectionCreator ), + hasCatalog( connectionCreator ), getSchema( connectionCreator ), getCatalog( connectionCreator ), Boolean.toString( autoCommit ), @@ -306,6 +310,8 @@ public DatabaseConnectionInfo getDatabaseConnectionInfo(Dialect dialect) { dbInfo.getJdbcDriver(), dialect.getClass(), dialect.getVersion(), + dbInfo.hasSchema(), + dbInfo.hasCatalog(), dbInfo.getSchema(), dbInfo.getCatalog(), dbInfo.getAutoCommitMode(), diff --git a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/spi/DataSourceBasedMultiTenantConnectionProviderImpl.java b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/spi/DataSourceBasedMultiTenantConnectionProviderImpl.java index 1e2622edc488..e76bf18f6422 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/spi/DataSourceBasedMultiTenantConnectionProviderImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/spi/DataSourceBasedMultiTenantConnectionProviderImpl.java @@ -123,6 +123,8 @@ public DatabaseConnectionInfo getDatabaseConnectionInfo(Dialect dialect) { null, dialect.getClass(), dialect.getVersion(), + true, + true, null, null, null, diff --git a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/spi/DatabaseConnectionInfo.java b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/spi/DatabaseConnectionInfo.java index 18baef4b9634..5042ccd6203c 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/spi/DatabaseConnectionInfo.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/spi/DatabaseConnectionInfo.java @@ -4,6 +4,7 @@ */ package org.hibernate.engine.jdbc.connections.spi; +import org.hibernate.Internal; import org.hibernate.dialect.DatabaseVersion; import org.hibernate.dialect.Dialect; @@ -79,6 +80,12 @@ public interface DatabaseConnectionInfo { @Nullable Integer getJdbcFetchSize(); + @Internal + boolean hasSchema(); + + @Internal + boolean hasCatalog(); + /** * Collects the information available here as a single String with the intent of using it in logging. */ diff --git a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/env/internal/ExtractedDatabaseMetaDataImpl.java b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/env/internal/ExtractedDatabaseMetaDataImpl.java index fd82a4f05ac8..690b0aca7255 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/env/internal/ExtractedDatabaseMetaDataImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/env/internal/ExtractedDatabaseMetaDataImpl.java @@ -35,6 +35,8 @@ public class ExtractedDatabaseMetaDataImpl implements ExtractedDatabaseMetaData private final JdbcEnvironment jdbcEnvironment; private final JdbcConnectionAccess connectionAccess; + private final boolean supportsSchemas; + private final boolean supportsCatalogs; private final String connectionCatalogName; private final String connectionSchemaName; @@ -67,6 +69,8 @@ public class ExtractedDatabaseMetaDataImpl implements ExtractedDatabaseMetaData jdbcMetadataAccessible = false; connectionSchemaName = null; connectionCatalogName = null; + supportsSchemas = true; + supportsCatalogs = true; databaseProductName = null; databaseProductVersion = null; supportsRefCursors = false; @@ -94,6 +98,8 @@ public class ExtractedDatabaseMetaDataImpl implements ExtractedDatabaseMetaData jdbcMetadataAccessible = true; final Dialect dialect = environment.getDialect(); final Connection connection = metaData.getConnection(); + supportsSchemas = metaData.supportsSchemasInDataManipulation(); + supportsCatalogs = metaData.supportsCatalogsInDataManipulation(); connectionSchemaName = dialect.getSchemaNameResolver().resolveSchemaName( connection, dialect ); connectionCatalogName = connection.getCatalog(); databaseProductName = metaData.getDatabaseProductName(); @@ -123,6 +129,16 @@ public JdbcEnvironment getJdbcEnvironment() { return jdbcEnvironment; } + @Override + public boolean supportsSchemas() { + return supportsSchemas; + } + + @Override + public boolean supportsCatalogs() { + return supportsCatalogs; + } + @Override public boolean supportsNamedParameters() { return supportsNamedParameters; diff --git a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/env/spi/ExtractedDatabaseMetaData.java b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/env/spi/ExtractedDatabaseMetaData.java index 9c3a57c529f5..1b4797976d5e 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/env/spi/ExtractedDatabaseMetaData.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/env/spi/ExtractedDatabaseMetaData.java @@ -26,10 +26,34 @@ public interface ExtractedDatabaseMetaData { */ JdbcEnvironment getJdbcEnvironment(); + /** + * The name of the database, according to the JDBC driver. + */ String getDatabaseProductName(); + /** + * The version of the database, according to the JDBC driver. + */ String getDatabaseProductVersion(); + /** + * Does this driver support named schemas in DML? + * + * @return {@code false} indicates the driver reported false; + * {@code true} indicates the driver reported true or that + * the driver could not be asked. + */ + boolean supportsSchemas(); + + /** + * Does this driver support named catalogs in DML? + * + * @return {@code false} indicates the driver reported false; + * {@code true} indicates the driver reported true or that + * the driver could not be asked. + */ + boolean supportsCatalogs(); + /** * Retrieve the name of the catalog in effect when we connected to the database. * @@ -59,8 +83,9 @@ public interface ExtractedDatabaseMetaData { /** * Does the driver report supporting {@link java.sql.Types#REF_CURSOR}? * - * @return {@code true} indicates the driver reported true; {@code false} indicates the driver reported false - * or that the driver could not be asked. + * @return {@code true} indicates the driver reported true; + * {@code false} indicates the driver reported false or that + * the driver could not be asked. * * @see java.sql.DatabaseMetaData#supportsRefCursors() * @see org.hibernate.dialect.Dialect#supportsRefCursors diff --git a/hibernate-hikaricp/src/main/java/org/hibernate/hikaricp/internal/HikariCPConnectionProvider.java b/hibernate-hikaricp/src/main/java/org/hibernate/hikaricp/internal/HikariCPConnectionProvider.java index f126934228e1..1f9816233455 100644 --- a/hibernate-hikaricp/src/main/java/org/hibernate/hikaricp/internal/HikariCPConnectionProvider.java +++ b/hibernate-hikaricp/src/main/java/org/hibernate/hikaricp/internal/HikariCPConnectionProvider.java @@ -27,6 +27,8 @@ import static org.hibernate.engine.jdbc.connections.internal.ConnectionProviderInitiator.toIsolationNiceName; import static org.hibernate.engine.jdbc.connections.internal.DatabaseConnectionInfoImpl.getFetchSize; import static org.hibernate.engine.jdbc.connections.internal.DatabaseConnectionInfoImpl.getIsolation; +import static org.hibernate.engine.jdbc.connections.internal.DatabaseConnectionInfoImpl.hasCatalog; +import static org.hibernate.engine.jdbc.connections.internal.DatabaseConnectionInfoImpl.hasSchema; import static org.hibernate.engine.jdbc.env.internal.JdbcEnvironmentInitiator.allowJdbcMetadataAccess; import static org.hibernate.hikaricp.internal.HikariConfigurationUtil.loadConfiguration; import static org.hibernate.internal.util.StringHelper.isBlank; @@ -108,6 +110,8 @@ public DatabaseConnectionInfo getDatabaseConnectionInfo(Dialect dialect) { : hikariConfig.getDriverClassName(), dialect.getClass(), dialect.getVersion(), + hasSchema( hikariDataSource ), + hasCatalog( hikariDataSource ), hikariConfig.getSchema(), hikariConfig.getCatalog(), Boolean.toString( hikariConfig.isAutoCommit() ), From b51c7d4b9051c1ac623f6504541776d8b19f4dac Mon Sep 17 00:00:00 2001 From: Gavin King Date: Mon, 4 Aug 2025 16:53:44 +1000 Subject: [PATCH 10/11] more work on DatabaseConnectionInfo sensible reuse of the JDBC Connection --- .../internal/AgroalConnectionProvider.java | 56 +-- .../c3p0/internal/C3P0ConnectionProvider.java | 56 +-- .../internal/ConnectionCreator.java | 2 +- .../internal/ConnectionCreatorFactory.java | 2 +- .../internal/DatabaseConnectionInfoImpl.java | 97 +--- .../DriverManagerConnectionProviderImpl.java | 423 ++---------------- .../jdbc/connections/internal/PoolState.java | 156 +++++++ .../internal/PooledConnections.java | 231 ++++++++++ .../internal/HikariCPConnectionProvider.java | 52 ++- 9 files changed, 537 insertions(+), 538 deletions(-) create mode 100644 hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/internal/PoolState.java create mode 100644 hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/internal/PooledConnections.java 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 1295af86b18a..3cf3bb25b0af 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 @@ -23,6 +23,7 @@ import org.hibernate.engine.jdbc.connections.spi.ConnectionProvider; import org.hibernate.engine.jdbc.connections.spi.ConnectionProviderConfigurationException; import org.hibernate.engine.jdbc.connections.spi.DatabaseConnectionInfo; +import org.hibernate.exception.JDBCConnectionException; import org.hibernate.internal.log.ConnectionInfoLogger; import org.hibernate.service.UnknownUnwrapTypeException; import org.hibernate.service.spi.Configurable; @@ -110,7 +111,7 @@ public void configure(Map properties) throws HibernateException ConnectionInfoLogger.INSTANCE.configureConnectionPool( "Agroal" ); try { - final Map config = toStringValuedProperties( properties ); + final var config = toStringValuedProperties( properties ); if ( !properties.containsKey( AgroalSettings.AGROAL_MAX_SIZE ) ) { final String maxSize = properties.containsKey( JdbcSettings.POOL_SIZE ) @@ -170,30 +171,35 @@ public boolean supportsAggressiveRelease() { public DatabaseConnectionInfo getDatabaseConnectionInfo(Dialect dialect) { final var poolConfig = agroalDataSource.getConfiguration().connectionPoolConfiguration(); final var connectionConfig = poolConfig.connectionFactoryConfiguration(); - return new DatabaseConnectionInfoImpl( - AgroalConnectionProvider.class, - connectionConfig.jdbcUrl(), - // Attempt to resolve the driver name from the dialect, - // in case it wasn't explicitly set and access to the - // database metadata is allowed - connectionConfig.connectionProviderClass() != null - ? connectionConfig.connectionProviderClass().toString() - : extractDriverNameFromMetadata(), - dialect.getClass(), - dialect.getVersion(), - hasSchema( agroalDataSource), - hasCatalog( agroalDataSource), - getSchema( agroalDataSource ), - getCatalog( agroalDataSource ), - Boolean.toString( connectionConfig.autoCommit() ), - connectionConfig.jdbcTransactionIsolation() != null - && connectionConfig.jdbcTransactionIsolation().isDefined() - ? toIsolationNiceName( connectionConfig.jdbcTransactionIsolation().level() ) - : toIsolationNiceName( getIsolation( agroalDataSource ) ), - poolConfig.minSize(), - poolConfig.maxSize(), - getFetchSize( agroalDataSource ) - ); + try ( var connection = agroalDataSource.getConnection() ) { + return new DatabaseConnectionInfoImpl( + AgroalConnectionProvider.class, + connectionConfig.jdbcUrl(), + // Attempt to resolve the driver name from the dialect, + // in case it wasn't explicitly set and access to the + // database metadata is allowed + connectionConfig.connectionProviderClass() != null + ? connectionConfig.connectionProviderClass().toString() + : extractDriverNameFromMetadata(), + dialect.getClass(), + dialect.getVersion(), + hasSchema( connection ), + hasCatalog( connection ), + getSchema( connection ), + getCatalog( connection ), + Boolean.toString( connectionConfig.autoCommit() ), + connectionConfig.jdbcTransactionIsolation() != null + && connectionConfig.jdbcTransactionIsolation().isDefined() + ? toIsolationNiceName( connectionConfig.jdbcTransactionIsolation().level() ) + : toIsolationNiceName( getIsolation( connection ) ), + poolConfig.minSize(), + poolConfig.maxSize(), + getFetchSize( connection ) + ); + } + catch (SQLException e) { + throw new JDBCConnectionException( "Could not create connection", e ); + } } private String extractDriverNameFromMetadata() { diff --git a/hibernate-c3p0/src/main/java/org/hibernate/c3p0/internal/C3P0ConnectionProvider.java b/hibernate-c3p0/src/main/java/org/hibernate/c3p0/internal/C3P0ConnectionProvider.java index 1b22775b11a4..563e2ff40456 100644 --- a/hibernate-c3p0/src/main/java/org/hibernate/c3p0/internal/C3P0ConnectionProvider.java +++ b/hibernate-c3p0/src/main/java/org/hibernate/c3p0/internal/C3P0ConnectionProvider.java @@ -13,6 +13,7 @@ import org.hibernate.engine.jdbc.connections.spi.ConnectionProvider; import org.hibernate.engine.jdbc.connections.spi.ConnectionProviderConfigurationException; import org.hibernate.engine.jdbc.connections.spi.DatabaseConnectionInfo; +import org.hibernate.exception.JDBCConnectionException; import org.hibernate.internal.log.ConnectionInfoLogger; import org.hibernate.service.UnknownUnwrapTypeException; import org.hibernate.service.spi.Configurable; @@ -163,32 +164,37 @@ public void configure(Map properties) { final var poolSettings = poolSettings( properties ); dataSource = createDataSource( jdbcUrl, connectionProps, poolSettings ); - final Integer fetchSize = getFetchSize( dataSource ); - final boolean hasSchema = hasSchema( dataSource ); - final boolean hasCatalog = hasCatalog( dataSource ); - final String schema = getSchema( dataSource ); - final String catalog = getCatalog( dataSource ); - if ( isolation == null ) { - isolation = getIsolation( dataSource ); + try ( var connection = dataSource.getConnection() ) { + final Integer fetchSize = getFetchSize( connection ); + final boolean hasSchema = hasSchema( connection ); + final boolean hasCatalog = hasCatalog( connection ); + final String schema = getSchema( connection ); + final String catalog = getCatalog( connection ); + if ( isolation == null ) { + isolation = getIsolation( connection ); + } + dbInfoProducer = dialect -> new DatabaseConnectionInfoImpl( + C3P0ConnectionProvider.class, + jdbcUrl, + jdbcDriverClass, + dialect.getClass(), + dialect.getVersion(), + hasSchema, + hasCatalog, + schema, + catalog, + Boolean.toString( autocommit ), + isolation == null ? null : toIsolationNiceName( isolation ), + requireNonNullElse( getInteger( C3P0_STYLE_MIN_POOL_SIZE.substring( 5 ), poolSettings ), + DEFAULT_MIN_POOL_SIZE ), + requireNonNullElse( getInteger( C3P0_STYLE_MAX_POOL_SIZE.substring( 5 ), poolSettings ), + DEFAULT_MAX_POOL_SIZE ), + fetchSize + ); + } + catch (SQLException e) { + throw new JDBCConnectionException( "Could not create connection", e ); } - dbInfoProducer = dialect -> new DatabaseConnectionInfoImpl( - C3P0ConnectionProvider.class, - jdbcUrl, - jdbcDriverClass, - dialect.getClass(), - dialect.getVersion(), - hasSchema, - hasCatalog, - schema, - catalog, - Boolean.toString( autocommit ), - isolation == null ? null : toIsolationNiceName( isolation ), - requireNonNullElse( getInteger( C3P0_STYLE_MIN_POOL_SIZE.substring( 5 ), poolSettings ), - DEFAULT_MIN_POOL_SIZE ), - requireNonNullElse( getInteger( C3P0_STYLE_MAX_POOL_SIZE.substring( 5 ), poolSettings ), - DEFAULT_MAX_POOL_SIZE ), - fetchSize - ); } private DataSource createDataSource(String jdbcUrl, Properties connectionProps, Map poolProperties) { diff --git a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/internal/ConnectionCreator.java b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/internal/ConnectionCreator.java index 6851c628a326..e2b1fff05bff 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/internal/ConnectionCreator.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/internal/ConnectionCreator.java @@ -11,7 +11,7 @@ * * @author Steve Ebersole */ -interface ConnectionCreator { +public interface ConnectionCreator { /** * Obtain the URL to which this creator connects. Intended just for informational (logging) purposes. * diff --git a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/internal/ConnectionCreatorFactory.java b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/internal/ConnectionCreatorFactory.java index 805a0693cf4c..858c0ed9073a 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/internal/ConnectionCreatorFactory.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/internal/ConnectionCreatorFactory.java @@ -15,7 +15,7 @@ * * @author Christian Beikov */ -interface ConnectionCreatorFactory { +public interface ConnectionCreatorFactory { ConnectionCreator create( Driver driver, diff --git a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/internal/DatabaseConnectionInfoImpl.java b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/internal/DatabaseConnectionInfoImpl.java index bc5789043b81..c51a98129539 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/internal/DatabaseConnectionInfoImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/internal/DatabaseConnectionInfoImpl.java @@ -4,6 +4,7 @@ */ package org.hibernate.engine.jdbc.connections.internal; +import java.sql.Connection; import java.sql.SQLException; import java.util.Map; @@ -16,7 +17,6 @@ import org.hibernate.internal.util.NullnessHelper; import org.hibernate.internal.util.config.ConfigurationHelper; -import javax.sql.DataSource; import static org.hibernate.dialect.SimpleDatabaseVersion.ZERO_VERSION; import static org.hibernate.engine.jdbc.connections.internal.ConnectionProviderInitiator.interpretIsolation; @@ -117,114 +117,55 @@ public DatabaseConnectionInfoImpl(Dialect dialect) { ); } - public static boolean hasSchema(DataSource dataSource) { - try ( var conn = dataSource.getConnection() ) { - return conn.getMetaData().supportsSchemasInDataManipulation(); + public static boolean hasSchema(Connection connection) { + try { + return connection.getMetaData().supportsSchemasInTableDefinitions(); } catch ( SQLException ignored ) { return true; } } - public static boolean hasCatalog(DataSource dataSource) { - try ( var conn = dataSource.getConnection() ) { - return conn.getMetaData().supportsCatalogsInDataManipulation(); + public static boolean hasCatalog(Connection connection) { + try { + return connection.getMetaData().supportsCatalogsInDataManipulation(); } catch ( SQLException ignored ) { return true; } } - public static String getSchema(DataSource dataSource) { - try ( var conn = dataSource.getConnection() ) { + public static String getSchema(Connection connection) { + try { // method introduced in 1.7 - return conn.getSchema(); + return connection.getSchema(); } catch ( SQLException|AbstractMethodError ignored ) { return null; } } - public static String getCatalog(DataSource dataSource) { - try ( var conn = dataSource.getConnection() ) { - return conn.getCatalog(); + public static String getCatalog(Connection connection) { + try { + return connection.getCatalog(); } catch ( SQLException ignored ) { return null; } } - static boolean hasSchema(ConnectionCreator creator) { - try ( var conn = creator.createConnection() ) { - return conn.getMetaData().supportsSchemasInTableDefinitions(); - } - catch ( SQLException ignored ) { - return true; - } - } - - static boolean hasCatalog(ConnectionCreator creator) { - try ( var conn = creator.createConnection() ) { - return conn.getMetaData().supportsCatalogsInDataManipulation(); - } - catch ( SQLException ignored ) { - return true; - } - } - - static String getSchema(ConnectionCreator creator) { - try ( var conn = creator.createConnection() ) { - // method introduced in 1.7 - return conn.getSchema(); - } - catch ( SQLException|AbstractMethodError ignored ) { - return null; - } - } - - static String getCatalog(ConnectionCreator creator) { - try ( var conn = creator.createConnection() ) { - return conn.getCatalog(); - } - catch ( SQLException ignored ) { - return null; - } - } - - public static Integer getFetchSize(DataSource dataSource) { - try ( var conn = dataSource.getConnection() ) { - try ( var statement = conn.createStatement() ) { - return statement.getFetchSize(); - } - } - catch ( SQLException ignored ) { - return null; - } - } - - public static Integer getIsolation(DataSource dataSource) { - try ( var conn = dataSource.getConnection() ) { - return conn.getTransactionIsolation(); - } - catch ( SQLException ignored ) { - return null; - } - } - - static Integer getFetchSize(ConnectionCreator creator) { - try ( var conn = creator.createConnection() ) { - try ( var statement = conn.createStatement() ) { - return statement.getFetchSize(); - } + public static Integer getFetchSize(Connection connection) { + try ( var statement = connection.createStatement() ) { + return statement.getFetchSize(); } catch ( SQLException ignored ) { return null; } } - static Integer getIsolation(ConnectionCreator creator) { - try ( var conn = creator.createConnection() ) { - return conn.getTransactionIsolation(); + public static Integer getIsolation(Connection connection) { + try { + return connection.getTransactionIsolation(); } catch ( SQLException ignored ) { return null; diff --git a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/internal/DriverManagerConnectionProviderImpl.java b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/internal/DriverManagerConnectionProviderImpl.java index dc7f467a4da9..7f584c4f3e2e 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/internal/DriverManagerConnectionProviderImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/internal/DriverManagerConnectionProviderImpl.java @@ -10,15 +10,7 @@ import java.sql.SQLException; import java.util.Map; import java.util.Properties; -import java.util.concurrent.ConcurrentLinkedQueue; -import java.util.concurrent.Executors; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.ThreadFactory; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.locks.ReadWriteLock; -import java.util.concurrent.locks.ReentrantReadWriteLock; - -import org.hibernate.HibernateException; + import org.hibernate.boot.registry.classloading.spi.ClassLoaderService; import org.hibernate.dialect.Database; import org.hibernate.dialect.Dialect; @@ -26,6 +18,7 @@ import org.hibernate.engine.jdbc.connections.spi.ConnectionProvider; import org.hibernate.engine.jdbc.connections.spi.ConnectionProviderConfigurationException; import org.hibernate.engine.jdbc.connections.spi.DatabaseConnectionInfo; +import org.hibernate.exception.JDBCConnectionException; import org.hibernate.service.UnknownUnwrapTypeException; import org.hibernate.service.spi.Configurable; import org.hibernate.service.spi.ServiceException; @@ -64,10 +57,6 @@ public class DriverManagerConnectionProviderImpl implements ConnectionProvider, Configurable, Stoppable, ServiceRegistryAwareService, ConnectionValidator { - //Thanks to Oleg Varaksin and his article on object pooling using the {@link java.util.concurrent} - //package, from which much of the pooling code here is derived. - //See http://ovaraksin.blogspot.com/2013/08/simple-and-lightweight-pool.html - public static final String MIN_SIZE = "hibernate.connection.min_pool_size"; public static final String INITIAL_SIZE = "hibernate.connection.initial_pool_size"; // in TimeUnit.SECONDS @@ -93,17 +82,17 @@ public void configure(Map configurationValues) { ConnectionInfoLogger.INSTANCE.usingHibernateBuiltInConnectionPool(); PooledConnections pool = buildPool( configurationValues, serviceRegistry ); final long validationInterval = getLong( VALIDATION_INTERVAL, configurationValues, 30 ); - this.state = new PoolState( pool, validationInterval ); + state = new PoolState( pool, validationInterval ); } - private PooledConnections buildPool(Map configurationValues, ServiceRegistryImplementor serviceRegistry) { - final boolean autoCommit = getBoolean( AUTOCOMMIT, configurationValues ); // default to false - final int minSize = getInt( MIN_SIZE, configurationValues, 1 ); - final int maxSize = getInt( POOL_SIZE, configurationValues, 20 ); - final int initialSize = getInt( INITIAL_SIZE, configurationValues, minSize ); - - final ConnectionCreator creator = buildCreator( configurationValues, serviceRegistry ); - return new PooledConnections.Builder( creator, autoCommit ) + private PooledConnections buildPool(Map configuration, ServiceRegistryImplementor serviceRegistry) { + final var creator = buildCreator( configuration, serviceRegistry ); + final boolean autoCommit = getBoolean( AUTOCOMMIT, configuration ); // default autocommit to false + final int minSize = getInt( MIN_SIZE, configuration, 1 ); + final int maxSize = getInt( POOL_SIZE, configuration, 20 ); + final int initialSize = getInt( INITIAL_SIZE, configuration, minSize ); + return new PooledConnections.Builder( creator ) + .autoCommit( autoCommit ) .initialSize( initialSize ) .minSize( minSize ) .maxSize( maxSize ) @@ -124,7 +113,7 @@ private static ConnectionCreator buildCreator( } else { //try to guess the driver class from the JDBC URL - for ( Database database: Database.values() ) { + for ( var database: Database.values() ) { if ( database.matchesUrl( url ) ) { driverClassName = database.getDriverClassName( url ); if ( driverClassName != null ) { @@ -163,24 +152,30 @@ private static ConnectionCreator buildCreator( configurationValues ); - dbInfo = new DatabaseConnectionInfoImpl( - DriverManagerConnectionProviderImpl.class, - url, - driverList, - null, - SimpleDatabaseVersion.ZERO_VERSION, - hasSchema( connectionCreator ), - hasCatalog( connectionCreator ), - getSchema( connectionCreator ), - getCatalog( connectionCreator ), - Boolean.toString( autoCommit ), - isolation != null - ? toIsolationNiceName( isolation ) - : toIsolationNiceName( getIsolation( connectionCreator ) ), - getInt( MIN_SIZE, configurationValues, 1 ), - getInt( POOL_SIZE, configurationValues, 20 ), - getFetchSize( connectionCreator ) - ); + + try ( var connection = connectionCreator.createConnection() ) { + dbInfo = new DatabaseConnectionInfoImpl( + DriverManagerConnectionProviderImpl.class, + url, + driverList, + null, + SimpleDatabaseVersion.ZERO_VERSION, + hasSchema( connection ), + hasCatalog( connection ), + getSchema( connection ), + getCatalog( connection ), + Boolean.toString( autoCommit ), + isolation != null + ? toIsolationNiceName( isolation ) + : toIsolationNiceName( getIsolation( connection ) ), + getInt( MIN_SIZE, configurationValues, 1 ), + getInt( POOL_SIZE, configurationValues, 20 ), + getFetchSize( connection ) + ); + } + catch (SQLException e) { + throw new JDBCConnectionException( "Could not create connection", e ); + } return connectionCreator; } @@ -341,11 +336,11 @@ public T unwrap(Class unwrapType) { } protected int getOpenConnections() { - return state.pool.allConnections.size() - state.pool.availableConnections.size(); + return state.getPool().getOpenConnectionCount(); } protected void validateConnectionsReturned() { - int allocationCount = getOpenConnections(); + final int allocationCount = getOpenConnections(); if ( allocationCount != 0 ) { ConnectionInfoLogger.INSTANCE.error( "Connection leak detected: there are " + allocationCount + " unclosed connections"); } @@ -377,346 +372,4 @@ protected void finalize() throws Throwable { public boolean isValid(Connection connection) throws SQLException { return true; } - - public static class PooledConnections { - - private final ConcurrentLinkedQueue allConnections = new ConcurrentLinkedQueue<>(); - private final ConcurrentLinkedQueue availableConnections = new ConcurrentLinkedQueue<>(); - - private final ConnectionCreator connectionCreator; - private final ConnectionValidator connectionValidator; - private final boolean autoCommit; - private final int minSize; - private final int maxSize; - - private volatile boolean primed; - - private PooledConnections( - Builder builder) { - ConnectionInfoLogger.INSTANCE.debugf( "Initializing Connection pool with %s Connections", builder.initialSize ); - connectionCreator = builder.connectionCreator; - connectionValidator = builder.connectionValidator == null - ? ConnectionValidator.ALWAYS_VALID - : builder.connectionValidator; - autoCommit = builder.autoCommit; - maxSize = builder.maxSize; - minSize = builder.minSize; - addConnections( builder.initialSize ); - } - - private void validate() { - final int size = size(); - - if ( !primed && size >= minSize ) { - // IMPL NOTE: the purpose of primed is to allow the pool to lazily reach its - // defined min-size. - ConnectionInfoLogger.INSTANCE.debug( "Connection pool now considered primed; min-size will be maintained" ); - primed = true; - } - - if ( size < minSize && primed ) { - int numberToBeAdded = minSize - size; - ConnectionInfoLogger.INSTANCE.debugf( "Adding %s Connections to the pool", numberToBeAdded ); - addConnections( numberToBeAdded ); - } - else if ( size > maxSize ) { - int numberToBeRemoved = size - maxSize; - ConnectionInfoLogger.INSTANCE.debugf( "Removing %s Connections from the pool", numberToBeRemoved ); - removeConnections( numberToBeRemoved ); - } - } - - private void add(Connection conn) { - final Connection connection = releaseConnection( conn ); - if ( connection != null ) { - availableConnections.offer( connection ); - } - } - - private Connection releaseConnection(Connection conn) { - Exception t = null; - try { - conn.setAutoCommit( true ); - conn.clearWarnings(); - if ( connectionValidator.isValid( conn ) ) { - return conn; - } - } - catch (SQLException ex) { - t = ex; - } - closeConnection( conn, t ); - ConnectionInfoLogger.INSTANCE.debug( "Connection release failed. Closing pooled connection", t ); - return null; - } - - private Connection poll() { - Connection conn; - do { - conn = availableConnections.poll(); - if ( conn == null ) { - synchronized (allConnections) { - if ( allConnections.size() < maxSize ) { - addConnections( 1 ); - return poll(); - } - } - throw new HibernateException( - "The internal connection pool has reached its maximum size and no connection is currently available" ); - } - conn = prepareConnection( conn ); - } while ( conn == null ); - return conn; - } - - protected Connection prepareConnection(Connection conn) { - Exception t = null; - try { - conn.setAutoCommit( autoCommit ); - if ( connectionValidator.isValid( conn ) ) { - return conn; - } - } - catch (SQLException ex) { - t = ex; - } - closeConnection( conn, t ); - ConnectionInfoLogger.INSTANCE.debug( "Connection preparation failed. Closing pooled connection", t ); - return null; - } - - protected void closeConnection(Connection conn, Throwable t) { - try { - conn.close(); - } - catch (SQLException ex) { - ConnectionInfoLogger.INSTANCE.unableToClosePooledConnection( ex ); - if ( t != null ) { - t.addSuppressed( ex ); - } - } - finally { - if ( !allConnections.remove( conn ) ) { - ConnectionInfoLogger.INSTANCE.debug( "Connection remove failed." ); - } - } - } - - public void close() throws SQLException { - try { - final int allocationCount = allConnections.size() - availableConnections.size(); - if (allocationCount > 0) { - ConnectionInfoLogger.INSTANCE.error( "Connection leak detected: there are " + allocationCount + " unclosed connections upon shutting down pool " + getUrl()); - } - } - finally { - removeConnections( Integer.MAX_VALUE ); - } - } - - public int size() { - return allConnections.size(); - } - - protected void removeConnections(int numberToBeRemoved) { - for ( int i = 0; i < numberToBeRemoved; i++ ) { - final Connection connection = availableConnections.poll(); - if ( connection == null ) { - break; - } - closeConnection( connection, null ); - } - } - - protected void addConnections(int numberOfConnections) { - for ( int i = 0; i < numberOfConnections; i++ ) { - Connection connection = connectionCreator.createConnection(); - allConnections.add( connection ); - availableConnections.add( connection ); - } - } - - public String getUrl() { - return connectionCreator.getUrl(); - } - - private static class Builder { - private final ConnectionCreator connectionCreator; - private ConnectionValidator connectionValidator; - private final boolean autoCommit; - private int initialSize = 1; - private int minSize = 1; - private int maxSize = 20; - - private Builder(ConnectionCreator connectionCreator, boolean autoCommit) { - this.connectionCreator = connectionCreator; - this.autoCommit = autoCommit; - } - - private Builder initialSize(int initialSize) { - this.initialSize = initialSize; - return this; - } - - private Builder minSize(int minSize) { - this.minSize = minSize; - return this; - } - - private Builder maxSize(int maxSize) { - this.maxSize = maxSize; - return this; - } - - private Builder validator(ConnectionValidator connectionValidator) { - this.connectionValidator = connectionValidator; - return this; - } - - private PooledConnections build() { - return new PooledConnections( this ); - } - } - } - - private static class PoolState implements Runnable { - - //Protecting any lifecycle state change: - private final ReadWriteLock statelock = new ReentrantReadWriteLock(); - private volatile boolean active = false; - private ScheduledExecutorService executorService; - - private final PooledConnections pool; - private final long validationInterval; - - private PoolState(PooledConnections pool, long validationInterval) { - this.pool = pool; - this.validationInterval = validationInterval; - } - - private void startIfNeeded() { - if ( active ) { - return; - } - statelock.writeLock().lock(); - try { - if ( active ) { - return; - } - executorService = Executors.newSingleThreadScheduledExecutor( new ValidationThreadFactory() ); - executorService.scheduleWithFixedDelay( - this, - validationInterval, - validationInterval, - TimeUnit.SECONDS - ); - active = true; - } - finally { - statelock.writeLock().unlock(); - } - } - - @Override - public void run() { - if ( active ) { - pool.validate(); - } - } - - private void stop() { - statelock.writeLock().lock(); - try { - if ( !active ) { - return; - } - ConnectionInfoLogger.INSTANCE.cleaningUpConnectionPool( pool.getUrl() ); - active = false; - if ( executorService != null ) { - executorService.shutdown(); - } - executorService = null; - try { - pool.close(); - } - catch (SQLException e) { - ConnectionInfoLogger.INSTANCE.unableToDestroyConnectionPool( e ); - } - } - finally { - statelock.writeLock().unlock(); - } - } - - private Connection getConnection() { - startIfNeeded(); - statelock.readLock().lock(); - try { - return pool.poll(); - } - finally { - statelock.readLock().unlock(); - } - } - - private void closeConnection(Connection conn) { - if (conn == null) { - return; - } - startIfNeeded(); - statelock.readLock().lock(); - try { - pool.add( conn ); - } - finally { - statelock.readLock().unlock(); - } - } - - private void validateConnections(ConnectionValidator validator) { - if ( !active ) { - return; - } - statelock.writeLock().lock(); - try { - RuntimeException ex = null; - for ( Connection connection : pool.allConnections ) { - SQLException e = null; - boolean isValid = false; - try { - isValid = validator.isValid( connection ); - } - catch (SQLException sqlException) { - e = sqlException; - } - if ( !isValid ) { - pool.closeConnection( connection, e ); - if ( ex == null ) { - ex = new RuntimeException( e ); - } - else if ( e != null ) { - ex.addSuppressed( e ); - } - } - } - if ( ex != null ) { - throw ex; - } - } - finally { - statelock.writeLock().unlock(); - } - } - } - - private static class ValidationThreadFactory implements ThreadFactory { - @Override - public Thread newThread(Runnable runnable) { - final Thread thread = new Thread( runnable ); - thread.setDaemon( true ); - thread.setName( "Hibernate Connection Pool Validation Thread" ); - return thread; - } - } - } diff --git a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/internal/PoolState.java b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/internal/PoolState.java new file mode 100644 index 000000000000..a6cb71ab5f25 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/internal/PoolState.java @@ -0,0 +1,156 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.engine.jdbc.connections.internal; + +import org.hibernate.internal.log.ConnectionInfoLogger; + +import java.sql.Connection; +import java.sql.SQLException; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; + +import static java.util.concurrent.Executors.newSingleThreadScheduledExecutor; + +class PoolState implements Runnable { + + //Protecting any lifecycle state change: + private final ReadWriteLock statelock = new ReentrantReadWriteLock(); + private volatile boolean active = false; + private ScheduledExecutorService executorService; + + private final PooledConnections pool; + private final long validationInterval; + + PoolState(PooledConnections pool, long validationInterval) { + this.pool = pool; + this.validationInterval = validationInterval; + } + + private void startIfNeeded() { + if ( active ) { + return; + } + statelock.writeLock().lock(); + try { + if ( active ) { + return; + } + executorService = + newSingleThreadScheduledExecutor( runnable -> { + final Thread thread = new Thread( runnable ); + thread.setDaemon( true ); + thread.setName( "Hibernate Connection Pool Validation Thread" ); + return thread; + } ); + executorService.scheduleWithFixedDelay( + this, + validationInterval, + validationInterval, + TimeUnit.SECONDS + ); + active = true; + } + finally { + statelock.writeLock().unlock(); + } + } + + @Override + public void run() { + if ( active ) { + pool.validate(); + } + } + + public PooledConnections getPool() { + return pool; + } + + void stop() { + statelock.writeLock().lock(); + try { + if ( !active ) { + return; + } + ConnectionInfoLogger.INSTANCE.cleaningUpConnectionPool( pool.getUrl() ); + active = false; + if ( executorService != null ) { + executorService.shutdown(); + } + executorService = null; + try { + pool.close(); + } + catch (SQLException e) { + ConnectionInfoLogger.INSTANCE.unableToDestroyConnectionPool( e ); + } + } + finally { + statelock.writeLock().unlock(); + } + } + + Connection getConnection() { + startIfNeeded(); + statelock.readLock().lock(); + try { + return pool.poll(); + } + finally { + statelock.readLock().unlock(); + } + } + + void closeConnection(Connection conn) { + if ( conn == null ) { + return; + } + startIfNeeded(); + statelock.readLock().lock(); + try { + pool.add( conn ); + } + finally { + statelock.readLock().unlock(); + } + } + + void validateConnections(ConnectionValidator validator) { + if ( !active ) { + return; + } + statelock.writeLock().lock(); + try { + RuntimeException ex = null; + for ( var connection : pool.getAllConnections() ) { + SQLException e = null; + boolean isValid = false; + try { + isValid = validator.isValid( connection ); + } + catch (SQLException sqlException) { + e = sqlException; + } + if ( !isValid ) { + pool.closeConnection( connection, e ); + if ( ex == null ) { + ex = new RuntimeException( e ); + } + else if ( e != null ) { + ex.addSuppressed( e ); + } + } + } + if ( ex != null ) { + throw ex; + } + } + finally { + statelock.writeLock().unlock(); + } + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/internal/PooledConnections.java b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/internal/PooledConnections.java new file mode 100644 index 000000000000..a9bd9e70e24f --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/internal/PooledConnections.java @@ -0,0 +1,231 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * Copyright Red Hat Inc. and Hibernate Authors + */ +package org.hibernate.engine.jdbc.connections.internal; + +import org.hibernate.HibernateException; +import org.hibernate.internal.log.ConnectionInfoLogger; + +import java.sql.Connection; +import java.sql.SQLException; +import java.util.concurrent.ConcurrentLinkedQueue; + +class PooledConnections { + + // Thanks to Oleg Varaksin and his article on object pooling using the {@link java.util.concurrent} + // package, from which the original pooling code here is was derived. + // See http://ovaraksin.blogspot.com/2013/08/simple-and-lightweight-pool.html + + private final ConcurrentLinkedQueue allConnections = new ConcurrentLinkedQueue<>(); + private final ConcurrentLinkedQueue availableConnections = new ConcurrentLinkedQueue<>(); + + private final ConnectionCreator connectionCreator; + private final ConnectionValidator connectionValidator; + private final boolean autoCommit; + private final int minSize; + private final int maxSize; + + private volatile boolean primed; + + private PooledConnections( + Builder builder) { + ConnectionInfoLogger.INSTANCE.debugf( "Initializing Connection pool with %s Connections", builder.initialSize ); + connectionCreator = builder.connectionCreator; + connectionValidator = builder.connectionValidator == null + ? ConnectionValidator.ALWAYS_VALID + : builder.connectionValidator; + autoCommit = builder.autoCommit; + maxSize = builder.maxSize; + minSize = builder.minSize; + addConnections( builder.initialSize ); + } + + void validate() { + final int size = size(); + + if ( !primed && size >= minSize ) { + // IMPL NOTE: the purpose of primed is to allow the pool to lazily reach its + // defined min-size. + ConnectionInfoLogger.INSTANCE.debug( "Connection pool now considered primed; min-size will be maintained" ); + primed = true; + } + + if ( size < minSize && primed ) { + int numberToBeAdded = minSize - size; + ConnectionInfoLogger.INSTANCE.debugf( "Adding %s Connections to the pool", numberToBeAdded ); + addConnections( numberToBeAdded ); + } + else if ( size > maxSize ) { + int numberToBeRemoved = size - maxSize; + ConnectionInfoLogger.INSTANCE.debugf( "Removing %s Connections from the pool", numberToBeRemoved ); + removeConnections( numberToBeRemoved ); + } + } + + void add(Connection conn) { + final Connection connection = releaseConnection( conn ); + if ( connection != null ) { + availableConnections.offer( connection ); + } + } + + private Connection releaseConnection(Connection conn) { + Exception t = null; + try { + conn.setAutoCommit( true ); + conn.clearWarnings(); + if ( connectionValidator.isValid( conn ) ) { + return conn; + } + } + catch (SQLException ex) { + t = ex; + } + closeConnection( conn, t ); + ConnectionInfoLogger.INSTANCE.debug( "Connection release failed. Closing pooled connection", t ); + return null; + } + + Connection poll() { + Connection conn; + do { + conn = availableConnections.poll(); + if ( conn == null ) { + synchronized (allConnections) { + if ( allConnections.size() < maxSize ) { + addConnections( 1 ); + return poll(); + } + } + throw new HibernateException( + "The internal connection pool has reached its maximum size and no connection is currently available" ); + } + conn = prepareConnection( conn ); + } + while ( conn == null ); + return conn; + } + + protected Connection prepareConnection(Connection conn) { + Exception t = null; + try { + conn.setAutoCommit( autoCommit ); + if ( connectionValidator.isValid( conn ) ) { + return conn; + } + } + catch (SQLException ex) { + t = ex; + } + closeConnection( conn, t ); + ConnectionInfoLogger.INSTANCE.debug( "Connection preparation failed. Closing pooled connection", t ); + return null; + } + + protected void closeConnection(Connection conn, Throwable t) { + try { + conn.close(); + } + catch (SQLException ex) { + ConnectionInfoLogger.INSTANCE.unableToClosePooledConnection( ex ); + if ( t != null ) { + t.addSuppressed( ex ); + } + } + finally { + if ( !allConnections.remove( conn ) ) { + ConnectionInfoLogger.INSTANCE.debug( "Connection remove failed." ); + } + } + } + + public void close() throws SQLException { + try { + final int allocationCount = allConnections.size() - availableConnections.size(); + if ( allocationCount > 0 ) { + ConnectionInfoLogger.INSTANCE.error( + "Connection leak detected: there are " + allocationCount + " unclosed connections upon shutting down pool " + getUrl() ); + } + } + finally { + removeConnections( Integer.MAX_VALUE ); + } + } + + public int size() { + return allConnections.size(); + } + + protected void removeConnections(int numberToBeRemoved) { + for ( int i = 0; i < numberToBeRemoved; i++ ) { + final Connection connection = availableConnections.poll(); + if ( connection == null ) { + break; + } + closeConnection( connection, null ); + } + } + + protected void addConnections(int numberOfConnections) { + for ( int i = 0; i < numberOfConnections; i++ ) { + Connection connection = connectionCreator.createConnection(); + allConnections.add( connection ); + availableConnections.add( connection ); + } + } + + public String getUrl() { + return connectionCreator.getUrl(); + } + + int getOpenConnectionCount() { + return allConnections.size() - availableConnections.size(); + } + + public Iterable getAllConnections() { + return allConnections; + } + + static class Builder { + private final ConnectionCreator connectionCreator; + private ConnectionValidator connectionValidator; + private boolean autoCommit; + private int initialSize = 1; + private int minSize = 1; + private int maxSize = 20; + + Builder(ConnectionCreator connectionCreator) { + this.connectionCreator = connectionCreator; + } + + Builder autoCommit(boolean autoCommit) { + this.autoCommit = autoCommit; + return this; + } + + Builder initialSize(int initialSize) { + this.initialSize = initialSize; + return this; + } + + Builder minSize(int minSize) { + this.minSize = minSize; + return this; + } + + Builder maxSize(int maxSize) { + this.maxSize = maxSize; + return this; + } + + Builder validator(ConnectionValidator connectionValidator) { + this.connectionValidator = connectionValidator; + return this; + } + + PooledConnections build() { + return new PooledConnections( this ); + } + } +} diff --git a/hibernate-hikaricp/src/main/java/org/hibernate/hikaricp/internal/HikariCPConnectionProvider.java b/hibernate-hikaricp/src/main/java/org/hibernate/hikaricp/internal/HikariCPConnectionProvider.java index 1f9816233455..837603564db4 100644 --- a/hibernate-hikaricp/src/main/java/org/hibernate/hikaricp/internal/HikariCPConnectionProvider.java +++ b/hibernate-hikaricp/src/main/java/org/hibernate/hikaricp/internal/HikariCPConnectionProvider.java @@ -16,6 +16,7 @@ import org.hibernate.engine.jdbc.connections.spi.ConnectionProvider; import org.hibernate.engine.jdbc.connections.spi.ConnectionProviderConfigurationException; import org.hibernate.engine.jdbc.connections.spi.DatabaseConnectionInfo; +import org.hibernate.exception.JDBCConnectionException; import org.hibernate.internal.log.ConnectionInfoLogger; import org.hibernate.service.UnknownUnwrapTypeException; import org.hibernate.service.spi.Configurable; @@ -99,29 +100,34 @@ public boolean supportsAggressiveRelease() { @Override public DatabaseConnectionInfo getDatabaseConnectionInfo(Dialect dialect) { - return new DatabaseConnectionInfoImpl( - HikariCPConnectionProvider.class, - hikariConfig.getJdbcUrl(), - // Attempt to resolve the driver name from the dialect, - // in case it wasn't explicitly set and access to the - // database metadata is allowed - isBlank( hikariConfig.getDriverClassName() ) - ? extractDriverNameFromMetadata() - : hikariConfig.getDriverClassName(), - dialect.getClass(), - dialect.getVersion(), - hasSchema( hikariDataSource ), - hasCatalog( hikariDataSource ), - hikariConfig.getSchema(), - hikariConfig.getCatalog(), - Boolean.toString( hikariConfig.isAutoCommit() ), - hikariConfig.getTransactionIsolation() != null - ? hikariConfig.getTransactionIsolation() - : toIsolationNiceName( getIsolation( hikariDataSource ) ), - hikariConfig.getMinimumIdle(), - hikariConfig.getMaximumPoolSize(), - getFetchSize( hikariDataSource ) - ); + try ( var connection = hikariDataSource.getConnection() ) { + return new DatabaseConnectionInfoImpl( + HikariCPConnectionProvider.class, + hikariConfig.getJdbcUrl(), + // Attempt to resolve the driver name from the dialect, + // in case it wasn't explicitly set and access to the + // database metadata is allowed + isBlank( hikariConfig.getDriverClassName() ) + ? extractDriverNameFromMetadata() + : hikariConfig.getDriverClassName(), + dialect.getClass(), + dialect.getVersion(), + hasSchema( connection ), + hasCatalog( connection ), + hikariConfig.getSchema(), + hikariConfig.getCatalog(), + Boolean.toString( hikariConfig.isAutoCommit() ), + hikariConfig.getTransactionIsolation() != null + ? hikariConfig.getTransactionIsolation() + : toIsolationNiceName( getIsolation( connection ) ), + hikariConfig.getMinimumIdle(), + hikariConfig.getMaximumPoolSize(), + getFetchSize( connection ) + ); + } + catch (SQLException e) { + throw new JDBCConnectionException( "Could not create connection", e ); + } } private String extractDriverNameFromMetadata() { From 2d251d662cb58878468f0f54cd3ec4ca58cc2970 Mon Sep 17 00:00:00 2001 From: Gavin King Date: Mon, 4 Aug 2025 17:36:40 +1000 Subject: [PATCH 11/11] roll back the connection used for creating the DatabaseConnectionInfoImpl because it has autoCommit disabled, and this caused problems on Db2 --- .../hibernate/agroal/internal/AgroalConnectionProvider.java | 6 +++++- .../org/hibernate/c3p0/internal/C3P0ConnectionProvider.java | 3 +++ .../internal/DriverManagerConnectionProviderImpl.java | 3 +++ .../hikaricp/internal/HikariCPConnectionProvider.java | 6 +++++- 4 files changed, 16 insertions(+), 2 deletions(-) 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 3cf3bb25b0af..f4e9724a01e5 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 @@ -172,7 +172,7 @@ public DatabaseConnectionInfo getDatabaseConnectionInfo(Dialect dialect) { final var poolConfig = agroalDataSource.getConfiguration().connectionPoolConfiguration(); final var connectionConfig = poolConfig.connectionFactoryConfiguration(); try ( var connection = agroalDataSource.getConnection() ) { - return new DatabaseConnectionInfoImpl( + final var info = new DatabaseConnectionInfoImpl( AgroalConnectionProvider.class, connectionConfig.jdbcUrl(), // Attempt to resolve the driver name from the dialect, @@ -196,6 +196,10 @@ public DatabaseConnectionInfo getDatabaseConnectionInfo(Dialect dialect) { poolConfig.maxSize(), getFetchSize( connection ) ); + if ( !connection.getAutoCommit() ) { + connection.rollback(); + } + return info; } catch (SQLException e) { throw new JDBCConnectionException( "Could not create connection", e ); diff --git a/hibernate-c3p0/src/main/java/org/hibernate/c3p0/internal/C3P0ConnectionProvider.java b/hibernate-c3p0/src/main/java/org/hibernate/c3p0/internal/C3P0ConnectionProvider.java index 563e2ff40456..03b55cd1d6f4 100644 --- a/hibernate-c3p0/src/main/java/org/hibernate/c3p0/internal/C3P0ConnectionProvider.java +++ b/hibernate-c3p0/src/main/java/org/hibernate/c3p0/internal/C3P0ConnectionProvider.java @@ -191,6 +191,9 @@ public void configure(Map properties) { DEFAULT_MAX_POOL_SIZE ), fetchSize ); + if ( !connection.getAutoCommit() ) { + connection.rollback(); + } } catch (SQLException e) { throw new JDBCConnectionException( "Could not create connection", e ); diff --git a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/internal/DriverManagerConnectionProviderImpl.java b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/internal/DriverManagerConnectionProviderImpl.java index 7f584c4f3e2e..b581a687bb3a 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/internal/DriverManagerConnectionProviderImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/jdbc/connections/internal/DriverManagerConnectionProviderImpl.java @@ -172,6 +172,9 @@ private static ConnectionCreator buildCreator( getInt( POOL_SIZE, configurationValues, 20 ), getFetchSize( connection ) ); + if ( !connection.getAutoCommit() ) { + connection.rollback(); + } } catch (SQLException e) { throw new JDBCConnectionException( "Could not create connection", e ); diff --git a/hibernate-hikaricp/src/main/java/org/hibernate/hikaricp/internal/HikariCPConnectionProvider.java b/hibernate-hikaricp/src/main/java/org/hibernate/hikaricp/internal/HikariCPConnectionProvider.java index 837603564db4..58396f965830 100644 --- a/hibernate-hikaricp/src/main/java/org/hibernate/hikaricp/internal/HikariCPConnectionProvider.java +++ b/hibernate-hikaricp/src/main/java/org/hibernate/hikaricp/internal/HikariCPConnectionProvider.java @@ -101,7 +101,7 @@ public boolean supportsAggressiveRelease() { @Override public DatabaseConnectionInfo getDatabaseConnectionInfo(Dialect dialect) { try ( var connection = hikariDataSource.getConnection() ) { - return new DatabaseConnectionInfoImpl( + final var info = new DatabaseConnectionInfoImpl( HikariCPConnectionProvider.class, hikariConfig.getJdbcUrl(), // Attempt to resolve the driver name from the dialect, @@ -124,6 +124,10 @@ public DatabaseConnectionInfo getDatabaseConnectionInfo(Dialect dialect) { hikariConfig.getMaximumPoolSize(), getFetchSize( connection ) ); + if ( !connection.getAutoCommit() ) { + connection.rollback(); + } + return info; } catch (SQLException e) { throw new JDBCConnectionException( "Could not create connection", e );