[Add][Update] CacheStore-redis 添加 Redis 缓存存储容器的实现模块;

[Add] META-INF/services/* 支持 SPI 机制;
[Add] RedisCacheStore, RedisCacheStoreFactory, RedisConnectionPool, RedisUtils 添加相关父类和辅助类;
[Add] RedisMapCacheStore, RedisMapCacheStoreTest 添加映射表缓存相关实现及完整单元测试;
[Add] RedisSingleCacheStore, RedisSingleCacheStoreTest 添加单项缓存相关实现及完整单元测试;
[Update] redis.clients:jedis 更新依赖项版本(3.2.0 -> 3.3.0);
This commit is contained in:
LamGC 2020-09-12 03:15:37 +08:00
parent 862ddc871e
commit c856d94384
Signed by: LamGC
GPG Key ID: 6C5AE2A913941E1D
10 changed files with 883 additions and 1 deletions

View File

@ -38,7 +38,7 @@
<dependency> <dependency>
<groupId>redis.clients</groupId> <groupId>redis.clients</groupId>
<artifactId>jedis</artifactId> <artifactId>jedis</artifactId>
<version>3.2.0</version> <version>3.3.0</version>
</dependency> </dependency>
</dependencies> </dependencies>

View File

@ -0,0 +1,102 @@
/*
* Copyright (C) 2020 LamGC
*
* ContentGrabbingJi is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* ContentGrabbingJi is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.lamgc.cgj.bot.cache.redis;
import net.lamgc.cgj.bot.cache.CacheKey;
import net.lamgc.cgj.bot.cache.CacheStore;
import redis.clients.jedis.Jedis;
import java.util.HashSet;
import java.util.Set;
/**
*
* @author LamGC
*/
public abstract class RedisCacheStore<V> implements CacheStore<V> {
/**
* 获取 Key 前缀.
* <p>key = getKeyPrefix() + key
* @param cacheKey CacheKey 对象.
* @return 返回 Key 前缀.
*/
protected String getKeyString(CacheKey cacheKey) {
return getKeyPrefix() + cacheKey.join(RedisUtils.KEY_SEPARATOR);
}
/**
* 获取 Key 的完整前缀.
* @return 返回完整前缀.
*/
protected abstract String getKeyPrefix();
@Override
public boolean setTimeToLive(CacheKey key, long ttl) {
String keyString = getKeyString(key);
return RedisConnectionPool.executeRedis(jedis -> {
Long result;
if (ttl >= 0) {
result = jedis.pexpire(keyString, ttl);
} else {
result = jedis.persist(keyString);
}
return result == RedisUtils.RETURN_CODE_OK;
});
}
@Override
public long getTimeToLive(CacheKey key) {
return RedisConnectionPool.executeRedis(jedis -> {
Long ttl = jedis.pttl(getKeyString(key));
return ttl < 0 ? -1 : ttl;
});
}
@Override
public long size() {
return RedisConnectionPool.executeRedis(Jedis::dbSize);
}
@Override
public boolean clear() {
return RedisConnectionPool.executeRedis(jedis -> RedisUtils.isOk(jedis.flushDB()));
}
@Override
public boolean exists(CacheKey key) {
return RedisConnectionPool.executeRedis(jedis -> jedis.exists(getKeyString(key)));
}
@Override
public boolean remove(CacheKey key) {
return RedisConnectionPool.executeRedis(jedis -> jedis.del(getKeyString(key)) == RedisUtils.RETURN_CODE_OK);
}
@Override
public Set<String> keySet() {
Set<String> keys = RedisConnectionPool.executeRedis(jedis -> jedis.keys(RedisUtils.KEY_PATTERN_ALL));
final int prefixLength = getKeyPrefix().length();
Set<String> newKeys = new HashSet<>();
for (String key : keys) {
newKeys.add(key.substring(prefixLength));
}
return newKeys;
}
}

View File

