RedisTemplate的配置和讲解以及和StringRedisTemplate的区别
本文主要讲redisTempalte的几种常用的序列化方式
- string,我们大部分情况下都希望存入redis的数据可读性强一些,并且value也不总是一个规则的类型,所以这里也是不用json序列化的原因,可以更自由方便,下边提供配置方法
package sca.pro.core.redis.configuration; import cn.hutool.core.convert.Convert; import org.apache.commons.pool2.impl.GenericObjectPoolConfig; import org.springframework.beans.factory.annotation.Value; import org.springframework.cache.annotation.EnableCaching; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.connection.RedisPassword; import org.springframework.data.redis.connection.RedisStandaloneConfiguration; import org.springframework.data.redis.connection.lettuce.LettuceClientConfiguration; import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory; import org.springframework.data.redis.connection.lettuce.LettucePoolingClientConfiguration; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.serializer.RedisSerializer; import org.springframework.data.redis.serializer.StringRedisSerializer; import java.time.Duration; @Configuration @EnableCaching public class RedisTemplateConfig { @Value("${spring.redis.database}") private int database; @Value("${spring.redis.host}") private String host; @Value("${spring.redis.password}") private String password; @Value("${spring.redis.port}") private String port; @Value("${spring.redis.timeout}") private String timeout; @Value("${spring.redis.lettuce.pool.max-idle}") private String maxIdle; @Value("${spring.redis.lettuce.pool.min-idle}") private String minIdle; @Value("${spring.redis.lettuce.pool.max-active}") private String maxActive; @Value("${spring.redis.lettuce.pool.max-wait}") private String maxWait; @Bean public LettuceConnectionFactory lettuceConnectionFactory() { GenericObjectPoolConfig genericObjectPoolConfig = new GenericObjectPoolConfig(); genericObjectPoolConfig.setMaxIdle(Convert.toInt(maxIdle)); genericObjectPoolConfig.setMinIdle(Convert.toInt(minIdle)); genericObjectPoolConfig.setMaxTotal(Convert.toInt(maxActive)); genericObjectPoolConfig.setMaxWaitMillis(Convert.toLong(maxWait)); genericObjectPoolConfig.setTimeBetweenEvictionRunsMillis(100); RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration(); redisStandaloneConfiguration.setDatabase(database); redisStandaloneConfiguration.setHostName(host); redisStandaloneConfiguration.setPort(Convert.toInt(port)); redisStandaloneConfiguration.setPassword(RedisPassword.of(password)); LettuceClientConfiguration clientConfig = LettucePoolingClientConfiguration.builder() .commandTimeout(Duration.ofMillis(Convert.toLong(timeout))) .poolConfig(genericObjectPoolConfig) .build(); LettuceConnectionFactory factory = new LettuceConnectionFactory(redisStandaloneConfiguration, clientConfig); return factory; } @Bean public RedisTemplate<String, String> redisTemplate(LettuceConnectionFactory factory) { // 配置redisTemplate RedisTemplate<String, String> redisTemplate = new RedisTemplate<>(); redisTemplate.setConnectionFactory(factory); redisTemplate.setKeySerializer(new StringRedisSerializer());//key序列化 redisTemplate.setValueSerializer(new StringRedisSerializer());//value序列化 //设置hash的key的序列化方式 redisTemplate.setHashKeySerializer(new StringRedisSerializer()); //设置hash的value的序列化方式 redisTemplate.setHashValueSerializer(new StringRedisSerializer()); redisTemplate.afterPropertiesSet();//使上面参数生效 return redisTemplate; } }
其实如果key和value都是string,那就等效于我们直接引入StringRedisTemplate
- 如果使用字节数组的形式序列化,redistemplate默认使用的jdk的序列化方式,但是jdk的序列化后的字节数组不仅重,而且序列化和反序列化我们用的是protobuf,如下
pom
<!-- 工具库 -->
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>18.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- 序列化 -->
<dependency>
<groupId>com.dyuproject.protostuff</groupId>
<artifactId>protostuff-core</artifactId>
<version>1.1.3</version>
</dependency>
<dependency>
<groupId>com.dyuproject.protostuff</groupId>
<artifactId>protostuff-runtime</artifactId>
<version>1.1.3</version>
</dependency>
2.自己编写序列化工具
@Slf4j
public class ProtoStuffUtil {
/**
* 序列化对象
*
* @param obj
* @return
*/
public static <T> byte[] serialize(T obj) {
if (obj == null) {
log.error("Failed to serializer, obj is null");
throw new RuntimeException("Failed to serializer");
}
@SuppressWarnings("unchecked") Schema<T> schema = (Schema<T>) RuntimeSchema.getSchema(obj.getClass());
LinkedBuffer buffer = LinkedBuffer.allocate(1024 * 1024);
byte[] protoStuff;
try {
protoStuff = ProtostuffIOUtil.toByteArray(obj, schema, buffer);
} catch (Exception e) {
log.error("Failed to serializer, obj:{}", obj, e);
throw new RuntimeException("Failed to serializer");
} finally {
buffer.clear();
}
return protoStuff;
}
/**
* 反序列化对象
*
* @param paramArrayOfByte
* @param targetClass
* @return
*/
public static <T> T deserialize(byte[] paramArrayOfByte, Class<T> targetClass) {
if (paramArrayOfByte == null || paramArrayOfByte.length == 0) {
log.error("Failed to deserialize, byte is empty");
throw new RuntimeException("Failed to deserialize");
}
T instance;
try {
instance = targetClass.newInstance();
} catch (InstantiationException | IllegalAccessException e) {
log.error("Failed to deserialize", e);
throw new RuntimeException("Failed to deserialize");
}
Schema<T> schema = RuntimeSchema.getSchema(targetClass);
ProtostuffIOUtil.mergeFrom(paramArrayOfByte, instance, schema);
return instance;
}
/**
* 序列化列表
*
* @param objList
* @return
*/
public static <T> byte[] serializeList(List<T> objList) {
if (objList == null || objList.isEmpty()) {
log.error("Failed to serializer, objList is empty");
throw new RuntimeException("Failed to serializer");
}
@SuppressWarnings("unchecked") Schema<T> schema =
(Schema<T>) RuntimeSchema.getSchema(objList.get(0).getClass());
LinkedBuffer buffer = LinkedBuffer.allocate(1024 * 1024);
byte[] protoStuff;
ByteArrayOutputStream bos = null;
try {
bos = new ByteArrayOutputStream();
ProtostuffIOUtil.writeListTo(bos, objList, schema, buffer);
protoStuff = bos.toByteArray();
} catch (Exception e) {
log.error("Failed to serializer, obj list:{}", objList, e);
throw new RuntimeException("Failed to serializer");
} finally {
buffer.clear();
try {
if (bos != null) {
bos.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
return protoStuff;
}
/**
* 反序列化列表
*
* @param paramArrayOfByte
* @param targetClass
* @return
*/
public static <T> List<T> deserializeList(byte[] paramArrayOfByte, Class<T> targetClass) {
if (paramArrayOfByte == null || paramArrayOfByte.length == 0) {
log.error("Failed to deserialize, byte is empty");
throw new RuntimeException("Failed to deserialize");
}
Schema<T> schema = RuntimeSchema.getSchema(targetClass);
List<T> result;
try {
result = ProtostuffIOUtil.parseListFrom(new ByteArrayInputStream(paramArrayOfByte), schema);
} catch (IOException e) {
log.error("Failed to deserialize", e);
throw new RuntimeException("Failed to deserialize");
}
return result;
}
}
3.RedisTemplate的工具类方法
@Component
public class RedisClient {
private final RedisTemplate<String, String> redisTemplate;
@Autowired
public RedisClient(RedisTemplate<String, String> redisTemplate) {
this.redisTemplate = redisTemplate;
}
/**
* get cache
*
* @param field
* @param targetClass
* @param <T>
* @return
*/
public <T> T get(final String field, Class<T> targetClass) {
byte[] result = redisTemplate.execute((RedisCallback<byte[]>) connection -> connection.get(field.getBytes()));
if (result == null) {
return null;
}
return ProtoStuffUtil.deserialize(result, targetClass);
}
/**
* put cache
*
* @param field
* @param obj
* @param <T>
* @return
*/
public <T> void set(String field, T obj) {
final byte[] value = ProtoStuffUtil.serialize(obj);
redisTemplate.execute((RedisCallback<Void>) connection -> {
connection.set(field.getBytes(), value);
return null;
});
}
/**
* put cache with expire time
*
* @param field
* @param obj
* @param expireTime 单位: s
* @param <T>
*/
public <T> void setWithExpire(String field, T obj, final long expireTime) {
final byte[] value = ProtoStuffUtil.serialize(obj);
redisTemplate.execute((RedisCallback<Void>) connection -> {
connection.setEx(field.getBytes(), expireTime, value);
return null;
});
}
/**
* get list cache
*
* @param field
* @param targetClass
* @param <T>
* @return
*/
public <T> List<T> getList(final String field, Class<T> targetClass) {
byte[] result = redisTemplate.execute((RedisCallback<byte[]>) connection -> connection.get(field.getBytes()));
if (result == null) {
return null;
}
return ProtoStuffUtil.deserializeList(result, targetClass);
}
/**
* put list cache
*
* @param field
* @param objList
* @param <T>
* @return
*/
public <T> void setList(String field, List<T> objList) {
final byte[] value = ProtoStuffUtil.serializeList(objList);
redisTemplate.execute((RedisCallback<Void>) connection -> {
connection.set(field.getBytes(), value);
return null;
});
}
/**
* put list cache with expire time
*
* @param field
* @param objList
* @param expireTime
* @param <T>
* @return
*/
public <T> void setListWithExpire(String field, List<T> objList, final long expireTime) {
final byte[] value = ProtoStuffUtil.serializeList(objList);
redisTemplate.execute((RedisCallback<Void>) connection -> {
connection.setEx(field.getBytes(), expireTime, value);
return null;
});
}
/**
* get h cache
*
* @param key
* @param field
* @param targetClass
* @param <T>
* @return
*/
public <T> T hGet(final String key, final String field, Class<T> targetClass) {
byte[] result = redisTemplate
.execute((RedisCallback<byte[]>) connection -> connection.hGet(key.getBytes(), field.getBytes()));
if (result == null) {
return null;
}
return ProtoStuffUtil.deserialize(result, targetClass);
}
/**
* put hash cache
*
* @param key
* @param field
* @param obj
* @param <T>
* @return
*/
public <T> boolean hSet(String key, String field, T obj) {
final byte[] value = ProtoStuffUtil.serialize(obj);
return redisTemplate.execute(
(RedisCallback<Boolean>) connection -> connection.hSet(key.getBytes(), field.getBytes(), value));
}
/**
* put hash cache
*
* @param key
* @param field
* @param obj
* @param <T>
*/
public <T> void hSetWithExpire(String key, String field, T obj, long expireTime) {
final byte[] value = ProtoStuffUtil.serialize(obj);
redisTemplate.execute((RedisCallback<Void>) connection -> {
connection.hSet(key.getBytes(), field.getBytes(), value);
connection.expire(key.getBytes(), expireTime);
return null;
});
}
/**
* get list cache
*
* @param key
* @param field
* @param targetClass
* @param <T>
* @return
*/
public <T> List<T> hGetList(final String key, final String field, Class<T> targetClass) {
byte[] result = redisTemplate
.execute((RedisCallback<byte[]>) connection -> connection.hGet(key.getBytes(), field.getBytes()));
if (result == null) {
return null;
}
return ProtoStuffUtil.deserializeList(result, targetClass);
}
/**
* put list cache
*
* @param key
* @param field
* @param objList
* @param <T>
* @return
*/
public <T> boolean hSetList(String key, String field, List<T> objList) {
final byte[] value = ProtoStuffUtil.serializeList(objList);
return redisTemplate.execute(
(RedisCallback<Boolean>) connection -> connection.hSet(key.getBytes(), field.getBytes(), value));
}
/**
* get cache by keys
*
* @param key
* @param fields
* @param targetClass
* @param <T>
* @return
*/
public <T> Map<String, T> hMGet(String key, Collection<String> fields, Class<T> targetClass) {
List<byte[]> byteFields = fields.stream().map(String::getBytes).collect(Collectors.toList());
byte[][] queryFields = new byte[byteFields.size()][];
byteFields.toArray(queryFields);
List<byte[]> cache = redisTemplate
.execute((RedisCallback<List<byte[]>>) connection -> connection.hMGet(key.getBytes(), queryFields));
Map<String, T> results = new HashMap<>(16);
Iterator<String> it = fields.iterator();
int index = 0;
while (it.hasNext()) {
String k = it.next();
if (cache.get(index) == null) {
index++;
continue;
}
results.put(k, ProtoStuffUtil.deserialize(cache.get(index), targetClass));
index++;
}
return results;
}
/**
* set cache by keys
*
* @param field
* @param values
* @param <T>
*/
public <T> void hMSet(String field, Map<String, T> values) {
Map<byte[], byte[]> byteValues = new HashMap<>(16);
for (Map.Entry<String, T> value : values.entrySet()) {
byteValues.put(value.getKey().getBytes(), ProtoStuffUtil.serialize(value.getValue()));
}
redisTemplate.execute((RedisCallback<Void>) connection -> {
connection.hMSet(field.getBytes(), byteValues);
return null;
});
}
/**
* get caches in hash
*
* @param key
* @param targetClass
* @param <T>
* @return
*/
public <T> Map<String, T> hGetAll(String key, Class<T> targetClass) {
Map<byte[], byte[]> records = redisTemplate
.execute((RedisCallback<Map<byte[], byte[]>>) connection -> connection.hGetAll(key.getBytes()));
Map<String, T> ret = new HashMap<>(16);
for (Map.Entry<byte[], byte[]> record : records.entrySet()) {
T obj = ProtoStuffUtil.deserialize(record.getValue(), targetClass);
ret.put(new String(record.getKey()), obj);
}
return ret;
}
/**
* list index
*
* @param key
* @param index
* @param targetClass
* @param <T>
* @return
*/
public <T> T lIndex(String key, int index, Class<T> targetClass) {
byte[] value =
redisTemplate.execute((RedisCallback<byte[]>) connection -> connection.lIndex(key.getBytes(), index));
return ProtoStuffUtil.deserialize(value, targetClass);
}
/**
* list range
*
* @param key
* @param start
* @param end
* @param targetClass
* @param <T>
* @return
*/
public <T> List<T> lRange(String key, int start, int end, Class<T> targetClass) {
List<byte[]> value = redisTemplate
.execute((RedisCallback<List<byte[]>>) connection -> connection.lRange(key.getBytes(), start, end));
return value.stream().map(record -> ProtoStuffUtil.deserialize(record, targetClass))
.collect(Collectors.toList());
}
/**
* list left push
*
* @param key
* @param obj
* @param <T>
*/
public <T> void lPush(String key, T obj) {
final byte[] value = ProtoStuffUtil.serialize(obj);
redisTemplate.execute((RedisCallback<Long>) connection -> connection.lPush(key.getBytes(), value));
}
/**
* list left push
*
* @param key
* @param objList
* @param <T>
*/
public <T> void lPush(String key, List<T> objList) {
List<byte[]> byteFields = objList.stream().map(ProtoStuffUtil::serialize).collect(Collectors.toList());
byte[][] values = new byte[byteFields.size()][];
redisTemplate.execute((RedisCallback<Long>) connection -> connection.lPush(key.getBytes(), values));
}
/**
* 精确删除key
*
* @param key
*/
public void deleteCache(String key) {
redisTemplate.delete(key);
}
/**
* 排行榜的存入
*
* @param redisKey
* @param immutablePair
*/
public void zAdd(String redisKey, ImmutablePair<String, BigDecimal> immutablePair) {
final byte[] key = redisKey.getBytes();
final byte[] value = immutablePair.getLeft().getBytes();
redisTemplate.execute((RedisCallback<Boolean>) connection -> connection
.zAdd(key, immutablePair.getRight().doubleValue(), value));
}
/**
* 获取排行榜低->高排序
*
* @param redisKey 要进行排序的类别
* @param start
* @param end
* @return
*/
public List<ImmutablePair<String, BigDecimal>> zRangeWithScores(String redisKey, int start, int end) {
Set<RedisZSetCommands.Tuple> items = redisTemplate.execute(
(RedisCallback<Set<RedisZSetCommands.Tuple>>) connection -> connection
.zRangeWithScores(redisKey.getBytes(), start, end));
return items.stream()
.map(record -> ImmutablePair.of(new String(record.getValue()), BigDecimal.valueOf(record.getScore())))
.collect(Collectors.toList());
}
/**
* 获取排行榜高->低排序
*
* @param redisKey 要进行排序的类别
* @param start
* @param end
* @return
*/
public List<ImmutablePair<String, BigDecimal>> zRevRangeWithScores(String redisKey, int start, int end) {
Set<RedisZSetCommands.Tuple> items = redisTemplate.execute(
(RedisCallback<Set<RedisZSetCommands.Tuple>>) connection -> connection
.zRevRangeWithScores(redisKey.getBytes(), start, end));
return items.stream()
.map(record -> ImmutablePair.of(new String(record.getValue()), BigDecimal.valueOf(record.getScore())))
.collect(Collectors.toList());
}
}
- 最推荐的一种序列化方式GenericJackson2JsonRedisSerializer,org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer 使用Jackson 实现JSON的序列化方式,Generic单词翻译过来表示:通用的意思,可以看出,是支持所有类。
@Bean
@ConditionalOnMissingBean(name = "redisTemplate")
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory){
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(factory);
//String的序列化方式
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
// 使用GenericJackson2JsonRedisSerializer 替换默认序列化(默认采用的是JDK序列化)
GenericJackson2JsonRedisSerializer genericJackson2JsonRedisSerializer = new GenericJackson2JsonRedisSerializer();
//key序列化方式采用String类型
template.setKeySerializer(stringRedisSerializer);
//value序列化方式采用jackson类型
template.setValueSerializer(genericJackson2JsonRedisSerializer);
//hash的key序列化方式也是采用String类型
template.setHashKeySerializer(stringRedisSerializer);
//hash的value也是采用jackson类型
template.setHashValueSerializer(genericJackson2JsonRedisSerializer);
template.afterPropertiesSet();
return template;
}
}
运行下边的测试类测试GenericJackson2JsonRedisSerializer,发现不管是字符串还是对象还是数组都很通用
@Test
void redisTemplateSerializeTest() {
String redisTemplateStringKey = "redisTemplateStringKey";
String redisTemplateUserObjectKey = "redisTemplateUserObjectKey";
String redisTemplateUserArrayObjectKey = "redisTemplateUserArrayObjectKey";
String redisTemplateJSONObjectKey = "redisTemplateJSONObjectKey";
String redisTemplateJSONArrayKey = "redisTemplateJSONArrayKey";
//序列化String类型和反序列化String类型
redisTemplate.opsForValue().set(redisTemplateStringKey, "austin");
String austin = (String) redisTemplate.opsForValue().get(redisTemplateStringKey);
System.out.println("stringGet: " + austin);
//序列化Object对象类型和反序列化Object对象类型 (User对象)
User user = new User("123", "austin", 25);
redisTemplate.opsForValue().set(redisTemplateUserObjectKey, user);
User userGet = (User) redisTemplate.opsForValue().get(redisTemplateUserObjectKey);
System.out.println("userGet: " + userGet);
//序列化Object对象数组类型和反序列化Object对象数组类型 (User[]对象数组)
User user1 = new User("1", "austin1", 25);
User user2 = new User("2", "austin2", 25);
User[] userArray = new User[]{user1, user2};
redisTemplate.opsForValue().set(redisTemplateUserArrayObjectKey, userArray);
User[] userArrayGet = (User[]) redisTemplate.opsForValue().get(redisTemplateUserArrayObjectKey);
System.out.println("userArrayGet: " + userArrayGet);
//序列化JSONObject对象类型和反序列化JSONObject对象类型
JSONObject jsonObject = new JSONObject();
jsonObject.put("id", "123");
jsonObject.put("name", "austin");
jsonObject.put("age", 25);
redisTemplate.opsForValue().set(redisTemplateJSONObjectKey, jsonObject);
JSONObject jsonObjectGet = (JSONObject) redisTemplate.opsForValue().get(redisTemplateJSONObjectKey);
System.out.println("jsonObjectGet: " + jsonObjectGet);
//序列化JSONArray对象类型和反序列化JSONArray对象类型
JSONArray jsonArray = new JSONArray();
JSONObject jsonObject1 = new JSONObject();
jsonObject1.put("id", "1");
jsonObject1.put("name", "austin1");
jsonObject1.put("age", 25);
JSONObject jsonObject2 = new JSONObject();
jsonObject2.put("id", "1");
jsonObject2.put("name", "austin2");
jsonObject2.put("age", 25);
jsonArray.add(jsonObject1);
jsonArray.add(jsonObject2);
redisTemplate.opsForValue().set(redisTemplateJSONArrayKey, jsonArray);
JSONArray jsonArrayGet = (JSONArray) redisTemplate.opsForValue().get(redisTemplateJSONArrayKey);
System.out.println("jsonArrayGet: " + jsonArrayGet);
}
key- value :
字符串类型
Key: redisTemplateStringKey
Value: "austin"
对象类型
Key: redisTemplateUserObjectKey
Value:
{
"@class": "com.example.jedisserializefrombytestojson.User",
"id": "123",
"name": "austin",
"age": 25
}
对象数组类型
Key: redisTemplateUserArrayObjectKey
Value:
[
"[Lcom.example.jedisserializefrombytestojson.User;",
[
{
"@class": "com.example.jedisserializefrombytestojson.User",
"id": "1",
"name": "austin1",
"age": 25
},
{
"@class": "com.example.jedisserializefrombytestojson.User",
"id": "2",
"name": "austin2",
"age": 25
}
]
]
JSONObject类型
Key: redisTemplateJSONObjectKey
Value:
{
"@class": "com.alibaba.fastjson.JSONObject",
"name": "austin",
"id": "123",
"age": 25
}
JSONArray类型
Key: redisTemplateJSONArrayKey
Value:
[
"com.alibaba.fastjson.JSONArray",
[
{
"@class": "com.alibaba.fastjson.JSONObject",
"name": "austin1",
"id": "1",
"age": 25
},
{
"@class": "com.alibaba.fastjson.JSONObject",
"name": "austin2",
"id": "1",
"age": 25
}
]
]