Skip to content

Commit f28a23a

Browse files
committed
Use by-id lookup for queries referring to identifier values.
Closes #2851
1 parent 0c9d255 commit f28a23a

File tree

2 files changed

+116
-6
lines changed

2 files changed

+116
-6
lines changed

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

Lines changed: 54 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -33,18 +33,20 @@
3333
import org.springframework.data.keyvalue.core.SortAccessor;
3434
import org.springframework.data.keyvalue.core.SpelSortAccessor;
3535
import org.springframework.data.keyvalue.core.query.KeyValueQuery;
36+
import org.springframework.data.mapping.PersistentPropertyPath;
3637
import org.springframework.data.redis.connection.RedisConnection;
3738
import org.springframework.data.redis.connection.RedisGeoCommands.GeoLocation;
3839
import org.springframework.data.redis.connection.RedisGeoCommands.GeoRadiusCommandArgs;
3940
import org.springframework.data.redis.connection.util.ByteArrayWrapper;
4041
import org.springframework.data.redis.core.convert.GeoIndexedPropertyValue;
42+
import org.springframework.data.redis.core.convert.RedisConverter;
4143
import org.springframework.data.redis.core.convert.RedisData;
44+
import org.springframework.data.redis.core.mapping.RedisPersistentProperty;
4245
import org.springframework.data.redis.repository.query.RedisOperationChain;
4346
import org.springframework.data.redis.repository.query.RedisOperationChain.NearPath;
4447
import org.springframework.data.redis.repository.query.RedisOperationChain.PathAndValue;
4548
import org.springframework.data.redis.util.ByteUtils;
4649
import org.springframework.expression.spel.standard.SpelExpressionParser;
47-
import org.springframework.lang.NonNullApi;
4850
import org.springframework.lang.Nullable;
4951
import org.springframework.util.CollectionUtils;
5052