@ -0,0 +1,53 @@
/*
* Copyright (C) 2020 LamGC
*
* ContentGrabbingJi is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* ContentGrabbingJi is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.lamgc.cgj.bot.cache.redis;
import net.lamgc.cgj.bot.cache.*;
import net.lamgc.cgj.bot.cache.convert.StringConverter;
/**
*
* @author LamGC
*/
@Factory(name = "Redis")
public class RedisCacheStoreFactory implements CacheStoreFactory {
@Override
public <V> SingleCacheStore<V> newSingleCacheStore(String identify, StringConverter<V> converter) {
return new RedisSingleCacheStore<>(identify, converter);
}
@Override
public <E> ListCacheStore<E> newListCacheStore(String identify, StringConverter<E> converter) {
return null;
}
@Override
public <E> SetCacheStore<E> newSetCacheStore(String identify, StringConverter<E> converter) {
return null;
}
@Override
public <V> MapCacheStore<V> newMapCacheStore(String identify, StringConverter<V> converter) {
return new RedisMapCacheStore<>(identify, converter);
}
@Override
public boolean canGetCacheStore() {
return RedisConnectionPool.available();
}
}

View File

@ -0,0 +1,103 @@
/*
* Copyright (C) 2020 LamGC
*
* ContentGrabbingJi is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* ContentGrabbingJi is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.lamgc.cgj.bot.cache.redis;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Function;
/**
* 统一的 Redis 连接池.
* @author LamGC
*/
public class RedisConnectionPool {
private final static Logger log = LoggerFactory.getLogger(RedisConnectionPool.class);
private final static AtomicReference<JedisPool> POOL = new AtomicReference<>();
public static synchronized void reconnectRedis() {
JedisPool jedisPool = POOL.get();
if (jedisPool != null && !jedisPool.isClosed()) {
return;
}
JedisPoolConfig config = new JedisPoolConfig();
config.setTestOnBorrow(true);
config.setTestOnReturn(true);
jedisPool = new JedisPool(config);
POOL.set(jedisPool);
}
/**
* 获取一个 Jedis 对象.
* <p>注意, 需回收 Jedis 对象, 否则可能会耗尽连接池导致后续操作受到影响.
* @return 返回可用的 Jedis 连接.
*/
public static Jedis getConnection() {
JedisPool pool = POOL.get();
if (pool == null || pool.isClosed()) {
reconnectRedis();
pool = POOL.get();
if (pool == null) {
throw new IllegalStateException("Redis connection lost");
}
}
return pool.getResource();
}
/**
* 执行 Redis 操作并返回相关值.
* <p>本方法会自动回收 Jedis.
* @param function 待运行的操作.
* @param <R> 返回值类型.
* @return 返回 function 返回的内容.
*/
public static <R> R executeRedis(Function<Jedis, R> function) {
try (Jedis jedis = getConnection()) {
return function.apply(jedis);
}
}
/**
* 检查 Redis 连接池是否有可用的资源.
* @return 如果连接池依然活跃, 返回 true.
*/
public static boolean available() {
JedisPool jedisPool = POOL.get();
if (jedisPool == null || jedisPool.isClosed()) {
reconnectRedis();
if (jedisPool == null || jedisPool.isClosed()) {
return false;
}
}
if (jedisPool.getNumIdle() == 0) {
try (Jedis jedis = jedisPool.getResource()) {
return "pong".equalsIgnoreCase(jedis.ping());
} catch (Exception e) {
log.error("Redis 连接测试时发生异常", e);
}
return false;
}
return true;
}
}

View File

