Skip to content

Commit 773b2f0

Browse files
committed
Avoid Connection.isReadOnly() call in resetConnectionAfterTransaction
Closes gh-23747
1 parent 7d02ba0 commit 773b2f0

File tree

7 files changed

+107
-13
lines changed

7 files changed

+107
-13
lines changed

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

+3-1
Original file line numberDiff line numberDiff line change
@@ -272,6 +272,7 @@ protected void doBegin(Object transaction, TransactionDefinition definition) {
272272

273273
Integer previousIsolationLevel = DataSourceUtils.prepareConnectionForTransaction(con, definition);
274274
txObject.setPreviousIsolationLevel(previousIsolationLevel);
275+
txObject.setReadOnly(definition.isReadOnly());
275276

276277
// Switch to manual commit if necessary. This is very expensive in some JDBC drivers,
277278
// so we don't want to do it unnecessarily (for example if we've explicitly
@@ -374,7 +375,8 @@ protected void doCleanupAfterCompletion(Object transaction) {
374375
if (txObject.isMustRestoreAutoCommit()) {
375376
con.setAutoCommit(true);
376377
}
377-
DataSourceUtils.resetConnectionAfterTransaction(con, txObject.getPreviousIsolationLevel());
378+
DataSourceUtils.resetConnectionAfterTransaction(
379+
con, txObject.getPreviousIsolationLevel(), txObject.isReadOnly());
378380
}
379381
catch (Throwable ex) {
380382
logger.debug("Could not reset JDBC Connection after transaction", ex);

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

+42
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,8 @@ private static Connection fetchConnection(DataSource dataSource) throws SQLExcep
169169
* @return the previous isolation level, if any
170170
* @throws SQLException if thrown by JDBC methods
171171
* @see #resetConnectionAfterTransaction
172+
* @see Connection#setTransactionIsolation
173+
* @see Connection#setReadOnly
172174
*/
173175
@Nullable
174176
public static Integer prepareConnectionForTransaction(Connection con, @Nullable TransactionDefinition definition)
@@ -220,8 +222,48 @@ public static Integer prepareConnectionForTransaction(Connection con, @Nullable
220222
* regarding read-only flag and isolation level.
221223
* @param con the Connection to reset
222224
* @param previousIsolationLevel the isolation level to restore, if any
225+
* @param resetReadOnly whether to reset the connection's read-only flag
226+
* @since 5.2.1
223227
* @see #prepareConnectionForTransaction
228+
* @see Connection#setTransactionIsolation
229+
* @see Connection#setReadOnly
224230
*/
231+
public static void resetConnectionAfterTransaction(
232+
Connection con, @Nullable Integer previousIsolationLevel, boolean resetReadOnly) {
233+
234+
Assert.notNull(con, "No Connection specified");
235+
try {
236+
// Reset transaction isolation to previous value, if changed for the transaction.
237+
if (previousIsolationLevel != null) {
238+
if (logger.isDebugEnabled()) {
239+
logger.debug("Resetting isolation level of JDBC Connection [" +
240+
con + "] to " + previousIsolationLevel);
241+
}
242+
con.setTransactionIsolation(previousIsolationLevel);
243+
}
244+
245+
// Reset read-only flag if we originally switched it to true on transaction begin.
246+
if (resetReadOnly) {
247+
if (logger.isDebugEnabled()) {
248+
logger.debug("Resetting read-only flag of JDBC Connection [" + con + "]");
249+
}
250+
con.setReadOnly(false);
251+
}
252+
}
253+
catch (Throwable ex) {
254+
logger.debug("Could not reset JDBC Connection after transaction", ex);
255+
}
256+
}
257+
258+
/**
259+
* Reset the given Connection after a transaction,
260+
* regarding read-only flag and isolation level.
261+
* @param con the Connection to reset
262+
* @param previousIsolationLevel the isolation level to restore, if any
263+
* @deprecated as of 5.1.11, in favor of
264+
* {@link #resetConnectionAfterTransaction(Connection, Integer, boolean)}
265+
*/
266+
@Deprecated
225267
public static void resetConnectionAfterTransaction(Connection con, @Nullable Integer previousIsolationLevel) {
226268
Assert.notNull(con, "No Connection specified");
227269
try {

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

+42-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2017 the original author or authors.
2+
* Copyright 2002-2019 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -57,35 +57,76 @@ public abstract class JdbcTransactionObjectSupport implements SavepointManager,
5757
@Nullable
5858
private Integer previousIsolationLevel;
5959

60+
private boolean readOnly = false;
61+
6062
private boolean savepointAllowed = false;
6163

6264

65+
/**
66+
* Set the ConnectionHolder for this transaction object.
67+
*/
6368
public void setConnectionHolder(@Nullable ConnectionHolder connectionHolder) {
6469
this.connectionHolder = connectionHolder;
6570
}
6671

72+
/**
73+
* Return the ConnectionHolder for this transaction object.
74+
*/
6775
public ConnectionHolder getConnectionHolder() {
6876
Assert.state(this.connectionHolder != null, "No ConnectionHolder available");
6977
return this.connectionHolder;
7078
}
7179

80+
/**
81+
* Check whether this transaction object has a ConnectionHolder.
82+
*/
7283
public boolean hasConnectionHolder() {
7384
return (this.connectionHolder != null);
7485
}
7586

87+
/**
88+
* Set the previous isolation level to retain, if any.
89+
*/
7690
public void setPreviousIsolationLevel(@Nullable Integer previousIsolationLevel) {
7791
this.previousIsolationLevel = previousIsolationLevel;
7892
}
7993

94+
/**
95+
* Return the retained previous isolation level, if any.
96+
*/
8097
@Nullable
8198
public Integer getPreviousIsolationLevel() {
8299
return this.previousIsolationLevel;
83100
}
84101

102+
/**
103+
* Set the read-only status of this transaction.
104+
* The default is {@code false}.
105+
* @since 5.2.1
106+
*/
107+
public void setReadOnly(boolean readOnly) {
108+
this.readOnly = readOnly;
109+
}
110+
111+
/**
112+
* Return the read-only status of this transaction.
113+
* @since 5.2.1
114+
*/
115+
public boolean isReadOnly() {
116+
return this.readOnly;
117+
}
118+
119+
/**
120+
* Set whether savepoints are allowed within this transaction.
121+
* The default is {@code false}.
122+
*/
85123
public void setSavepointAllowed(boolean savepointAllowed) {
86124
this.savepointAllowed = savepointAllowed;
87125
}
88126

127+
/**
128+
* Return whether savepoints are allowed within this transaction.
129+
*/
89130
public boolean isSavepointAllowed() {
90131
return this.savepointAllowed;
91132
}

spring-jdbc/src/test/java/org/springframework/jdbc/datasource/DataSourceTransactionManagerTests.java

+4-3
Original file line numberDiff line numberDiff line change
@@ -921,11 +921,13 @@ protected void doInTransactionWithoutResult(TransactionStatus status) {
921921
boolean condition = !TransactionSynchronizationManager.hasResource(ds);
922922
assertThat(condition).as("Hasn't thread connection").isTrue();
923923
InOrder ordered = inOrder(con);
924+
ordered.verify(con).setReadOnly(true);
924925
ordered.verify(con).setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE);
925926
ordered.verify(con).setAutoCommit(false);
926927
ordered.verify(con).commit();
927928
ordered.verify(con).setAutoCommit(true);
928929
ordered.verify(con).setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);
930+
ordered.verify(con).setReadOnly(false);
929931
verify(con).close();
930932
}
931933

@@ -954,11 +956,13 @@ protected void doInTransactionWithoutResult(TransactionStatus status) {
954956
boolean condition = !TransactionSynchronizationManager.hasResource(ds);
955957
assertThat(condition).as("Hasn't thread connection").isTrue();
956958
InOrder ordered = inOrder(con, stmt);
959+
ordered.verify(con).setReadOnly(true);
957960
ordered.verify(con).setAutoCommit(false);
958961
ordered.verify(stmt).executeUpdate("SET TRANSACTION READ ONLY");
959962
ordered.verify(stmt).close();
960963
ordered.verify(con).commit();
961964
ordered.verify(con).setAutoCommit(true);
965+
ordered.verify(con).setReadOnly(false);
962966
ordered.verify(con).close();
963967
}
964968

@@ -1437,7 +1441,6 @@ protected void doInTransactionWithoutResult(TransactionStatus status) throws Run
14371441
verify(con).rollback(sp);
14381442
verify(con).releaseSavepoint(sp);
14391443
verify(con).commit();
1440-
verify(con).isReadOnly();
14411444
verify(con).close();
14421445
}
14431446

@@ -1498,7 +1501,6 @@ protected void doInTransactionWithoutResult(TransactionStatus status) throws Run
14981501
verify(con).rollback(sp);
14991502
verify(con).releaseSavepoint(sp);
15001503
verify(con).commit();
1501-
verify(con).isReadOnly();
15021504
verify(con).close();
15031505
}
15041506

@@ -1559,7 +1561,6 @@ protected void doInTransactionWithoutResult(TransactionStatus status) throws Run
15591561
verify(con).rollback(sp);
15601562
verify(con).releaseSavepoint(sp);
15611563
verify(con).commit();
1562-
verify(con).isReadOnly();
15631564
verify(con).close();
15641565
}
15651566

