Browse Source

Do not keep target connection after failed settings

Includes aligned setReadOnly exception suppression.

Closes gh-35980

(cherry picked from commit ab33000750)
6.2.x
Juergen Hoeller 1 week ago
parent
commit
24df35c55a
  1. 45
      spring-jdbc/src/main/java/org/springframework/jdbc/datasource/DataSourceUtils.java
  2. 62
      spring-jdbc/src/main/java/org/springframework/jdbc/datasource/LazyConnectionDataSourceProxy.java

45
spring-jdbc/src/main/java/org/springframework/jdbc/datasource/DataSourceUtils.java

@ -202,24 +202,10 @@ public abstract class DataSourceUtils {
boolean debugEnabled = logger.isDebugEnabled(); boolean debugEnabled = logger.isDebugEnabled();
// Set read-only flag. // Set read-only flag.
if (setReadOnly) { if (setReadOnly) {
try { if (debugEnabled) {
if (debugEnabled) { logger.debug("Setting JDBC Connection [" + con + "] read-only");
logger.debug("Setting JDBC Connection [" + con + "] read-only");
}
con.setReadOnly(true);
}
catch (SQLException | RuntimeException ex) {
Throwable exToCheck = ex;
while (exToCheck != null) {
if (exToCheck.getClass().getSimpleName().contains("Timeout")) {
// Assume it's a connection timeout that would otherwise get lost: for example, from JDBC 4.0
throw ex;
}
exToCheck = exToCheck.getCause();
}
// "read-only not supported" SQLException -> ignore, it's just a hint anyway
logger.debug("Could not set JDBC Connection read-only", ex);
} }
setReadOnlyIfPossible(con);
} }
// Apply specific isolation level, if any. // Apply specific isolation level, if any.
@ -238,6 +224,31 @@ public abstract class DataSourceUtils {
return previousIsolationLevel; return previousIsolationLevel;
} }
/**
* Apply the read-only hint to the given Connection,
* suppressing exceptions other than timeout-related ones.
* @param con the Connection to prepare
* @throws SQLException in case of a timeout exception
* @since 6.2.15
*/
static void setReadOnlyIfPossible(Connection con) throws SQLException {
try {
con.setReadOnly(true);
}
catch (SQLException | RuntimeException ex) {
Throwable exToCheck = ex;
while (exToCheck != null) {
if (exToCheck.getClass().getSimpleName().contains("Timeout")) {
// Assume it's a connection timeout that would otherwise get lost: for example, from JDBC 4.0
throw ex;
}
exToCheck = exToCheck.getCause();
}
// "read-only not supported" SQLException -> ignore, it's just a hint anyway
logger.debug("Could not set JDBC Connection read-only", ex);
}
}
/** /**
* Reset the given Connection after a transaction, * Reset the given Connection after a transaction,
* regarding read-only flag and isolation level. * regarding read-only flag and isolation level.

62
spring-jdbc/src/main/java/org/springframework/jdbc/datasource/LazyConnectionDataSourceProxy.java

@ -471,48 +471,56 @@ public class LazyConnectionDataSourceProxy extends DelegatingDataSource {
/** /**
* Return the target Connection, fetching it and initializing it if necessary. * Return the target Connection, fetching it and initializing it if necessary.
*/ */
private Connection getTargetConnection(Method operation) throws SQLException { private Connection getTargetConnection(Method operation) throws Throwable {
if (this.target == null) { Connection target = this.target;
// No target Connection held -> fetch one. if (target != null) {
// Target Connection already held -> return it.
if (logger.isTraceEnabled()) { if (logger.isTraceEnabled()) {
logger.trace("Connecting to database for operation '" + operation.getName() + "'"); logger.trace("Using existing database connection for operation '" + operation.getName() + "'");
} }
return target;
}
// Fetch physical Connection from DataSource. // No target Connection held -> fetch one.
DataSource dataSource = getDataSourceToUse(); if (logger.isTraceEnabled()) {
this.target = (this.username != null ? dataSource.getConnection(this.username, this.password) : logger.trace("Connecting to database for operation '" + operation.getName() + "'");
dataSource.getConnection()); }
if (this.target == null) {
throw new IllegalStateException("DataSource returned null from getConnection(): " + dataSource); // Fetch physical Connection from DataSource.
} DataSource dataSource = getDataSourceToUse();
target = (this.username != null ? dataSource.getConnection(this.username, this.password) :
dataSource.getConnection());
if (target == null) {
throw new IllegalStateException("DataSource returned null from getConnection(): " + dataSource);
}
// Apply kept transaction settings, if any. // Apply kept transaction settings, if any.
try {
if (this.readOnly && readOnlyDataSource == null) { if (this.readOnly && readOnlyDataSource == null) {
try { DataSourceUtils.setReadOnlyIfPossible(target);
this.target.setReadOnly(true);
}
catch (Exception ex) {
// "read-only not supported" -> ignore, it's just a hint anyway
logger.debug("Could not set JDBC Connection read-only", ex);
}
} }
if (this.transactionIsolation != null && if (this.transactionIsolation != null &&
!this.transactionIsolation.equals(defaultTransactionIsolation())) { !this.transactionIsolation.equals(defaultTransactionIsolation())) {
this.target.setTransactionIsolation(this.transactionIsolation); target.setTransactionIsolation(this.transactionIsolation);
} }
if (this.autoCommit != null && this.autoCommit != defaultAutoCommit()) { if (this.autoCommit != null && this.autoCommit != defaultAutoCommit()) {
this.target.setAutoCommit(this.autoCommit); target.setAutoCommit(this.autoCommit);
} }
} }
catch (Throwable settingsEx) {
else { logger.debug("Failed to apply transaction settings to JDBC Connection", settingsEx);
// Target Connection already held -> return it. // Close Connection and do not set it as target.
if (logger.isTraceEnabled()) { try {
logger.trace("Using existing database connection for operation '" + operation.getName() + "'"); target.close();
}
catch (Throwable closeEx) {
logger.debug("Could not close JDBC Connection after failed settings", closeEx);
} }
throw settingsEx;
} }
return this.target; this.target = target;
return target;
} }
private DataSource getDataSourceToUse() { private DataSource getDataSourceToUse() {

Loading…
Cancel
Save