@ -0,0 +1,175 @@
/*
* Copyright (C) 2020 LamGC
*
* ContentGrabbingJi is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* ContentGrabbingJi is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.lamgc.cgj.bot.cache.redis;
import com.google.common.base.Strings;
import net.lamgc.cgj.bot.cache.CacheKey;
import net.lamgc.cgj.bot.cache.MapCacheStore;
import net.lamgc.cgj.bot.cache.convert.StringConverter;
import java.util.*;
/**
* Redis Map缓存存储容器.
* @param <V> 值类型.
* @author LamGC
*/
public class RedisMapCacheStore<V> extends RedisCacheStore<Map<String, V>> implements MapCacheStore<V> {
private final String keyPrefix;
private final StringConverter<V> converter;
public RedisMapCacheStore(String keyPrefix, StringConverter<V> converter) {
keyPrefix = Strings.nullToEmpty(keyPrefix).trim();
if (!keyPrefix.isEmpty() && keyPrefix.endsWith(RedisUtils.KEY_SEPARATOR)) {
this.keyPrefix = keyPrefix;
} else {
this.keyPrefix = keyPrefix + RedisUtils.KEY_SEPARATOR;
}
this.converter = Objects.requireNonNull(converter);
}
@Override
protected String getKeyPrefix() {
return this.keyPrefix;
}
@Override
public int mapSize(CacheKey key) {
return RedisConnectionPool.executeRedis(jedis -> {
String keyString = getKeyString(key);
if (jedis.exists(keyString)) {
return jedis.hlen(keyString).intValue();
}
return 0;
});
}
@Override
public Set<String> mapFieldSet(CacheKey key) {
return RedisConnectionPool.executeRedis(jedis -> {
String keyString = getKeyString(key);
if (jedis.exists(keyString)) {
return jedis.hkeys(keyString);
}
return null;
});
}
@Override
public Set<V> mapValueSet(CacheKey key) {
List<String> rawValueSet = RedisConnectionPool.executeRedis(jedis -> {
String keyString = getKeyString(key);
if (jedis.exists(keyString)) {
return jedis.hvals(keyString);
}
return null;
});
if (rawValueSet == null) {
return null;
}
Set<V> result = new HashSet<>();
for (String rawValue : rawValueSet) {
result.add(converter.from(rawValue));
}
return result;
}
@Override
public boolean put(CacheKey key, String field, V value) {
Objects.requireNonNull(field);
Objects.requireNonNull(value);
return RedisConnectionPool.executeRedis(jedis -> {
String keyString = getKeyString(key);
return jedis.hset(keyString, field, converter.to(value)) == RedisUtils.RETURN_CODE_OK;
});
}
@Override
public boolean putAll(CacheKey key, Map<String, V> map) {
Objects.requireNonNull(key);
Objects.requireNonNull(map);
if (map.size() == 0 && exists(key)) {
return true;
}
final Map<String, String> targetMap = new HashMap<>(map.size());
map.forEach((k, v) -> targetMap.put(k, converter.to(v)));
return RedisConnectionPool.executeRedis(jedis -> {
String keyString = getKeyString(key);
return RedisUtils.isOk(jedis.hmset(keyString, targetMap));
});
}
@Override
public boolean putIfNotExist(CacheKey key, String field, V value) {
Objects.requireNonNull(field);
Objects.requireNonNull(value);
return RedisConnectionPool.executeRedis(jedis -> {
String keyString = getKeyString(key);
return jedis.hsetnx(keyString, field, converter.to(value)) == RedisUtils.RETURN_CODE_OK;
});
}
@Override
public V get(CacheKey key, String field) {
Objects.requireNonNull(field);
String value = RedisConnectionPool.executeRedis(jedis -> jedis.hget(getKeyString(key), field));
if (value == null) {
return null;
}
return converter.from(value);
}
@Override
public boolean removeField(CacheKey key, String field) {
Objects.requireNonNull(field);
return RedisConnectionPool.executeRedis(jedis ->
jedis.hdel(getKeyString(key), field) == RedisUtils.RETURN_CODE_OK);
}
@Override
public boolean containsField(CacheKey key, String field) {
Objects.requireNonNull(field);
return RedisConnectionPool.executeRedis(jedis -> jedis.hexists(getKeyString(key), field));
}
@Override
public boolean mapIsEmpty(CacheKey key) {
return mapSize(key) == 0;
}
@Override
public boolean clearMap(CacheKey key) {
Set<String> fields = mapFieldSet(key);
if (fields == null) {
return false;
} else if (fields.size() == 0) {
return true;
}
String[] fieldsArray = new String[fields.size()];
fields.toArray(fieldsArray);
return RedisConnectionPool.executeRedis(jedis ->
jedis.hdel(getKeyString(key), fieldsArray) != RedisUtils.RETURN_CODE_FAILED);
}
}

