From 0691af7ca4c70baf07b076574e7cab3ea752c744 Mon Sep 17 00:00:00 2001 From: Costin Leau Date: Fri, 15 Jul 2011 19:11:35 +0300 Subject: [PATCH] DATAREDIS-72 + add prefix strategy --- .../redis/cache/DefaultRedisCachePrefix.java | 44 ++++++++++++++++++ .../data/redis/cache/RedisCache.java | 46 +++++++------------ .../data/redis/cache/RedisCacheManager.java | 16 ++++++- .../data/redis/cache/RedisCachePrefix.java | 37 +++++++++++++++ .../data/redis/cache/RedisCacheTest.java | 2 +- .../collections/CollectionTestParams.java | 14 +++--- 6 files changed, 121 insertions(+), 38 deletions(-) create mode 100644 src/main/java/org/springframework/data/redis/cache/DefaultRedisCachePrefix.java create mode 100644 src/main/java/org/springframework/data/redis/cache/RedisCachePrefix.java diff --git a/src/main/java/org/springframework/data/redis/cache/DefaultRedisCachePrefix.java b/src/main/java/org/springframework/data/redis/cache/DefaultRedisCachePrefix.java new file mode 100644 index 000000000..ba670fa30 --- /dev/null +++ b/src/main/java/org/springframework/data/redis/cache/DefaultRedisCachePrefix.java @@ -0,0 +1,44 @@ +/* + * Copyright 2011 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.data.redis.cache; + +import org.springframework.data.redis.serializer.RedisSerializer; +import org.springframework.data.redis.serializer.StringRedisSerializer; + +/** + * Default implementation for {@link RedisCachePrefix} which uses the given cache name and a delimiter for creating the prefix. + * + * @author Costin Leau + */ +public class DefaultRedisCachePrefix implements RedisCachePrefix { + + private final RedisSerializer serializer = new StringRedisSerializer(); + private final String delimiter; + + public DefaultRedisCachePrefix() { + this(":"); + } + + public DefaultRedisCachePrefix(String delimiter) { + this.delimiter = delimiter; + } + + @Override + public byte[] prefix(String cacheName) { + return serializer.serialize((delimiter != null ? cacheName.concat(":") : cacheName)); + } +} diff --git a/src/main/java/org/springframework/data/redis/cache/RedisCache.java b/src/main/java/org/springframework/data/redis/cache/RedisCache.java index 2e34accf8..dec3e29de 100644 --- a/src/main/java/org/springframework/data/redis/cache/RedisCache.java +++ b/src/main/java/org/springframework/data/redis/cache/RedisCache.java @@ -23,13 +23,9 @@ import org.springframework.cache.Cache; import org.springframework.cache.interceptor.DefaultValueWrapper; import org.springframework.dao.DataAccessException; import org.springframework.data.redis.connection.RedisConnection; -import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.core.RedisCallback; import org.springframework.data.redis.core.RedisTemplate; -import org.springframework.data.redis.serializer.RedisSerializer; import org.springframework.data.redis.serializer.StringRedisSerializer; -import org.springframework.data.redis.support.collections.DefaultRedisSet; -import org.springframework.data.redis.support.collections.RedisSet; import org.springframework.util.Assert; /** @@ -38,12 +34,11 @@ import org.springframework.util.Assert; * @author Costin Leau */ @SuppressWarnings("unchecked") -public class RedisCache implements Cache { +class RedisCache implements Cache { private final String name; - private final RedisSet keys; private final RedisTemplate template; - private final byte[] nameAsBytes; + private final byte[] prefix; private final byte[] setName; /** @@ -51,30 +46,20 @@ public class RedisCache implements Cache { * Constructs a new RedisCache instance. * * @param name cache name + * @param prefix + * @param cachePrefix */ - public RedisCache(String name, RedisTemplate template) { + RedisCache(String name, byte[] prefix, RedisTemplate template) { + Assert.hasText(name, "non-empty cache name is required"); this.name = name; this.template = template; + this.prefix = prefix; StringRedisSerializer stringSerializer = new StringRedisSerializer(); - this.nameAsBytes = stringSerializer.serialize(name + ":"); - - - // extract connection and create an ops for set - RedisConnectionFactory connectionFactory = template.getConnectionFactory(); - RedisTemplate setTemplate = new RedisTemplate(); - setTemplate.setConnectionFactory(connectionFactory); - setTemplate.setDefaultSerializer(template.getDefaultSerializer()); - setTemplate.setKeySerializer(stringSerializer); - - // the values of the set are the cache keys - setTemplate.setValueSerializer(template.getKeySerializer()); - setTemplate.afterPropertiesSet(); - - String sName = name + "-keys"; - this.keys = new DefaultRedisSet(sName, setTemplate); + // name of the set holding the keys + String sName = name + "~keys"; this.setName = stringSerializer.serialize(sName); } @@ -108,7 +93,7 @@ public class RedisCache implements Cache { @Override public void put(final Object key, final Object value) { final byte[] k = computeKey(key); - + template.execute(new RedisCallback() { public Object doInRedis(RedisConnection connection) throws DataAccessException { connection.set(k, template.getValueSerializer().serialize(value)); @@ -135,6 +120,7 @@ public class RedisCache implements Cache { @Override public void clear() { // need to del each key individually + // TODO: change this to a sorted set to allow pagination template.execute(new RedisCallback() { public Object doInRedis(RedisConnection connection) throws DataAccessException { // need to paginate the keys @@ -149,11 +135,13 @@ public class RedisCache implements Cache { } private byte[] computeKey(Object key) { - RedisSerializer keySerializer = template.getKeySerializer(); - byte[] k = keySerializer.serialize(key); + byte[] k = template.getKeySerializer().serialize(key); - byte[] result = Arrays.copyOf(nameAsBytes, nameAsBytes.length + k.length); - System.arraycopy(k, 0, result, nameAsBytes.length, k.length); + if (prefix == null || prefix.length == 0) + return k; + + byte[] result = Arrays.copyOf(prefix, prefix.length + k.length); + System.arraycopy(k, 0, result, prefix.length, k.length); return result; } } \ No newline at end of file diff --git a/src/main/java/org/springframework/data/redis/cache/RedisCacheManager.java b/src/main/java/org/springframework/data/redis/cache/RedisCacheManager.java index ece643021..4704ca115 100644 --- a/src/main/java/org/springframework/data/redis/cache/RedisCacheManager.java +++ b/src/main/java/org/springframework/data/redis/cache/RedisCacheManager.java @@ -27,6 +27,8 @@ import org.springframework.data.redis.core.RedisTemplate; /** * CacheManager implementation for Redis. + * By default saves the keys by appending a prefix (which acts as a namespace). For performance reasons, the current implementation + * uses a set for the keys in each cache. * * @author Costin Leau */ @@ -37,6 +39,9 @@ public class RedisCacheManager implements CacheManager { private final Collection names = Collections.unmodifiableSet(caches.keySet()); private final RedisTemplate template; + private boolean usePrefix; + private RedisCachePrefix cachePrefix = new DefaultRedisCachePrefix(); + public RedisCacheManager(RedisTemplate template) { this.template = template; } @@ -44,7 +49,7 @@ public class RedisCacheManager implements CacheManager { public Cache getCache(String name) { Cache c = caches.get(name); if (c == null) { - c = new RedisCache(name, template); + c = new RedisCache(name, (usePrefix ? cachePrefix.prefix(name) : null), template); caches.put(name, c); names.add(name); } @@ -55,4 +60,13 @@ public class RedisCacheManager implements CacheManager { public Collection getCacheNames() { return names; } + + /** + * Sets the cachePrefix. + * + * @param cachePrefix the cachePrefix to set + */ + public void setCachePrefix(RedisCachePrefix cachePrefix) { + this.cachePrefix = cachePrefix; + } } \ No newline at end of file diff --git a/src/main/java/org/springframework/data/redis/cache/RedisCachePrefix.java b/src/main/java/org/springframework/data/redis/cache/RedisCachePrefix.java new file mode 100644 index 000000000..985d5c58c --- /dev/null +++ b/src/main/java/org/springframework/data/redis/cache/RedisCachePrefix.java @@ -0,0 +1,37 @@ +/* + * Copyright 2011 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.data.redis.cache; + +/** + * Contract for generating 'prefixes' for Cache keys saved in Redis. + * Due to the 'flat' nature of the Redis storage, the prefix is used as a 'namespace' for grouping the key/values inside a cache (and + * to avoid collision with other caches or keys inside Redis). + * + * @author Costin Leau + */ +public interface RedisCachePrefix { + + /** + * Returns the prefix for the given cache (identified by name). + * Note the prefix is returned in raw form so it can be saved directly to Redis without any serialization. + * + * @param cacheName the name of the cache using the prefix + * @return the prefix for the given cache. + */ + byte[] prefix(String cacheName); + +} diff --git a/src/test/java/org/springframework/data/redis/cache/RedisCacheTest.java b/src/test/java/org/springframework/data/redis/cache/RedisCacheTest.java index 5c9920f0d..6851fcf75 100644 --- a/src/test/java/org/springframework/data/redis/cache/RedisCacheTest.java +++ b/src/test/java/org/springframework/data/redis/cache/RedisCacheTest.java @@ -52,7 +52,7 @@ public class RedisCacheTest extends AbstractNativeCacheTest { @Override protected Cache createCache(RedisTemplate nativeCache) { - return new RedisCache(CACHE_NAME, nativeCache); + return new RedisCache(CACHE_NAME, CACHE_NAME.concat(":").getBytes(), nativeCache); } @Override diff --git a/src/test/java/org/springframework/data/redis/support/collections/CollectionTestParams.java b/src/test/java/org/springframework/data/redis/support/collections/CollectionTestParams.java index 9462b269d..2654e6edf 100644 --- a/src/test/java/org/springframework/data/redis/support/collections/CollectionTestParams.java +++ b/src/test/java/org/springframework/data/redis/support/collections/CollectionTestParams.java @@ -135,13 +135,13 @@ public abstract class CollectionTestParams { jsonPersonTemplateRJC.setConnectionFactory(rjcConnFactory); jsonPersonTemplateRJC.afterPropertiesSet(); - return Arrays.asList(new Object[][] { { stringFactory, stringTemplateRJC }, + return Arrays.asList(new Object[][] { { stringFactory, stringTemplate }, { stringFactory, stringTemplateRJC }, { personFactory, personTemplateRJC }, { stringFactory, stringTemplateJR }, - { personFactory, personTemplateJR }, { stringFactory, stringTemplate }, - { personFactory, personTemplate }, { stringFactory, xstreamStringTemplate }, - { personFactory, xstreamPersonTemplate }, { stringFactory, xstreamStringTemplateJR }, - { personFactory, xstreamPersonTemplateJR }, { personFactory, jsonPersonTemplate }, - { personFactory, jsonPersonTemplateJR }, { stringFactory, xstreamStringTemplateRJC }, - { personFactory, xstreamPersonTemplateRJC }, { personFactory, jsonPersonTemplateRJC } }); + { personFactory, personTemplateJR }, { personFactory, personTemplate }, + { stringFactory, xstreamStringTemplate }, { personFactory, xstreamPersonTemplate }, + { stringFactory, xstreamStringTemplateJR }, { personFactory, xstreamPersonTemplateJR }, + { personFactory, jsonPersonTemplate }, { personFactory, jsonPersonTemplateJR }, + { stringFactory, xstreamStringTemplateRJC }, { personFactory, xstreamPersonTemplateRJC }, + { personFactory, jsonPersonTemplateRJC } }); } }