2222import java .sql .SQLException ;
2323import java .util .LinkedHashMap ;
2424import java .util .Map ;
25+ import java .util .Set ;
2526import java .util .stream .Stream ;
2627
2728import javax .sql .DataSource ;
4243import org .springframework .data .redis .connection .RedisConnectionFactory ;
4344import org .springframework .data .redis .connection .jedis .JedisConnectionFactory ;
4445import org .springframework .data .redis .connection .lettuce .LettuceConnectionFactory ;
46+ import org .springframework .data .redis .core .ZSetOperations .TypedTuple ;
4547import org .springframework .jdbc .datasource .DataSourceTransactionManager ;
4648import org .springframework .transaction .support .TransactionTemplate ;
4749
@@ -117,8 +119,9 @@ void visibilityDuringManagedTransaction() throws SQLException {
117119 .containsEntry ("isMember(inside)" , false );
118120 }
119121
122+ @ SuppressWarnings ("unchecked" )
120123 @ Test // GH-3187
121- void allRangeWithScoresMethodsInTransactionShouldNotReturnNull () throws SQLException {
124+ void allRangeWithScoresMethodsShouldExecuteImmediatelyInTransaction () throws SQLException {
122125
123126 DataSource ds = mock (DataSource .class );
124127 when (ds .getConnection ()).thenReturn (mock (Connection .class ));
@@ -127,23 +130,83 @@ void allRangeWithScoresMethodsInTransactionShouldNotReturnNull() throws SQLExcep
127130 TransactionTemplate txTemplate = new TransactionTemplate (txMgr );
128131 txTemplate .afterPropertiesSet ();
129132
130- stringTemplate .opsForZSet ().add ("testzset" , "member1" , 1.0 );
131- stringTemplate .opsForZSet ().add ("testzset" , "member2" , 2.0 );
133+ // Add data outside transaction
134+ stringTemplate .opsForZSet ().add ("testzset" , "outside1" , 1.0 );
135+ stringTemplate .opsForZSet ().add ("testzset" , "outside2" , 2.0 );
132136
133137 Map <String , Object > result = txTemplate .execute (x -> {
134138 Map <String , Object > ops = new LinkedHashMap <>();
135- ops .put ("rangeWithScores" , stringTemplate .opsForZSet ().rangeWithScores ("testzset" , 0 , -1 ));
136- ops .put ("reverseRangeWithScores" , stringTemplate .opsForZSet ().reverseRangeWithScores ("testzset" , 0 , -1 ));
137- ops .put ("rangeByScoreWithScores" , stringTemplate .opsForZSet ().rangeByScoreWithScores ("testzset" , 1.0 , 2.0 ));
138- ops .put ("reverseRangeByScoreWithScores" , stringTemplate .opsForZSet ().reverseRangeByScoreWithScores ("testzset" , 1.0 , 2.0 ));
139+
140+ // Query data added outside transaction (should execute immediately)
141+ ops .put ("rangeWithScores_before" ,
142+ stringTemplate .opsForZSet ().rangeWithScores ("testzset" , 0 , -1 ));
143+ ops .put ("reverseRangeWithScores_before" ,
144+ stringTemplate .opsForZSet ().reverseRangeWithScores ("testzset" , 0 , -1 ));
145+ ops .put ("rangeByScoreWithScores_before" ,
146+ stringTemplate .opsForZSet ().rangeByScoreWithScores ("testzset" , 1.0 , 2.0 ));
147+ ops .put ("reverseRangeByScoreWithScores_before" ,
148+ stringTemplate .opsForZSet ().reverseRangeByScoreWithScores ("testzset" , 1.0 , 2.0 ));
149+
150+ // Add inside transaction (goes into multi/exec queue)
151+ ops .put ("add_result" , stringTemplate .opsForZSet ().add ("testzset" , "inside" , 3.0 ));
152+
153+ // Changes made inside transaction should not be visible yet (read executes immediately)
154+ ops .put ("rangeWithScores_after" ,
155+ stringTemplate .opsForZSet ().rangeWithScores ("testzset" , 0 , -1 ));
156+ ops .put ("reverseRangeWithScores_after" ,
157+ stringTemplate .opsForZSet ().reverseRangeWithScores ("testzset" , 0 , -1 ));
158+ ops .put ("rangeByScoreWithScores_after" ,
159+ stringTemplate .opsForZSet ().rangeByScoreWithScores ("testzset" , 1.0 , 3.0 ));
160+ ops .put ("reverseRangeByScoreWithScores_after" ,
161+ stringTemplate .opsForZSet ().reverseRangeByScoreWithScores ("testzset" , 1.0 , 3.0 ));
162+
139163 return ops ;
140164 });
141165
142- // Issue #3187: All should return data, not null
143- assertThat (result .get ("rangeWithScores" )).isNotNull ();
144- assertThat (result .get ("reverseRangeWithScores" )).isNotNull ();
145- assertThat (result .get ("rangeByScoreWithScores" )).isNotNull ();
146- assertThat (result .get ("reverseRangeByScoreWithScores" )).isNotNull ();
166+ // add result is null (no result until exec)
167+ assertThat (result ).containsEntry ("add_result" , null );
168+
169+ // before: only data added outside transaction is visible
170+ assertThat ((Set <TypedTuple <String >>) result .get ("rangeWithScores_before" ))
171+ .hasSize (2 )
172+ .extracting (TypedTuple ::getValue )
173+ .containsExactly ("outside1" , "outside2" );
174+
175+ assertThat ((Set <TypedTuple <String >>) result .get ("reverseRangeWithScores_before" ))
176+ .hasSize (2 )
177+ .extracting (TypedTuple ::getValue )
178+ .containsExactly ("outside2" , "outside1" );
179+
180+ assertThat ((Set <TypedTuple <String >>) result .get ("rangeByScoreWithScores_before" ))
181+ .hasSize (2 )
182+ .extracting (TypedTuple ::getValue )
183+ .containsExactly ("outside1" , "outside2" );
184+
185+ assertThat ((Set <TypedTuple <String >>) result .get ("reverseRangeByScoreWithScores_before" ))
186+ .hasSize (2 )
187+ .extracting (TypedTuple ::getValue )
188+ .containsExactly ("outside2" , "outside1" );
189+
190+ // after: changes made inside transaction are still not visible
191+ assertThat ((Set <TypedTuple <String >>) result .get ("rangeWithScores_after" ))
192+ .hasSize (2 )
193+ .extracting (TypedTuple ::getValue )
194+ .containsExactly ("outside1" , "outside2" );
195+
196+ assertThat ((Set <TypedTuple <String >>) result .get ("reverseRangeWithScores_after" ))
197+ .hasSize (2 )
198+ .extracting (TypedTuple ::getValue )
199+ .containsExactly ("outside2" , "outside1" );
200+
201+ assertThat ((Set <TypedTuple <String >>) result .get ("rangeByScoreWithScores_after" ))
202+ .hasSize (2 )
203+ .extracting (TypedTuple ::getValue )
204+ .containsExactly ("outside1" , "outside2" );
205+
206+ assertThat ((Set <TypedTuple <String >>) result .get ("reverseRangeByScoreWithScores_after" ))
207+ .hasSize (2 )
208+ .extracting (TypedTuple ::getValue )
209+ .containsExactly ("outside2" , "outside1" );
147210 }
148211
149212 static Stream <Arguments > argumentsStream () {
0 commit comments