Skip to content

Commit a8410aa

Browse files
committed
Merge branch '6.2.x'
# Conflicts: # spring-jdbc/src/main/java/org/springframework/jdbc/datasource/DataSourceUtils.java # spring-jdbc/src/test/java/org/springframework/jdbc/datasource/DataSourceTransactionManagerTests.java
2 parents 81d6921 + a330277 commit a8410aa

File tree

4 files changed

+137
-46
lines changed

4 files changed

+137
-46
lines changed

spring-jdbc/src/main/java/org/springframework/jdbc/datasource/DataSourceTransactionManager.java

Lines changed: 42 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,8 @@ public class DataSourceTransactionManager extends AbstractPlatformTransactionMan
126126

127127
private boolean enforceReadOnly = false;
128128

129+
private volatile @Nullable Boolean defaultReadOnly;
130+
129131

130132
/**
131133
* Create a new {@code DataSourceTransactionManager} instance.
@@ -269,13 +271,18 @@ protected void doBegin(Object transaction, TransactionDefinition definition) {
269271
if (logger.isDebugEnabled()) {
270272
logger.debug("Acquired Connection [" + newCon + "] for JDBC transaction");
271273
}
274+
if (definition.isReadOnly()) {
275+
checkDefaultReadOnly(newCon);
276+
}
272277
txObject.setConnectionHolder(new ConnectionHolder(newCon), true);
273278
}
274279

275280
txObject.getConnectionHolder().setSynchronizedWithTransaction(true);
276281
con = txObject.getConnectionHolder().getConnection();
277282

278-
Integer previousIsolationLevel = DataSourceUtils.prepareConnectionForTransaction(con, definition);
283+
Integer previousIsolationLevel = DataSourceUtils.prepareConnectionForTransaction(con,
284+
definition.getIsolationLevel(),
285+
(definition.isReadOnly() && !isDefaultReadOnly()));
279286
txObject.setPreviousIsolationLevel(previousIsolationLevel);
280287
txObject.setReadOnly(definition.isReadOnly());
281288

@@ -380,8 +387,9 @@ protected void doCleanupAfterCompletion(Object transaction) {
380387
if (txObject.isMustRestoreAutoCommit()) {
381388
con.setAutoCommit(true);
382389
}
383-
DataSourceUtils.resetConnectionAfterTransaction(
384-
con, txObject.getPreviousIsolationLevel(), txObject.isReadOnly());
390+
DataSourceUtils.resetConnectionAfterTransaction(con,
391+
txObject.getPreviousIsolationLevel(),
392+
(txObject.isReadOnly() && !isDefaultReadOnly()));
385393
}
386394
catch (Throwable ex) {
387395
logger.debug("Could not reset JDBC Connection after transaction", ex);
@@ -398,6 +406,37 @@ protected void doCleanupAfterCompletion(Object transaction) {
398406
}
399407

400408

409+
/**
410+
* Check the default {@link Connection#isReadOnly()} flag on a freshly
411+
* obtained connection from the {@code DataSource}, assuming that the
412+
* same flag applies to all connections obtained from the given setup.
413+
* @param newCon the Connection to check
414+
* @since 6.2.13
415+
* @see #isDefaultReadOnly()
416+
*/
417+
private void checkDefaultReadOnly(Connection newCon) {
418+
if (this.defaultReadOnly == null) {
419+
try {
420+
this.defaultReadOnly = newCon.isReadOnly();
421+
}
422+
catch (Throwable ex) {
423+
logger.debug("Could not determine default JDBC Connection isReadOnly - assuming false", ex);
424+
this.defaultReadOnly = false;
425+
}
426+
}
427+
}
428+
429+
/**
430+
* Check whether the default read-only flag has been determined as {@code true},
431+
* assuming that all encountered connections will be read-only by default and
432+
* therefore do not need explicit {@link Connection#setReadOnly} (re)setting.
433+
* @since 6.2.13
434+
* @see #checkDefaultReadOnly(Connection)
435+
*/
436+
private boolean isDefaultReadOnly() {
437+
return (this.defaultReadOnly == Boolean.TRUE);
438+
}
439+
401440
/**
402441
* Prepare the transactional {@code Connection} right after transaction begin.
403442
* <p>The default implementation executes a "SET TRANSACTION READ ONLY" statement

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

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -170,18 +170,36 @@ private static Connection fetchConnection(DataSource dataSource) throws SQLExcep
170170
* @param definition the transaction definition to apply
171171
* @return the previous isolation level, if any
172172
* @throws SQLException if thrown by JDBC methods
173-
* @see #resetConnectionAfterTransaction
173+
* @see #prepareConnectionForTransaction(Connection, int, boolean)
174+
*/
175+
public static @Nullable Integer prepareConnectionForTransaction(Connection con, @Nullable TransactionDefinition definition)
176+
throws SQLException {
177+
178+
return prepareConnectionForTransaction(con,
179+
(definition != null ? definition.getIsolationLevel() : TransactionDefinition.ISOLATION_DEFAULT),
180+
(definition != null && definition.isReadOnly()));
181+
}
182+
183+
/**
184+
* Prepare the given Connection with the given transaction semantics.
185+
* @param con the Connection to prepare
186+
* @param isolationLevel the isolation level to apply
187+
* @param setReadOnly whether to set the read-only flag
188+
* @return the previous isolation level, if any
189+
* @throws SQLException if thrown by JDBC methods
190+
* @since 6.2.13
191+
* @see #resetConnectionAfterTransaction(Connection, Integer, boolean)
174192
* @see Connection#setTransactionIsolation
175193
* @see Connection#setReadOnly
176194
*/
177-
public static @Nullable Integer prepareConnectionForTransaction(Connection con, @Nullable TransactionDefinition definition)
195+
static @Nullable Integer prepareConnectionForTransaction(Connection con, int isolationLevel, boolean setReadOnly)
178196
throws SQLException {
179197

180198
Assert.notNull(con, "No Connection specified");
181199

182200
boolean debugEnabled = logger.isDebugEnabled();
183201
// Set read-only flag.
184-
if (definition != null && definition.isReadOnly()) {
202+
if (setReadOnly) {
185203
try {
186204
if (debugEnabled) {
187205
logger.debug("Setting JDBC Connection [" + con + "] read-only");
@@ -204,15 +222,14 @@ private static Connection fetchConnection(DataSource dataSource) throws SQLExcep
204222

205223
// Apply specific isolation level, if any.
206224
Integer previousIsolationLevel = null;
207-
if (definition != null && definition.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT) {
225+
if (isolationLevel != TransactionDefinition.ISOLATION_DEFAULT) {
208226
if (debugEnabled) {
209-
logger.debug("Changing isolation level of JDBC Connection [" + con + "] to " +
210-
definition.getIsolationLevel());
227+
logger.debug("Changing isolation level of JDBC Connection [" + con + "] to " + isolationLevel);
211228
}
212229
int currentIsolation = con.getTransactionIsolation();
213-
if (currentIsolation != definition.getIsolationLevel()) {
230+
if (currentIsolation != isolationLevel) {
214231
previousIsolationLevel = currentIsolation;
215-
con.setTransactionIsolation(definition.getIsolationLevel());
232+
con.setTransactionIsolation(isolationLevel);
216233
}
217234
}
218235

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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,9 @@ public LazyConnectionDataSourceProxy(DataSource targetDataSource) {
150150
*/
151151
public void setReadOnlyDataSource(@Nullable DataSource readOnlyDataSource) {
152152
this.readOnlyDataSource = readOnlyDataSource;
153+
if (getTargetDataSource() == null) {
154+
setTargetDataSource(readOnlyDataSource);
155+
}
153156
}
154157

155158
/**
@@ -384,7 +387,7 @@ public LazyConnectionInvocationHandler(String username, String password) {
384387
return null;
385388
}
386389
case "isReadOnly" -> {
387-
return this.readOnly;
390+
return (this.readOnly || getTargetDataSource() == readOnlyDataSource);
388391
}
389392
case "setReadOnly" -> {
390393
this.readOnly = (Boolean) args[0];

0 commit comments

Comments
 (0)