View File

@ -0,0 +1,77 @@
/*
* Copyright (C) 2020 LamGC
*
* ContentGrabbingJi is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* ContentGrabbingJi is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.lamgc.cgj.bot.cache.redis;
import com.google.common.base.Strings;
import net.lamgc.cgj.bot.cache.CacheKey;
import net.lamgc.cgj.bot.cache.SingleCacheStore;
import net.lamgc.cgj.bot.cache.convert.StringConverter;
import java.util.Objects;
/**
* Redis 单项缓存存储容器.
* @param <V> 值类型.
* @see net.lamgc.cgj.bot.cache.CacheStore
* @see net.lamgc.cgj.bot.cache.SingleCacheStore
* @see net.lamgc.cgj.bot.cache.redis.RedisCacheStore
* @author LamGC
*/
public class RedisSingleCacheStore<V> extends RedisCacheStore<V> implements SingleCacheStore<V> {
private final String keyPrefix;
private final StringConverter<V> converter;
public RedisSingleCacheStore(String keyPrefix, StringConverter<V> converter) {
keyPrefix = Strings.nullToEmpty(keyPrefix).trim();
if (!keyPrefix.isEmpty() && keyPrefix.endsWith(RedisUtils.KEY_SEPARATOR)) {
this.keyPrefix = keyPrefix;
} else {
this.keyPrefix = keyPrefix + RedisUtils.KEY_SEPARATOR;
}
this.converter = Objects.requireNonNull(converter);
}
@Override
public boolean set(CacheKey key, V value) {
return RedisConnectionPool.executeRedis(jedis ->
RedisUtils.isOk(jedis.set(getKeyString(key), converter.to(Objects.requireNonNull(value)))));
}
@Override
public boolean setIfNotExist(CacheKey key, V value) {
return RedisConnectionPool.executeRedis(jedis ->
jedis.setnx(getKeyString(key), converter.to(Objects.requireNonNull(value)))
== RedisUtils.RETURN_CODE_OK);
}
@Override
public V get(CacheKey key) {
String value = RedisConnectionPool.executeRedis(jedis -> jedis.get(getKeyString(key)));
if (value == null) {
return null;
}
return converter.from(value);
}
@Override
protected String getKeyPrefix() {
return this.keyPrefix;
}
}

View File

@ -0,0 +1,54 @@
/*
* Copyright (C) 2020 LamGC
*
* ContentGrabbingJi is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* ContentGrabbingJi is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.lamgc.cgj.bot.cache.redis;
/**
* @author LamGC
*/
public class RedisUtils {
/**
* 返回码 - 成功
*/
public final static int RETURN_CODE_OK = 1;
/**
* 返回码 - 失败
*/
public final static int RETURN_CODE_FAILED = 0;
/**
* Key匹配规则 - 所有Key
*/
public final static String KEY_PATTERN_ALL = "*";
/**
* Key 分隔符
*/
public final static String KEY_SEPARATOR = ":";
/**
* 检查字符串返回结果是否为操作成功.
* @param result 字符串返回结果.
* @return 如果为操作成功, 返回 true.
*/
public static boolean isOk(String result) {
return "OK".equalsIgnoreCase(result);
}
}

View File

@ -0,0 +1,18 @@
#
# Copyright (C) 2020 LamGC
#
# ContentGrabbingJi is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# ContentGrabbingJi is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
#
net.lamgc.cgj.bot.cache.redis.RedisCacheStoreFactory

View File

