Redis
的setnx
命令是当key
不存在时设置key
,但setnx
不能同时完成expire
设置失效时长,不能保证setnx
和expire
的原子性。我们可以使用set
命令完成setnx
和expire
的操作,并且这种操作是原子操作。
下面是set
命令的可选项:
set key value [EX seconds] [PX milliseconds] [NX|XX]
EX seconds:设置失效时长,单位秒
PX milliseconds:设置失效时长,单位毫秒
NX:key不存在时设置value,成功返回OK,失败返回(nil)
XX:key存在时设置value,成功返回OK,失败返回(nil)
案例:设置name=p7+,失效时长100s,不存在时设置
1.1.1.1:6379> set name p7+ ex 100 nx
OK
1.1.1.1:6379> get name
"p7+"
1.1.1.1:6379> ttl name
(integer) 94
从上面可以看出,多个命令放在同一个redis
连接中并且redis
是单线程的,因此上面的操作可以看成setnx
和expire
的结合体,是原子性的。
在Java
中,如何使用RedisTemplate
封装上述操作呢?
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import .Jackson2JsonRedisSerializer;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
import ;
/**
* Redis拓展set为setnx
**/
@Component
public class RedisStringOps {
/**
* RedisTemplate 装饰器
* @date 2019/6/11 14:45
**/
private static class RedisTemplateHolder {
/**
* 最大有20个redis连接被使用,其他的连接要等待令牌释放
* 令牌数量自己定义,这个令牌是为了避免高并发下,获取redis连接数时,抛出的异常
* 在压力测试下,性能也很可观
*/
private static Semaphore semaphore = new Semaphore(20);
private RedisTemplateHolder() {
}
public static RedisTemplate getRedisTemplate(RedisTemplate redisTemplate) {
try {
();
return redisTemplate;
} catch (Exception e) {
throw new IllegalStateException(e);
}
}
public static void release() {
();
}
public static Object execute(Statement statement, RedisTemplate<String, Object> redisTemplate) {
try {
return (getRedisTemplate(redisTemplate));
} finally {
();
}
}
}
private interface Statement {
Object prepare(final RedisTemplate redisTemplate);
}
@Resource
private RedisTemplate redisTemplate;
private static RedisSerializer<String> stringSerializer = new StringRedisSerializer();
private static RedisSerializer<Object> blobSerializer = new JdkSerializationRedisSerializer();
/**
* 如果key不存在,set key and expire key
*
* @param key
* @param value
* @param expire
* @return
*/
public boolean setAndExpireIfAbsent(final String key, final Serializable value, final long expire) {
Boolean result = (Boolean) (new Statement() {
@Override
public Object prepare(RedisTemplate redisTemplate) {
return (new RedisCallback<Boolean>() {
@Override
public Boolean doInRedis(RedisConnection connection) throws DataAccessException {
Object obj = ("set", serialize(key), serialize(value), ("NX"), ("EX"), (expire));
return obj != null;
}
});
}
}, redisTemplate);
return result;
}
public boolean setIfAbsent(final String key, final Serializable value) {
Boolean result = (Boolean) (new Statement() {
@Override
public Object prepare(RedisTemplate redisTemplate) {
return (new RedisCallback<Boolean>() {
@Override
public Boolean doInRedis(RedisConnection connection) throws DataAccessException {
Object obj = ("set", serialize(key), serialize(value), ("NX"));
return obj != null;
}
});
}
}, redisTemplate);
return result;
}
public void delete(final String key) {
(new Statement() {
public Object prepare(RedisTemplate redisTemplate) {
(serialize(key));
return null;
}
}, redisTemplate);
}
private <T> Jackson2JsonRedisSerializer<T> configuredJackson2JsonRedisSerializer(Class<T> clazz) {
Jackson2JsonRedisSerializer<T> serializer = new Jackson2JsonRedisSerializer<T>(clazz);
ObjectMapper objectMapper = new ObjectMapper();
// json转实体忽略未知属性
(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
// 实体转json忽略null
(.NON_NULL);
(objectMapper);
return serializer;
}
private byte[] serialize(Object object) {
return serialize(object, );
}
private byte[] serialize(Object object, SerializeFormat sf) {
if (object == null) {
return new byte[0];
}
if (sf == ) {
return (object);
}
if (object instanceof String || (())) {
return ((object));
} else {
return configuredJackson2JsonRedisSerializer(()).serialize(object);
}
}
}
/**
* 工具方法
* 判定指定的 Class 对象是否表示一个基本类型或者包装器类型
* @param clazz
* @return
*/
@SuppressWarnings("rawtypes")
public static boolean isPrimitive(Class clazz){
if(()){
return true;
} else
try {
if(("TYPE") !=null &&
((Class)(("TYPE").get(null))).isPrimitive()){
return true;
}
} catch (Exception e) {
}
return false;
}
/**
* Redis序列化形式
**/
public enum SerializeFormat {
// 字符串序列化形式,基本类型(包装类型)、字符串和可JSON化的数据类型才能选用
STRING,
// 二进制对象序列化形式,所有可序列化java对象类型
BLOB,
;
}