spring-orm/src/main/java/org/springframework/orm/hibernate5/HibernateTransactionManager.java

+4-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2018 the original author or authors.
2+
* Copyright 2002-2019 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -481,6 +481,7 @@ protected void doBegin(Object transaction, TransactionDefinition definition) {
481481
Connection con = ((SessionImplementor) session).connection();
482482
Integer previousIsolationLevel = DataSourceUtils.prepareConnectionForTransaction(con, definition);
483483
txObject.setPreviousIsolationLevel(previousIsolationLevel);
484+
txObject.setReadOnly(definition.isReadOnly());
484485
if (this.allowResultAccessAfterCompletion && !txObject.isNewSession()) {
485486
int currentHoldability = con.getHoldability();
486487
if (currentHoldability != ResultSet.HOLD_CURSORS_OVER_COMMIT) {
@@ -712,7 +713,8 @@ protected void doCleanupAfterCompletion(Object transaction) {
712713
if (previousHoldability != null) {
713714
con.setHoldability(previousHoldability);
714715
}
715-
DataSourceUtils.resetConnectionAfterTransaction(con, txObject.getPreviousIsolationLevel());
716+
DataSourceUtils.resetConnectionAfterTransaction(
717+
con, txObject.getPreviousIsolationLevel(), txObject.isReadOnly());
716718
}
717719
catch (HibernateException ex) {
718720
logger.debug("Could not access JDBC Connection of Hibernate Session", ex);

spring-orm/src/main/java/org/springframework/orm/jpa/JpaTransactionManager.java

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2018 the original author or authors.
2+
* Copyright 2002-2019 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -402,6 +402,7 @@ protected void doBegin(Object transaction, TransactionDefinition definition) {
402402
Object transactionData = getJpaDialect().beginTransaction(em,
403403
new JpaTransactionDefinition(definition, timeoutToUse, txObject.isNewEntityManagerHolder()));
404404
txObject.setTransactionData(transactionData);
405+
txObject.setReadOnly(definition.isReadOnly());
405406

406407
// Register transaction timeout.
407408
if (timeoutToUse != TransactionDefinition.TIMEOUT_DEFAULT) {

spring-orm/src/main/java/org/springframework/orm/jpa/vendor/HibernateJpaDialect.java

+10-5
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2018 the original author or authors.
2+
* Copyright 2002-2019 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -194,7 +194,8 @@ else if (isolationLevelNeeded) {
194194
session.setDefaultReadOnly(true);
195195
}
196196
}
197-
return new SessionTransactionData(session, previousFlushMode, preparedCon, previousIsolationLevel);
197+
return new SessionTransactionData(
198+
session, previousFlushMode, preparedCon, previousIsolationLevel, definition.isReadOnly());
198199
}
199200

200201
@Override
@@ -203,7 +204,7 @@ public Object prepareTransaction(EntityManager entityManager, boolean readOnly,
203204

204205
Session session = getSession(entityManager);
205206
FlushMode previousFlushMode = prepareFlushMode(session, readOnly);
206-
return new SessionTransactionData(session, previousFlushMode, null, null);
207+
return new SessionTransactionData(session, previousFlushMode, null, null, readOnly);
207208
}
208209

209210
@SuppressWarnings("deprecation")
@@ -370,13 +371,16 @@ private static class SessionTransactionData {
370371
@Nullable
371372
private final Integer previousIsolationLevel;
372373

374+
private final boolean readOnly;
375+
373376
public SessionTransactionData(Session session, @Nullable FlushMode previousFlushMode,
374-
@Nullable Connection preparedCon, @Nullable Integer previousIsolationLevel) {
377+
@Nullable Connection preparedCon, @Nullable Integer previousIsolationLevel, boolean readOnly) {
375378

376379
this.session = session;
377380
this.previousFlushMode = previousFlushMode;
378381
this.preparedCon = preparedCon;
379382
this.previousIsolationLevel = previousIsolationLevel;
383+
this.readOnly = readOnly;
380384
}
381385

382386
@SuppressWarnings("deprecation")
@@ -392,7 +396,8 @@ public void resetSessionState() {
392396
"make sure to use connection release mode ON_CLOSE (the default) and to run against " +
393397
"Hibernate 4.2+ (or switch HibernateJpaDialect's prepareConnection flag to false");
394398
}
395-
DataSourceUtils.resetConnectionAfterTransaction(conToReset, this.previousIsolationLevel);
399+
DataSourceUtils.resetConnectionAfterTransaction(
400+
conToReset, this.previousIsolationLevel, this.readOnly);
396401
}
397402
}
398403
}

0 commit comments

Comments
 (0)