Skip to content

Commit ae2042b

Browse files
mp911dechristophstrobl
authored andcommitted
DATAREDIS-988 - Consider transaction participation when releasing connections.
We now consider RedisTemplate's enableTransactionSupport configuration when releasing connections. Previously, we released bound connections when being in a read-only transaction regardless the configuration in RedisTemplate. This broke session callbacks that expected to remain on the same connection all but the first command were executed on a different connection. Releasing a connection now no longer unbinds a connection if transaction support is disabled. Original Pull Request: #453
1 parent da1de92 commit ae2042b

File tree

3 files changed

+98
-6
lines changed

3 files changed

+98
-6
lines changed

src/main/java/org/springframework/data/redis/core/RedisConnectionUtils.java

Lines changed: 35 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -187,8 +187,24 @@ private static RedisConnection createConnectionProxy(RedisConnection connection,
187187
*
188188
* @param conn the Redis connection to close.
189189
* @param factory the Redis factory that the connection was created with.
190+
* @deprecated since 2.1.9, use {@link #releaseConnection(RedisConnection, RedisConnectionFactory, boolean)}
190191
*/
192+
@Deprecated
191193
public static void releaseConnection(@Nullable RedisConnection conn, RedisConnectionFactory factory) {
194+
releaseConnection(conn, factory, false);
195+
}
196+
197+
/**
198+
* Closes the given connection, created via the given factory if not managed externally (i.e. not bound to the
199+
* thread).
200+
*
201+
* @param conn the Redis connection to close.
202+
* @param factory the Redis factory that the connection was created with.
203+
* @param enableTransactionSupport whether transaction support is enabled.
204+
* @since 2.1.9
205+
*/
206+
public static void releaseConnection(@Nullable RedisConnection conn, RedisConnectionFactory factory,
207+
boolean enableTransactionSupport) {
192208

193209
if (conn == null) {
194210
return;
@@ -203,11 +219,25 @@ public static void releaseConnection(@Nullable RedisConnection conn, RedisConnec
203219
return;
204220
}
205221

206-
// release transactional/read-only and non-transactional/non-bound connections.
207-
// transactional connections for read-only transactions get no synchronizer registered
208-
if (isConnectionTransactional(conn, factory) && TransactionSynchronizationManager.isCurrentTransactionReadOnly()) {
209-
unbindConnection(factory);
210-
} else if (!isConnectionTransactional(conn, factory)) {
222+
if (isConnectionTransactional(conn, factory)) {
223+
224+
// release transactional/read-only and non-transactional/non-bound connections.
225+
// transactional connections for read-only transactions get no synchronizer registered
226+
if (enableTransactionSupport && TransactionSynchronizationManager.isCurrentTransactionReadOnly()) {
227+
if (log.isDebugEnabled()) {
228+
log.debug("Unbinding Redis Connection");
229+
}
230+
unbindConnection(factory);
231+
} else {
232+
233+
// Not participating in transaction management.
234+
// Connection could have been attached via session callback.
235+
if (log.isDebugEnabled()) {
236+
log.debug("Leaving bound Redis Connection attached");
237+
}
238+
}
239+
240+
} else {
211241
if (log.isDebugEnabled()) {
212242
log.debug("Closing Redis Connection");
213243
}

src/main/java/org/springframework/data/redis/core/RedisTemplate.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -234,7 +234,7 @@ public <T> T execute(RedisCallback<T> action, boolean exposeConnection, boolean
234234
// TODO: any other connection processing?
235235
return postProcessResult(result, connToUse, existingConnection);
236236
} finally {
237-
RedisConnectionUtils.releaseConnection(conn, factory);
237+
RedisConnectionUtils.releaseConnection(conn, factory, enableTransactionSupport);
238238
}
239239
}
240240

src/test/java/org/springframework/data/redis/core/RedisTemplateUnitTests.java

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,14 +28,20 @@
2828
import org.junit.runner.RunWith;
2929
import org.mockito.Mock;
3030
import org.mockito.junit.MockitoJUnitRunner;
31+
3132
import org.springframework.dao.DataAccessException;
3233
import org.springframework.data.redis.connection.RedisConnection;
3334
import org.springframework.data.redis.connection.RedisConnectionFactory;
3435
import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer;
3536
import org.springframework.instrument.classloading.ShadowingClassLoader;
37+
import org.springframework.lang.Nullable;
38+
import org.springframework.transaction.support.TransactionSynchronizationManager;
3639

3740
/**
41+
* Unit tests for {@link RedisTemplate}.
42+
*
3843
* @author Christoph Strobl
44+
* @author Mark Paluch
3945
*/
4046
@RunWith(MockitoJUnitRunner.class)
4147
public class RedisTemplateUnitTests {
@@ -47,6 +53,8 @@ public class RedisTemplateUnitTests {
4753
@Before
4854
public void setUp() {
4955

56+
TransactionSynchronizationManager.clear();
57+
5058
template = new RedisTemplate<>();
5159
template.setConnectionFactory(connectionFactoryMock);
5260
when(connectionFactoryMock.getConnection()).thenReturn(redisConnectionMock);
@@ -96,6 +104,60 @@ public void executeWithStickyConnectionShouldNotCloseConnectionWhenDone() {
96104
verify(redisConnectionMock, never()).close();
97105
}
98106

107+
@Test // DATAREDIS-988
108+
public void executeSessionShouldReuseConnection() {
109+
110+
template.execute(new SessionCallback<Object>() {
111+
@Nullable
112+
@Override
113+
public <K, V> Object execute(RedisOperations<K, V> operations) throws DataAccessException {
114+
template.multi();
115+
template.multi();
116+
return null;
117+
}
118+
});
119+
120+
verify(connectionFactoryMock).getConnection();
121+
verify(redisConnectionMock).close();
122+
}
123+
124+
@Test // DATAREDIS-988
125+
public void executeSessionInTransactionShouldReuseConnection() {
126+
127+
TransactionSynchronizationManager.setCurrentTransactionReadOnly(true);
128+
129+
template.execute(new SessionCallback<Object>() {
130+
@Override
131+
public <K, V> Object execute(RedisOperations<K, V> operations) throws DataAccessException {
132+
template.multi();
133+
template.multi();
134+
return null;
135+
}
136+
});
137+
138+
verify(connectionFactoryMock).getConnection();
139+
verify(redisConnectionMock).close();
140+
}
141+
142+
@Test // DATAREDIS-988
143+
public void transactionAwareTemplateShouldReleaseConnection() {
144+
145+
template.setEnableTransactionSupport(true);
146+
TransactionSynchronizationManager.setCurrentTransactionReadOnly(true);
147+
148+
template.execute(new SessionCallback<Object>() {
149+
@Override
150+
public <K, V> Object execute(RedisOperations<K, V> operations) throws DataAccessException {
151+
template.multi();
152+
template.multi();
153+
return null;
154+
}
155+
});
156+
157+
verify(connectionFactoryMock, times(2)).getConnection();
158+
verify(redisConnectionMock, times(2)).close();
159+
}
160+
99161
static class SomeArbitrarySerializableObject implements Serializable {
100162
private static final long serialVersionUID = -5973659324040506423L;
101163
}

0 commit comments

Comments
 (0)