@@ -100,7 +102,7 @@ private <T> List<T> doFind(RedisOperationChain criteria, long offset, int rows,
100102

101103
RedisCallback<Map<byte[], Map<byte[], byte[]>>> callback = connection -> {
102104

103-
List<byte[]> keys = findKeys(criteria, rows, keyspace, connection);
105+
List<byte[]> keys = findKeys(criteria, rows, keyspace, type, connection);
104106
byte[] keyspaceBin = getRequiredAdapter().getConverter().getConversionService().convert(keyspace + ":",
105107
byte[].class);
106108

@@ -143,18 +145,35 @@ private <T> List<T> doFind(RedisOperationChain criteria, long offset, int rows,
143145
return result;
144146
}
145147

146-
private List<byte[]> findKeys(RedisOperationChain criteria, int rows, String keyspace, RedisConnection connection) {
148+
private List<byte[]> findKeys(RedisOperationChain criteria, int rows, String keyspace, Class<?> domainType,
149+
RedisConnection connection) {
147150

148151
List<byte[]> allKeys = new ArrayList<>();
149152

150153
if (!criteria.getSismember().isEmpty()) {
151-
allKeys.addAll(connection.sInter(keys(keyspace + ":", criteria.getSismember())));
154+
155+
Set<PathAndValue> sismember = criteria.getSismember();
156+
if (sismember.size() == 1) {
157+
KeySelector keySelector = KeySelector.of(getRequiredAdapter().getConverter(), sismember, domainType);
158+
if (!keySelector.setValueLookup().isEmpty()) {
159+
allKeys.addAll(connection.sInter(keys(keyspace + ":", keySelector.setValueLookup())));
160+
}
161+
162+
allKeys.addAll(keySelector.keys());
163+
} else {
164+
allKeys.addAll(connection.sInter(keys(keyspace + ":", sismember)));
165+
}
152166
}
153167

154-
if (!criteria.getOrSismember().isEmpty()) {
168+
KeySelector keySelector = KeySelector.of(getRequiredAdapter().getConverter(), criteria.getOrSismember(),
169+
domainType);
170+
171+
if (!keySelector.setValueLookup().isEmpty()) {
155172
allKeys.addAll(connection.sUnion(keys(keyspace + ":", criteria.getOrSismember())));
156173
}
157174

175+
allKeys.addAll(keySelector.keys());
176+
158177
if (criteria.getNear() != null) {
159178

160179
GeoRadiusCommandArgs limit = GeoRadiusCommandArgs.newGeoRadiusArgs();
@@ -170,7 +189,6 @@ private List<byte[]> findKeys(RedisOperationChain criteria, int rows, String key
170189
}
171190
}
172191

173-
174192
Set<ByteArrayWrapper> unique = new LinkedHashSet<>(allKeys.size());
175193
allKeys.forEach(key -> unique.add(new ByteArrayWrapper(key)));
176194

@@ -244,4 +262,34 @@ public RedisOperationChain resolve(KeyValueQuery<?> query) {
244262
return (RedisOperationChain) query.getCriteria();
245263
}
246264
}
265+
266+
/**
267+
* Value object capturing the direct object keys and set of values that need to be looked up from the secondary
268+
* indexes.
269+
*
270+
* @param keys
271+
* @param setValueLookup
272+
*/
273+
record KeySelector(Collection<byte[]> keys, Set<PathAndValue> setValueLookup) {
274+
275+
static KeySelector of(RedisConverter converter, Set<PathAndValue> pathAndValues, Class<?> domainType) {
276+
277+
Set<byte[]> keys = new LinkedHashSet<>();
278+
Set<PathAndValue> remainder = new LinkedHashSet<>();
279+
280+
for (PathAndValue pathAndValue : pathAndValues) {
281+
282+
PersistentPropertyPath<RedisPersistentProperty> path = converter.getMappingContext()
283+
.getPersistentPropertyPath(pathAndValue.getPath(), domainType);
284+
if (path.getLeafProperty().isIdProperty()) {
285+
byte[] key = converter.getConversionService().convert(pathAndValue.getFirstValue(), byte[].class);
286+
keys.add(key);
287+
} else {
288+
remainder.add(pathAndValue);
289+
}
290+
}
291+
292+
return new KeySelector(keys, remainder);
293+
}
294+
}
247295
}

src/test/java/org/springframework/data/redis/repository/RedisRepositoryIntegrationTestBase.java

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727

2828
import org.junit.jupiter.api.BeforeEach;
2929
import org.junit.jupiter.api.Test;
30+
3031
import org.springframework.beans.factory.annotation.Autowired;
3132
import org.springframework.data.annotation.Id;
3233
import org.springframework.data.annotation.Reference;
@@ -101,6 +102,57 @@ void simpleFindShouldReturnEntitiesCorrectly() {
101102
assertThat(repo.findByLastname("al'thor")).contains(rand);
102103
}
103104

105+
@Test // GH-2851
106+
void shouldReturnSingleEntityByIdViaQueryMethod() {
107+
108+
Person rand = new Person();
109+
rand.firstname = "rand";
110+
rand.lastname = "al'thor";
111+
112+
Person egwene = new Person();
113+
egwene.firstname = "egwene";
114+
115+
repo.saveAll(Arrays.asList(rand, egwene));
116+
117+
assertThat(repo.findEntityById(rand.getId())).isEqualTo(rand);
118+
assertThat(repo.findEntityById(egwene.getId())).isEqualTo(egwene);
119+
}
120+
121+
@Test // GH-2851
122+
void shouldProjectSingleResult() {
123+
124+
Person rand = new Person();
125+
rand.firstname = "rand";
126+
rand.lastname = "al'thor";
127+
128+
Person egwene = new Person();
129+
egwene.firstname = "egwene";
130+
131+
repo.saveAll(Arrays.asList(rand, egwene));
132+
133+
PersonProjection projectionById = repo.findProjectionById(rand.getId());
134+
assertThat(projectionById).isNotNull();
135+
assertThat(projectionById.getFirstname()).isEqualTo(rand.firstname);
136+
}
137+
138+
@Test // GH-2851
139+
void shouldProjectCollection() {
140+
141+
Person rand = new Person();
142+
rand.firstname = "rand";
143+
rand.lastname = "al'thor";
144+
145+
Person egwene = new Person();
146+
egwene.firstname = "egwene";
147+
148+
repo.saveAll(Arrays.asList(rand, egwene));
149+
150+
List<PersonProjection> projectionById = repo.findProjectionBy();
151+
assertThat(projectionById).hasSize(2) //
152+
.extracting(PersonProjection::getFirstname) //
153+
.contains(rand.getFirstname(), egwene.getFirstname());
154+
}
155+
104156
@Test // DATAREDIS-425
105157
void simpleFindByMultipleProperties() {
106158

@@ -570,10 +622,20 @@ public interface PersonRepository extends PagingAndSortingRepository<Person, Str
570622

571623
Slice<Person> findByHometownLocationNear(Point point, Distance distance, Pageable pageable);
572624

625+
Person findEntityById(String id);
626+
627+
PersonProjection findProjectionById(String id);
628+
629+
List<PersonProjection> findProjectionBy();
630+
573631
@Override
574632
<S extends Person> List<S> findAll(Example<S> example);
575633
}
576634

635+
public interface PersonProjection {
636+
String getFirstname();
637+
}
638+
577639
public interface CityRepository extends CrudRepository<City, String> {
578640

579641
List<City> findByLocationNear(Point point, Distance distance);

0 commit comments

Comments
 (0)