From 7c711751dd2a69363cf5206659231ebece81a0be Mon Sep 17 00:00:00 2001 From: LLEFEVRE Date: Mon, 3 Jun 2024 12:08:00 +0200 Subject: [PATCH] HHH-18220 Detect if Application Continuity is enabled for Oracle dialect --- .../cfg/DialectSpecificSettings.java | 10 +++ .../org/hibernate/dialect/OracleDialect.java | 9 ++ .../dialect/OracleServerConfiguration.java | 87 ++++++++++++++++--- 3 files changed, 95 insertions(+), 11 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/DialectSpecificSettings.java b/hibernate-core/src/main/java/org/hibernate/cfg/DialectSpecificSettings.java index 5ed23f434d..de4f24e239 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/DialectSpecificSettings.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/DialectSpecificSettings.java @@ -12,6 +12,7 @@ package org.hibernate.cfg; * its underlying JDBC {@link java.sql.Connection}. * * @author Marco Belladelli + * @author Loïc Lefèvre */ public interface DialectSpecificSettings { /** @@ -28,6 +29,15 @@ public interface DialectSpecificSettings { */ public static final String ORACLE_EXTENDED_STRING_SIZE = "hibernate.dialect.oracle.extended_string_size"; + /** + * Specifies whether this database is accessed using a database service protected by Application Continuity. + * + * @settingDefault {@code false} + * + * @see Application Continuity for Java + */ + public static final String ORACLE_APPLICATION_CONTINUITY = "hibernate.dialect.oracle.application_continuity"; + /** * Specifies whether this database's {@code ansinull} setting is enabled. * diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/OracleDialect.java b/hibernate-core/src/main/java/org/hibernate/dialect/OracleDialect.java index da7f6926b2..99a5297abd 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/OracleDialect.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/OracleDialect.java @@ -187,6 +187,9 @@ public class OracleDialect extends Dialect { // Is MAX_STRING_SIZE set to EXTENDED? protected final boolean extended; + // Is the database accessed using a database service protected by Application Continuity. + protected final boolean applicationContinuity; + protected final int driverMajorVersion; protected final int driverMinorVersion; @@ -200,6 +203,7 @@ public class OracleDialect extends Dialect { super(version); autonomous = false; extended = false; + applicationContinuity = false; driverMajorVersion = 19; driverMinorVersion = 0; } @@ -212,6 +216,7 @@ public class OracleDialect extends Dialect { super( info ); autonomous = serverConfiguration.isAutonomous(); extended = serverConfiguration.isExtended(); + applicationContinuity = serverConfiguration.isApplicationContinuity(); this.driverMinorVersion = serverConfiguration.getDriverMinorVersion(); this.driverMajorVersion = serverConfiguration.getDriverMajorVersion(); } @@ -258,6 +263,10 @@ public class OracleDialect extends Dialect { return extended; } + public boolean isApplicationContinuity() { + return applicationContinuity; + } + @Override protected DatabaseVersion getMinimumSupportedVersion() { return MINIMUM_VERSION; diff --git a/hibernate-core/src/main/java/org/hibernate/dialect/OracleServerConfiguration.java b/hibernate-core/src/main/java/org/hibernate/dialect/OracleServerConfiguration.java index ee7c104ac1..6b1845dff4 100644 --- a/hibernate-core/src/main/java/org/hibernate/dialect/OracleServerConfiguration.java +++ b/hibernate-core/src/main/java/org/hibernate/dialect/OracleServerConfiguration.java @@ -6,15 +6,20 @@ */ package org.hibernate.dialect; +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 oracle.jdbc.replay.ReplayStatistics; +import oracle.jdbc.replay.ReplayableConnection; import org.hibernate.engine.jdbc.dialect.spi.DialectResolutionInfo; import org.hibernate.internal.util.config.ConfigurationHelper; +import static oracle.jdbc.replay.ReplayableConnection.StatisticsReportType.FOR_CURRENT_CONNECTION; +import static org.hibernate.cfg.DialectSpecificSettings.ORACLE_APPLICATION_CONTINUITY; import static org.hibernate.cfg.DialectSpecificSettings.ORACLE_AUTONOMOUS_DATABASE; import static org.hibernate.cfg.DialectSpecificSettings.ORACLE_EXTENDED_STRING_SIZE; @@ -22,10 +27,12 @@ import static org.hibernate.cfg.DialectSpecificSettings.ORACLE_EXTENDED_STRING_S * Utility class that extract some initial configuration from the database for {@link OracleDialect}. * * @author Marco Belladelli + * @author Loïc Lefèvre */ public class OracleServerConfiguration { private final boolean autonomous; private final boolean extended; + private final boolean applicationContinuity; private final int driverMajorVersion; private final int driverMinorVersion; @@ -37,6 +44,10 @@ public class OracleServerConfiguration { return extended; } + public boolean isApplicationContinuity() { + return applicationContinuity; + } + public int getDriverMajorVersion() { return driverMajorVersion; } @@ -46,7 +57,7 @@ public class OracleServerConfiguration { } public OracleServerConfiguration(boolean autonomous, boolean extended) { - this( autonomous, extended, 19, 0 ); + this( autonomous, extended, false, 19, 0 ); } public OracleServerConfiguration( @@ -54,8 +65,18 @@ public class OracleServerConfiguration { boolean extended, int driverMajorVersion, int driverMinorVersion) { + this(autonomous, extended, false, driverMajorVersion, driverMinorVersion); + } + + public OracleServerConfiguration( + boolean autonomous, + boolean extended, + boolean applicationContinuity, + int driverMajorVersion, + int driverMinorVersion) { this.autonomous = autonomous; this.extended = extended; + this.applicationContinuity = applicationContinuity; this.driverMajorVersion = driverMajorVersion; this.driverMinorVersion = driverMinorVersion; } @@ -63,6 +84,7 @@ public class OracleServerConfiguration { public static OracleServerConfiguration fromDialectResolutionInfo(DialectResolutionInfo info) { Boolean extended = null; Boolean autonomous = null; + Boolean applicationContinuity = null; Integer majorVersion = null; Integer minorVersion = null; final DatabaseMetaData databaseMetaData = info.getDatabaseMetadata(); @@ -70,15 +92,51 @@ public class OracleServerConfiguration { majorVersion = databaseMetaData.getDriverMajorVersion(); minorVersion = databaseMetaData.getDriverMinorVersion(); - try (final Statement statement = databaseMetaData.getConnection().createStatement()) { - final ResultSet rs = statement.executeQuery( - "select cast('string' as varchar2(32000)), " + - "sys_context('USERENV','CLOUD_SERVICE') from dual" - ); - if ( rs.next() ) { - // succeeded, so MAX_STRING_SIZE == EXTENDED - extended = true; - autonomous = isAutonomous( rs.getString( 2 ) ); + + try { + final Connection c = databaseMetaData.getConnection(); + + // Use Oracle JDBC replay statistics information to determine if this + // connection is protected by Application Continuity + try { + final ReplayableConnection re = (ReplayableConnection) c; + ReplayStatistics stats = re.getReplayStatistics(FOR_CURRENT_CONNECTION); + + final long totalRequests = stats.getTotalRequests(); + final long protectedCalls = stats.getTotalProtectedCalls(); + + try (final Statement s = c.createStatement()) { + try (final ResultSet r = s.executeQuery("select 1 from dual")) { + r.next(); + } + } + + stats = re.getReplayStatistics(FOR_CURRENT_CONNECTION); + + // Application continuity is enabled on this database service if the number of + // total requests and the number of protected calls for this connection have + // both increased. + applicationContinuity = stats.getTotalRequests() > totalRequests && stats.getTotalProtectedCalls() > protectedCalls; + } + catch(Exception e) { + // A ClassCastException or a NullPointerException are expected here in the case + // the Connection Factory is not the right one (not Replayable: ClassCastException) + // or if the database service has not been configured (server side) to enable + // application continuity (NullPointerException). + applicationContinuity = false; + } + + // continue the checks... + try (final Statement statement = c.createStatement()) { + final ResultSet rs = statement.executeQuery( + "select cast('string' as varchar2(32000)), " + + "sys_context('USERENV','CLOUD_SERVICE') from dual" + ); + if (rs.next()) { + // succeeded, so MAX_STRING_SIZE == EXTENDED + extended = true; + autonomous = isAutonomous(rs.getString(2)); + } } } catch (SQLException ex) { @@ -102,6 +160,13 @@ public class OracleServerConfiguration { false ); } + if ( applicationContinuity == null ) { + applicationContinuity = ConfigurationHelper.getBoolean( + ORACLE_APPLICATION_CONTINUITY, + info.getConfigurationValues(), + false + ); + } if ( majorVersion == null ) { try { java.sql.Driver driver = java.sql.DriverManager.getDriver( "jdbc:oracle:thin:" ); @@ -114,7 +179,7 @@ public class OracleServerConfiguration { } } - return new OracleServerConfiguration( autonomous, extended, majorVersion, minorVersion ); + return new OracleServerConfiguration( autonomous, extended, applicationContinuity, majorVersion, minorVersion ); } private static boolean isAutonomous(String cloudServiceParam) {