diff --git a/src/main/java/org/springframework/data/redis/core/IndexWriter.java b/src/main/java/org/springframework/data/redis/core/IndexWriter.java index a7f1cf5f60..3d3b048de5 100644 --- a/src/main/java/org/springframework/data/redis/core/IndexWriter.java +++ b/src/main/java/org/springframework/data/redis/core/IndexWriter.java @@ -33,23 +33,26 @@ /** * {@link IndexWriter} takes care of writing secondary index structures to - * Redis. Depending on the type of {@link IndexedData} it uses eg. Sets with specific names to add actually referenced - * keys to. While doing so {@link IndexWriter} also keeps track of all indexes associated with the root types key, which + * Redis. Depending on the type of {@link IndexedData}, it uses Sets with specific names to add actually referenced keys + * to. While doing so {@link IndexWriter} also keeps track of all indexes associated with the root types key, which * allows to remove the root key from all indexes in case of deletion. * * @author Christoph Strobl * @author Rob Winch + * @author Mark Paluch * @since 1.7 */ class IndexWriter { + private static final byte[] SEPARATOR = ":".getBytes(); + private static final byte[] IDX = "idx".getBytes(); + private final RedisConnection connection; private final RedisConverter converter; /** * Creates new {@link IndexWriter}. * - * @param keyspace The key space to write index values to. Must not be {@literal null}. * @param connection must not be {@literal null}. * @param converter must not be {@literal null}. */ @@ -127,7 +130,7 @@ public void removeKeyFromIndexes(String keyspace, Object key) { Assert.notNull(key, "Key must not be null"); byte[] binKey = toBytes(key); - byte[] indexHelperKey = ByteUtils.concatAll(toBytes(keyspace + ":"), binKey, toBytes(":idx")); + byte[] indexHelperKey = createIndexKey(keyspace, binKey); for (byte[] indexKey : connection.sMembers(indexHelperKey)) { @@ -147,10 +150,10 @@ public void removeKeyFromIndexes(String keyspace, Object key) { */ public void removeAllIndexes(String keyspace) { - Set potentialIndex = connection.keys(toBytes(keyspace + ":*")); + Set potentialIndex = connection.keys(createIndexKey(keyspace, "*")); if (!potentialIndex.isEmpty()) { - connection.del(potentialIndex.toArray(new byte[potentialIndex.size()][])); + connection.del(potentialIndex.toArray(new byte[0][])); } } @@ -162,7 +165,7 @@ private void removeKeyFromExistingIndexes(byte[] key, Iterable inde } /** - * Remove given key from all indexes matching {@link IndexedData#getIndexName()}: + * Remove given key from all indexes matching {@link IndexedData#getIndexName()}. * * @param key * @param indexedData @@ -171,8 +174,7 @@ protected void removeKeyFromExistingIndexes(byte[] key, IndexedData indexedData) Assert.notNull(indexedData, "IndexedData must not be null"); - Set existingKeys = connection - .keys(toBytes(indexedData.getKeyspace() + ":" + indexedData.getIndexName() + ":*")); + Set existingKeys = connection.keys(createIndexKey(indexedData.getKeyPrefix(), "*")); if (!CollectionUtils.isEmpty(existingKeys)) { for (byte[] existingKey : existingKeys) { @@ -216,12 +218,12 @@ protected void addKeyToIndex(byte[] key, IndexedData indexedData) { return; } - byte[] indexKey = toBytes(indexedData.getKeyspace() + ":" + indexedData.getIndexName() + ":"); + byte[] indexKey = toBytes(indexedData.getKeyPrefix(), SEPARATOR); indexKey = ByteUtils.concat(indexKey, toBytes(value)); connection.sAdd(indexKey, key); // keep track of indexes used for the object - connection.sAdd(ByteUtils.concatAll(toBytes(indexedData.getKeyspace() + ":"), key, toBytes(":idx")), indexKey); + connection.sAdd(createIndexKey(indexedData.getKeyspace(), key), indexKey); } else if (indexedData instanceof GeoIndexedPropertyValue propertyValue) { Object value = propertyValue.getValue(); @@ -229,17 +231,53 @@ protected void addKeyToIndex(byte[] key, IndexedData indexedData) { return; } - byte[] indexKey = toBytes(indexedData.getKeyspace() + ":" + indexedData.getIndexName()); + byte[] indexKey = toBytes(indexedData.getKeyPrefix()); connection.geoAdd(indexKey, propertyValue.getPoint(), key); // keep track of indexes used for the object - connection.sAdd(ByteUtils.concatAll(toBytes(indexedData.getKeyspace() + ":"), key, toBytes(":idx")), indexKey); + connection.sAdd(createIndexKey(indexedData.getKeyspace(), key), indexKey); } else { throw new IllegalArgumentException( String.format("Cannot write index data for unknown index type %s", indexedData.getClass())); } } + private byte[] createIndexKey(String keyspace, byte[] key) { + return createIndexKey(keyspace, key, IDX); + } + + private byte[] createIndexKey(Object... items) { + + Object[] elements = new Object[items.length + (items.length - 1)]; + + int j = 0; + for (int i = 0; i < items.length; i++) { + + elements[j++] = items[i]; + if (items.length - 1 > i) { + elements[j++] = SEPARATOR; + } + } + + return toBytes(elements); + } + + private byte[] toBytes(Object... values) { + + byte[][] arrays = new byte[values.length][]; + + for (int i = 0; i < values.length; i++) { + + if (values[i] instanceof byte[] bb) { + arrays[i] = bb; + } else { + arrays[i] = toBytes(values[i]); + } + } + + return ByteUtils.concatAll(arrays); + } + private byte[] toBytes(@Nullable Object source) { if (source == null) { diff --git a/src/main/java/org/springframework/data/redis/core/RedisQueryEngine.java b/src/main/java/org/springframework/data/redis/core/RedisQueryEngine.java index 07b946d2b9..64dfe66e97 100644 --- a/src/main/java/org/springframework/data/redis/core/RedisQueryEngine.java +++ b/src/main/java/org/springframework/data/redis/core/RedisQueryEngine.java @@ -25,6 +25,7 @@ import java.util.Map; import java.util.Set; +import org.springframework.core.convert.ConversionService; import org.springframework.data.geo.Circle; import org.springframework.data.geo.GeoResult; import org.springframework.data.geo.GeoResults; @@ -81,8 +82,8 @@ private RedisQueryEngine(CriteriaAccessor criteriaAccessor, @Override @SuppressWarnings("unchecked") - public List execute(RedisOperationChain criteria, Comparator sort, long offset, int rows, - String keyspace, Class type) { + public List execute(RedisOperationChain criteria, Comparator sort, long offset, int rows, String keyspace, + Class type) { List result = doFind(criteria, offset, rows, keyspace, type); if (sort != null) { @@ -199,8 +200,7 @@ private List findKeys(RedisOperationChain criteria, int rows, String key } @Override - public List execute(RedisOperationChain criteria, Comparator sort, long offset, int rows, - String keyspace) { + public List execute(RedisOperationChain criteria, Comparator sort, long offset, int rows, String keyspace) { return execute(criteria, sort, offset, rows, keyspace, Object.class); } @@ -229,14 +229,13 @@ public long count(RedisOperationChain criteria, String keyspace) { private byte[][] keys(String prefix, Collection source) { + ConversionService conversionService = getRequiredAdapter().getConverter().getConversionService(); byte[][] keys = new byte[source.size()][]; int i = 0; for (PathAndValue pathAndValue : source) { - byte[] convertedValue = getRequiredAdapter().getConverter().getConversionService() - .convert(pathAndValue.getFirstValue(), byte[].class); - byte[] fullPath = getRequiredAdapter().getConverter().getConversionService() - .convert(prefix + pathAndValue.getPath() + ":", byte[].class); + byte[] convertedValue = conversionService.convert(pathAndValue.getFirstValue(), byte[].class); + byte[] fullPath = conversionService.convert(prefix + pathAndValue.getPath() + ":", byte[].class); keys[i] = ByteUtils.concat(fullPath, convertedValue); i++; diff --git a/src/main/java/org/springframework/data/redis/core/convert/IndexedData.java b/src/main/java/org/springframework/data/redis/core/convert/IndexedData.java index e6c0fce9e5..122bdfd2c7 100644 --- a/src/main/java/org/springframework/data/redis/core/convert/IndexedData.java +++ b/src/main/java/org/springframework/data/redis/core/convert/IndexedData.java @@ -38,4 +38,13 @@ public interface IndexedData { */ String getKeyspace(); + /** + * Return the key prefix for usage in Redis. + * + * @return concatenated form of the keyspace and the index name. + * @since 3.3.4 + */ + default String getKeyPrefix() { + return getKeyspace() + ":" + getIndexName(); + } }