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..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 @@ -23,14 +23,13 @@ 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; 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; @@ -40,8 +39,12 @@ 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.connections.internal.DatabaseConnectionInfoImpl.hasCatalog; +import static org.hibernate.engine.jdbc.connections.internal.DatabaseConnectionInfoImpl.hasSchema; import static org.hibernate.engine.jdbc.env.internal.JdbcEnvironmentInitiator.allowJdbcMetadataAccess; /** @@ -108,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 ) @@ -166,27 +169,41 @@ public boolean supportsAggressiveRelease() { @Override public DatabaseConnectionInfo getDatabaseConnectionInfo(Dialect dialect) { - final AgroalConnectionPoolConfiguration acpc = - agroalDataSource.getConfiguration().connectionPoolConfiguration(); - final AgroalConnectionFactoryConfiguration acfc = acpc.connectionFactoryConfiguration(); - return new DatabaseConnectionInfoImpl( - AgroalConnectionProvider.class, - acfc.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() - : extractDriverNameFromMetadata(), - dialect.getVersion(), - Boolean.toString( acfc.autoCommit() ), - acfc.jdbcTransactionIsolation() != null && acfc.jdbcTransactionIsolation().isDefined() - ? toIsolationNiceName( acfc.jdbcTransactionIsolation().level() ) - : toIsolationNiceName( getIsolation( agroalDataSource ) ), - acpc.minSize(), - acpc.maxSize(), - getFetchSize( agroalDataSource ) - ); + final var poolConfig = agroalDataSource.getConfiguration().connectionPoolConfiguration(); + final var connectionConfig = poolConfig.connectionFactoryConfiguration(); + try ( var connection = agroalDataSource.getConnection() ) { + final var info = 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 ) + ); + if ( !connection.getAutoCommit() ) { + connection.rollback(); + } + return info; + } + 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 a3eb44e2f418..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 @@ -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; @@ -43,8 +44,12 @@ 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.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; @@ -159,23 +164,40 @@ public void configure(Map properties) { final var poolSettings = poolSettings( properties ); dataSource = createDataSource( jdbcUrl, connectionProps, poolSettings ); - final Integer fetchSize = getFetchSize( 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 + ); + if ( !connection.getAutoCommit() ) { + connection.rollback(); + } + } + catch (SQLException e) { + throw new JDBCConnectionException( "Could not create connection", e ); } - dbInfoProducer = dialect -> new DatabaseConnectionInfoImpl( - C3P0ConnectionProvider.class, - jdbcUrl, - jdbcDriverClass, - dialect.getVersion(), - 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-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; + } + +} 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/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/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; 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} */ 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/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 5ea084c1347a..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; @@ -36,7 +36,12 @@ 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; + private final boolean hasSchema; + private final boolean hasCatalog; + protected final String schema; + protected final String catalog; protected final String autoCommitMode; protected final String isolationLevel; protected final Integer poolMinSize; @@ -47,7 +52,11 @@ public DatabaseConnectionInfoImpl( Class connectionProviderClass, String jdbcUrl, String jdbcDriver, - DatabaseVersion dialectVersion, + Class dialectClass, DatabaseVersion dialectVersion, + boolean hasSchema, + boolean hasCatalog, + String schema, + String catalog, String autoCommitMode, String isolationLevel, Integer poolMinSize, @@ -57,11 +66,16 @@ 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 ); this.isolationLevel = nullIfEmpty( isolationLevel ); this.poolMinSize = poolMinSize; this.poolMaxSize = poolMaxSize; this.fetchSize = fetchSize; + this.dialectClass = dialectClass; } public DatabaseConnectionInfoImpl(Map settings, Dialect dialect) { @@ -69,7 +83,12 @@ public DatabaseConnectionInfoImpl(Map settings, Dialect dialect) null, determineUrl( settings ), determineDriver( settings ), + dialect.getClass(), dialect.getVersion(), + true, + true, + null, + null, determineAutoCommitMode( settings ), determineIsolationLevel( settings ), // No setting for min. pool size @@ -80,43 +99,73 @@ 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.getClass(), + dialect.getVersion(), + true, + true, + null, + null, + null, + null, + null, + null, + null + ); + } + + public static boolean hasSchema(Connection connection) { + try { + return connection.getMetaData().supportsSchemasInTableDefinitions(); + } + catch ( SQLException ignored ) { + return true; + } } - public static Integer getFetchSize(DataSource dataSource) { - try ( var conn = dataSource.getConnection() ) { - try ( var statement = conn.createStatement() ) { - return statement.getFetchSize(); - } + public static boolean hasCatalog(Connection connection) { + try { + return connection.getMetaData().supportsCatalogsInDataManipulation(); } catch ( SQLException ignored ) { + return true; + } + } + + public static String getSchema(Connection connection) { + try { + // method introduced in 1.7 + return connection.getSchema(); + } + catch ( SQLException|AbstractMethodError ignored ) { return null; } } - public static Integer getIsolation(DataSource dataSource) { - try ( var conn = dataSource.getConnection() ) { - return conn.getTransactionIsolation(); + public static String getCatalog(Connection connection) { + try { + return connection.getCatalog(); } 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; @@ -163,12 +212,34 @@ public Integer getPoolMaxSize() { return fetchSize; } + @Override + public @Nullable String getSchema() { + return schema; + } + + @Override + public @Nullable String getCatalog() { + return catalog; + } + + @Override + public boolean hasSchema() { + return hasSchema; + } + + @Override + public boolean hasCatalog() { + return hasCatalog; + } + @Override 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 \tIsolation level: %s \tJDBC fetch size: %s @@ -178,10 +249,13 @@ public String toInfoString() { .formatted( handleEmpty( jdbcUrl ), handleEmpty( jdbcDriver ), + handleEmpty( dialectClass ), handleEmpty( dialectVersion ), + handleEmpty( catalog, hasCatalog ), + handleEmpty( schema, hasSchema ), handleEmpty( autoCommitMode ), handleEmpty( isolationLevel ), - handleEmpty( fetchSize ), + handleFetchSize( fetchSize ), handleEmpty( connectionProviderClass ), handleEmpty( poolMinSize ), handleEmpty( poolMaxSize ) @@ -192,16 +266,20 @@ 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(); } 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 f85299f20b37..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 @@ -157,36 +157,21 @@ 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.getClass(), dialect.getVersion(), + metaData == null || metaData.supportsSchemas(), + metaData == null || metaData.supportsCatalogs(), + metaData == null ? null : metaData.getConnectionSchemaName(), + metaData == null ? null : metaData.getConnectionCatalogName(), null, - isolationLevel, + metaData == null ? null : isolationString( metaData ), null, null, - fetchSize + metaData != null ? fetchSize( metaData ) : null ) { @Override public String toInfoString() { @@ -196,4 +181,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() ) + "]"; + } } 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..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 @@ -10,23 +10,15 @@ 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.cfg.AvailableSettings; import org.hibernate.dialect.Database; import org.hibernate.dialect.Dialect; import org.hibernate.dialect.SimpleDatabaseVersion; 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; @@ -35,12 +27,20 @@ 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; +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.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; @@ -57,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 @@ -86,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( 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 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 ) @@ -108,7 +104,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 ) { @@ -117,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 ) { @@ -139,7 +135,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 ); @@ -156,21 +152,33 @@ private static ConnectionCreator buildCreator( configurationValues ); - ; - dbInfo = new DatabaseConnectionInfoImpl( - DriverManagerConnectionProviderImpl.class, - url, - driverList, - SimpleDatabaseVersion.ZERO_VERSION, - Boolean.toString( autoCommit ), - isolation != null - ? toIsolationNiceName( isolation ) - : toIsolationNiceName( getIsolation( connectionCreator ) ), - getInt( MIN_SIZE, configurationValues, 1 ), - getInt( AvailableSettings.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 ) + ); + if ( !connection.getAutoCommit() ) { + connection.rollback(); + } + } + catch (SQLException e) { + throw new JDBCConnectionException( "Could not create connection", e ); + } return connectionCreator; } @@ -190,7 +198,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 + "'" ); } @@ -298,7 +306,12 @@ public DatabaseConnectionInfo getDatabaseConnectionInfo(Dialect dialect) { DriverManagerConnectionProviderImpl.class, dbInfo.getJdbcUrl(), dbInfo.getJdbcDriver(), + dialect.getClass(), dialect.getVersion(), + dbInfo.hasSchema(), + dbInfo.hasCatalog(), + dbInfo.getSchema(), + dbInfo.getCatalog(), dbInfo.getAutoCommitMode(), dbInfo.getIsolationLevel(), dbInfo.getPoolMinSize(), @@ -326,11 +339,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"); } @@ -362,346 +375,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-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..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 @@ -121,7 +121,12 @@ public DatabaseConnectionInfo getDatabaseConnectionInfo(Dialect dialect) { null, null, null, + dialect.getClass(), dialect.getVersion(), + true, + true, + null, + null, 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 191a4e964810..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; @@ -37,6 +38,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. */ @@ -67,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 61a81c6735c4..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 @@ -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} @@ -34,9 +35,14 @@ 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; + private final String databaseProductName; + private final String databaseProductVersion; + private final boolean supportsRefCursors; private final boolean supportsNamedParameters; private final boolean supportsScrollableResults; @@ -54,46 +60,63 @@ 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, - 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.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; + supportsSchemas = true; + supportsCatalogs = true; + 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(); + supportsSchemas = metaData.supportsSchemasInDataManipulation(); + supportsCatalogs = metaData.supportsCatalogsInDataManipulation(); + 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 @@ -106,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; @@ -151,6 +184,16 @@ public String getConnectionSchemaName() { return connectionSchemaName; } + @Override + public String getDatabaseProductName() { + return databaseProductName; + } + + @Override + public String getDatabaseProductVersion() { + return databaseProductVersion; + } + @Override public String getUrl() { return url; @@ -198,130 +241,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 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; + private static int defaultFetchSize(Connection connection) { + try ( var statement = connection.createStatement() ) { + return statement.getFetchSize(); } - - public Builder apply(DatabaseMetaData databaseMetaData) throws SQLException { - connectionCatalogName = databaseMetaData.getConnection().getCatalog(); - // NOTE : databaseMetaData.getConnection().getSchema() would require java 1.7 as baseline - 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; - } - - 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, - 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 dad55f55dce0..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 @@ -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 @@ -25,6 +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. * @@ -54,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 @@ -161,6 +191,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/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 { 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; 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..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 @@ -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; @@ -27,6 +28,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; @@ -97,24 +100,38 @@ 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.getVersion(), - Boolean.toString( hikariConfig.isAutoCommit() ), - hikariConfig.getTransactionIsolation() != null - ? hikariConfig.getTransactionIsolation() - : toIsolationNiceName( getIsolation( hikariDataSource ) ), - hikariConfig.getMinimumIdle(), - hikariConfig.getMaximumPoolSize(), - getFetchSize( hikariDataSource ) - ); + try ( var connection = hikariDataSource.getConnection() ) { + final var info = 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 ) + ); + if ( !connection.getAutoCommit() ) { + connection.rollback(); + } + return info; + } + catch (SQLException e) { + throw new JDBCConnectionException( "Could not create connection", e ); + } } private String extractDriverNameFromMetadata() {