@ -0,0 +1,151 @@
/*
* Copyright (C) 2020 LamGC
*
* ContentGrabbingJi is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* ContentGrabbingJi is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.lamgc.cgj.bot.cache.redis;
import net.lamgc.cgj.bot.cache.CacheKey;
import net.lamgc.cgj.bot.cache.MapCacheStore;
import net.lamgc.cgj.bot.cache.convert.StringToStringConverter;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import java.util.HashMap;
import java.util.Map;
/**
* @see RedisMapCacheStore
*/
public class RedisMapCacheStoreTest {
private final static MapCacheStore<String> cacheStore =
new RedisMapCacheStore<>("test", new StringToStringConverter());
@Before
public void before() {
Assert.assertTrue(cacheStore.clear());
}
@After
public void after() {
Assert.assertTrue(cacheStore.clear());
}
@Test
public void nullThrowTest() {
final CacheKey key = new CacheKey("testKey");
Assert.assertThrows(NullPointerException.class, () -> cacheStore.mapSize(null));
Assert.assertThrows(NullPointerException.class, () -> cacheStore.mapFieldSet(null));
Assert.assertThrows(NullPointerException.class, () -> cacheStore.mapValueSet(null));
Assert.assertThrows(NullPointerException.class, () -> cacheStore.put(null, "field", "value"));
Assert.assertThrows(NullPointerException.class, () -> cacheStore.put(key, null, "value"));
Assert.assertThrows(NullPointerException.class, () -> cacheStore.put(key, "field", null));
Assert.assertThrows(NullPointerException.class, () -> cacheStore.putAll(null, new HashMap<>()));
Assert.assertThrows(NullPointerException.class, () -> cacheStore.putAll(key, null));
Assert.assertThrows(NullPointerException.class, () -> cacheStore.putIfNotExist(null, "field", "value"));
Assert.assertThrows(NullPointerException.class, () -> cacheStore.putIfNotExist(key, null, "value"));
Assert.assertThrows(NullPointerException.class, () -> cacheStore.putIfNotExist(key, "field", null));
Assert.assertThrows(NullPointerException.class, () -> cacheStore.get(key, null));
Assert.assertThrows(NullPointerException.class, () -> cacheStore.get(null, "field"));
Assert.assertThrows(NullPointerException.class, () -> cacheStore.removeField(key, null));
Assert.assertThrows(NullPointerException.class, () -> cacheStore.removeField(null, "field"));
Assert.assertThrows(NullPointerException.class, () -> cacheStore.mapIsEmpty(null));
Assert.assertThrows(NullPointerException.class, () -> cacheStore.clearMap(null));
}
@Test
public void keyNotExistTest() {
final CacheKey key = new CacheKey("testKey");
HashMap<String, String> testMap = new HashMap<>();
testMap.put("testField", "value");
// 例外情况: 因为 Redis 对空 Map 的处理机制, 导致返回只能为空.
Assert.assertEquals(0, cacheStore.mapSize(key));
// 例外情况: mapIsEmpty Redis 没有相应指令, 所以是依靠 mapSize 实现的,
// 同样因 mapSize 的原因, 不存在等于空.
Assert.assertTrue(cacheStore.mapIsEmpty(key));
Assert.assertFalse(cacheStore.clearMap(key));
Assert.assertFalse(cacheStore.containsField(key, "Field"));
Assert.assertFalse(cacheStore.removeField(key, "Field"));
Assert.assertNull(cacheStore.get(key, "Field"));
Assert.assertTrue(cacheStore.put(key, "Field", "value"));
Assert.assertTrue("clearMap operation failed!", cacheStore.remove(key));
Assert.assertTrue(cacheStore.putAll(key, testMap));
Assert.assertTrue("clearMap operation failed!", cacheStore.remove(key));
Assert.assertTrue(cacheStore.putIfNotExist(key, "Field", "value"));
}
@Test
public void putAndGetTest() {
final CacheKey key = new CacheKey("testKey");
final Map<String, String> expectedMap = new HashMap<>();
expectedMap.put("test01", "testValue01");
expectedMap.put("test02", "testValue02");
expectedMap.put("test03", "testValue03");
expectedMap.put("test04", "testValue04");
expectedMap.put("test05", "testValue05");
expectedMap.put("test06", "testValue06");
// put/get, mapIsEmpty, containsField
Assert.assertTrue("put operation failed!", cacheStore.put(key, "test00", "testValue00"));
Assert.assertTrue(cacheStore.containsField(key, "test00"));
Assert.assertEquals("testValue00", cacheStore.get(key, "test00"));
Assert.assertTrue("removeField operation failed!", cacheStore.removeField(key, "test00"));
// putIfNotExist
Assert.assertTrue(cacheStore.putIfNotExist(key, "test00", "testValue00"));
Assert.assertFalse(cacheStore.putIfNotExist(key, "test00", "testValue00"));
Assert.assertTrue("clearMap operation failed!", cacheStore.clearMap(key));
// putAll
Assert.assertTrue(cacheStore.putAll(key, expectedMap));
Assert.assertTrue(expectedMap.keySet().containsAll(cacheStore.mapFieldSet(key)));
Assert.assertTrue(expectedMap.values().containsAll(cacheStore.mapValueSet(key)));
}
@Test
public void fieldChangeTest() {
final CacheKey key = new CacheKey("testKey");
final Map<String, String> expectedMap = new HashMap<>();
expectedMap.put("test01", "testValue01");
expectedMap.put("test02", "testValue02");
expectedMap.put("test03", "testValue03");
expectedMap.put("test04", "testValue04");
expectedMap.put("test05", "testValue05");
expectedMap.put("test06", "testValue06");
// mapSize, clearMap, mapIsEmpty 测试
Assert.assertTrue("putAll operation failed!", cacheStore.putAll(key, expectedMap));
Assert.assertEquals(expectedMap.size(), cacheStore.mapSize(key));
Assert.assertTrue(cacheStore.clearMap(key));
Assert.assertEquals(0, cacheStore.mapSize(key));
Assert.assertTrue(cacheStore.mapIsEmpty(key));
// removeField 多分支测试
Assert.assertTrue("put operation failed!", cacheStore.put(key, "test00", "testValue00"));
Assert.assertTrue(cacheStore.containsField(key, "test00"));
Assert.assertEquals("testValue00", cacheStore.get(key, "test00"));
Assert.assertTrue("removeField operation failed!", cacheStore.removeField(key, "test00"));
Assert.assertFalse(cacheStore.removeField(key, "test00"));
}
}

View File

@ -0,0 +1,149 @@
/*
* Copyright (C) 2020 LamGC
*
* ContentGrabbingJi is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* ContentGrabbingJi is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package net.lamgc.cgj.bot.cache.redis;
import net.lamgc.cgj.bot.cache.CacheKey;
import net.lamgc.cgj.bot.cache.SingleCacheStore;
import net.lamgc.cgj.bot.cache.convert.StringToStringConverter;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import java.util.HashMap;
import java.util.Map;
/**
* @see RedisCacheStore
* @see RedisSingleCacheStore
*/
public class RedisSingleCacheStoreTest {
private final static SingleCacheStore<String> cacheStore = new RedisSingleCacheStore<>("test", new StringToStringConverter());
@Before
public void before() {
Assert.assertTrue(cacheStore.clear());
}
@Test
public void nullThrowTest() {
final SingleCacheStore<String> tempCacheStore =
new RedisSingleCacheStore<>("test" + RedisUtils.KEY_SEPARATOR, new StringToStringConverter());
final CacheKey key = new CacheKey("testKey");
// RedisSingleCacheStore
Assert.assertThrows(NullPointerException.class, () -> tempCacheStore.set(null, "testValue"));
Assert.assertThrows(NullPointerException.class, () -> tempCacheStore.set(key, null));
Assert.assertThrows(NullPointerException.class, () -> tempCacheStore.get(null));
Assert.assertThrows(NullPointerException.class, () -> tempCacheStore.setIfNotExist(null, "testValue"));
Assert.assertThrows(NullPointerException.class, () -> tempCacheStore.setIfNotExist(key, null));
// RedisCacheStore
Assert.assertThrows(NullPointerException.class, () -> tempCacheStore.exists(null));
Assert.assertThrows(NullPointerException.class, () -> tempCacheStore.getTimeToLive(null));
Assert.assertThrows(NullPointerException.class, () -> tempCacheStore.setTimeToLive(null, 0));
Assert.assertThrows(NullPointerException.class, () -> tempCacheStore.remove(null));
}
@Test
public void setAndGetTest() {
final CacheKey key = new CacheKey("testKey");
final String value = "testValue";
Assert.assertTrue("Set operation failed!", cacheStore.set(key, value));
Assert.assertEquals(value, cacheStore.get(key));
Assert.assertTrue("Remove operation failed!", cacheStore.remove(key));
Assert.assertNull("Set operation failed!", cacheStore.get(key));
}
@Test
public void setIfNotExistTest() {
final CacheKey key = new CacheKey("testKey");
final String value = "testValue";
final String value2 = "testValue02";
Assert.assertTrue("Set operation failed!", cacheStore.set(key, value));
Assert.assertFalse(cacheStore.setIfNotExist(key, value2));
Assert.assertEquals(value, cacheStore.get(key));
}
@Test
public void expireTest() throws InterruptedException {
final CacheKey key = new CacheKey("testKey");
final String value = "testValue";
// Cache
Assert.assertFalse(cacheStore.setTimeToLive(key, 300));
Assert.assertEquals(-1, cacheStore.getTimeToLive(key));
// TTL 到期被动检查测试: 使用 exists expire 检查失败后返回 false.
Assert.assertTrue("Set operation failed!", cacheStore.set(key, value));
Assert.assertTrue("SetTTL operation failed!", cacheStore.setTimeToLive(key, 200));
Assert.assertNotEquals(-1, cacheStore.getTimeToLive(key));
Thread.sleep(300);
Assert.assertFalse(cacheStore.exists(key));
// 取消 TTL 测试
Assert.assertTrue("Set operation failed!", cacheStore.set(key, value));
Assert.assertTrue("SetTTL operation failed!", cacheStore.setTimeToLive(key, 200));
Assert.assertTrue("SetTTL operation failed!", cacheStore.setTimeToLive(key, -1));
Thread.sleep(300);
Assert.assertTrue(cacheStore.exists(key));
Assert.assertEquals(-1, cacheStore.getTimeToLive(key));
}
@Test
public void removeTest() {
final CacheKey key = new CacheKey("testKey");
final String value = "testValue";
// 删除不存在Cache测试
Assert.assertFalse(cacheStore.remove(key));
// 删除存在的Cache测试
Assert.assertTrue("Set operation failed!", cacheStore.set(key, value));
Assert.assertTrue(cacheStore.remove(key));
}
@Test
public void clearTest() {
final CacheKey key = new CacheKey("testKey");
final String value = "testValue";
Assert.assertTrue("Set operation failed!", cacheStore.set(key, value));
Assert.assertTrue(cacheStore.exists(key));
Assert.assertTrue("Clear operation failed!", cacheStore.clear());
Assert.assertFalse(cacheStore.exists(key));
}
@Test
public void sizeAndKeySetTest() {
Map<String, String> expectedMap = new HashMap<>();
expectedMap.put("test01", "testValue01");
expectedMap.put("test02", "testValue02");
expectedMap.put("test03", "testValue03");
expectedMap.put("test04", "testValue04");
expectedMap.put("test05", "testValue05");
expectedMap.put("test06", "testValue06");
expectedMap.forEach((key, value) -> cacheStore.set(new CacheKey(key), value));
Assert.assertEquals(expectedMap.size(), cacheStore.size());
Assert.assertTrue(expectedMap.keySet().containsAll(cacheStore.keySet()));
}
}