Spring之RedisTemplate配置与应用转载
原创文章链接: liuyueyi.github.io/hexblog/201…
Spring之RedisTemplate配置和使用
Spring针对Redis使用封装更强大Template为了便于使用;先前的Spring它也用于生态系统。redis但直接使用Jedis要执行相应的交互操作,现在只需查看它RedisTemplate它是如何实现的,使用起来是否更方便?
I. 基本配置
1. 依赖
仍然采用。Jedis连接池管理,因此除了引入 spring-data-redis
此外,还有jedis依赖,pom添加
org.springframework.data
spring-data-redis
1.8.4.RELEASE
redis.clients
jedis
2.9.0
复制代码
如果需要指定与序列化相关的参数,还可以引入jackson,本文是一个简单的入门级,而不是添加
2. 配置文件
准备redis相关配置参数,常见的有host, port, password, timeout...,以下是一个简单的配置并给出相应的含义
redis.hostName=127.0.0.1
redis.port=6379
redis.password=https://blog.hhui.top
# 连接超时
redis.timeout=10000
#最大自由数
redis.maxIdle=300
#控制一个pool可以分配多少jedis实例,用于替换上述redis.maxActive,如果是jedis 2.4稍后使用此属性
redis.maxTotal=1000
#最大连接设置等待时间。如果超过此时间,将收到异常。设置-1表示没有限制。
redis.maxWaitMillis=1000
#连接的最短空闲时间 默认1800000毫秒(30分钟)
redis.minEvictableIdleTimeMillis=300000
#一次释放的最大连接数,默认3
redis.numTestsPerEvictionRun=1024
#逐出扫描的时间间隔(毫秒) 如果为阴性,驱逐线程未运行, 默认-1
redis.timeBetweenEvictionRunsMillis=30000
#从池中删除连接之前进行验证,如果测试失败,然后从池中删除连接,并尝试获取另一个
redis.testOnBorrow=true
#空闲时检查有效性, 默认false
redis.testWhileIdle=true
复制代码
说明
- redis请记住设置密码,尤其是在允许远程访问时。如果没有密码和默认端口号,很容易扫描和注入脚本,然后开始为人们挖掘(个人经验...)
II. 使用和测试
按照一般思路,首先要加载上述配置并创建redis连接池,然后实例化RedisTemplate对象,并最终保持此强度以开始各种读写操作
1. 配置类
使用JavaConfig配置方式,主要有两种Bean,读取配置文件以设置各种参数。 RedisConnectionFactory
和预期的 RedisTemplate
@Configuration
@PropertySource("classpath:redis.properties")
public class RedisConfig extends JCacheConfigurerSupport {
@Autowired
private Environment environment;
@Bean
public RedisConnectionFactory redisConnectionFactory() {
JedisConnectionFactory fac = new JedisConnectionFactory();
fac.setHostName(environment.getProperty("redis.hostName"));
fac.setPort(Integer.parseInt(environment.getProperty("redis.port")));
fac.setPassword(environment.getProperty("redis.password"));
fac.setTimeout(Integer.parseInt(environment.getProperty("redis.timeout")));
fac.getPoolConfig().setMaxIdle(Integer.parseInt(environment.getProperty("redis.maxIdle")));
fac.getPoolConfig().setMaxTotal(Integer.parseInt(environment.getProperty("redis.maxTotal")));
fac.getPoolConfig().setMaxWaitMillis(Integer.parseInt(environment.getProperty("redis.maxWaitMillis")));
fac.getPoolConfig().setMinEvictableIdleTimeMillis(
Integer.parseInt(environment.getProperty("redis.minEvictableIdleTimeMillis")));
fac.getPoolConfig()
.setNumTestsPerEvictionRun(Integer.parseInt(environment.getProperty("redis.numTestsPerEvictionRun")));
fac.getPoolConfig().setTimeBetweenEvictionRunsMillis(
Integer.parseInt(environment.getProperty("redis.timeBetweenEvictionRunsMillis")));
fac.getPoolConfig().setTestOnBorrow(Boolean.parseBoolean(environment.getProperty("redis.testOnBorrow")));
fac.getPoolConfig().setTestWhileIdle(Boolean.parseBoolean(environment.getProperty("redis.testWhileIdle")));
return fac;
}
@Bean
public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate redis = new RedisTemplate<>();
redis.setConnectionFactory(redisConnectionFactory);
redis.afterPropertiesSet();
return redis;
}
}
复制代码
2. 测试和使用
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {RedisConfig.class})
public class RedisTest {
@Autowired
private RedisTemplate redisTemplate;
@Test
public void testRedisObj() {
Map properties = new HashMap<>();
properties.put("123", "hello");
properties.put("abc", 456);
redisTemplate.opsForHash().putAll("hash", properties);
Map
执行后的输出如下
ans: {123=hello, abc=456}
复制代码
从上面的配置和实现来看,非常简单,基本上没有圆圈,而是使用redis-cli均匀,但找不到 hash
这个key的内容
127.0.0.1:6379> get hash
(nil)
127.0.0.1:6379> keys *
1) "xacxedx00x05tx00x04hash"
复制代码
使用代码检查没有问题,直接控制台连接,发现此key这不是我们所期望的,加上几个前缀,why ?
3. 序列化问题
为了解决上述问题debug进去看看是什么引起的。
对应的源代码位置:
// org.springframework.data.redis.core.AbstractOperations#rawKey
byte[] rawKey(Object key) {
Assert.notNull(key, "non null key required");
return this.keySerializer() == null && key instanceof byte[] ? (byte[])((byte[])key) : this.keySerializer().serialize(key);
}
复制代码
你可以看到这个key不是我们期望的 key.getBytes()
, 相反,它调用 this.keySerializer().serialize(key)
,而debug默认的结果Serializer是 JdkSerializationRedisSerializer
然后逐步深入。链接如下
// org.springframework.core.serializer.support.SerializingConverter#convert
// org.springframework.core.serializer.DefaultSerializer#serialize
public class DefaultSerializer implements Serializer
因此,具体实施非常明确,即 ObjectOutputStream
,这件事是Java最原始的序列化反序列化流工具将包含类型信息,因此它将携带字符串前缀。
因此,要解决这个问题,替换原来的 JdkSerializationRedisSerializer
,改为String在某种程度上 StringRedisSerializer
,所以在RedisTemplate的配置,略有修改
@Bean
public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate redis = new RedisTemplate<>();
redis.setConnectionFactory(redisConnectionFactory);
// 设置redis的String/Value默认序列化方法
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
redis.setKeySerializer(stringRedisSerializer);
redis.setValueSerializer(stringRedisSerializer);
redis.setHashKeySerializer(stringRedisSerializer);
redis.setHashValueSerializer(stringRedisSerializer);
redis.afterPropertiesSet();
return redis;
}
复制代码
再次执行,结果是尴尬的事情出现,抛出异常,类型转换失败
java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
at org.springframework.data.redis.serializer.StringRedisSerializer.serialize(StringRedisSerializer.java:33)
at org.springframework.data.redis.core.AbstractOperations.rawHashValue(AbstractOperations.java:171)
at org.springframework.data.redis.core.DefaultHashOperations.putAll(DefaultHashOperations.java:129)
...
复制代码
查看先前的测试案例,map中的value有integer,而 StringRedisSerializer
收到的参数必须为String,所以不要使用这个,根据外观编写另一个兼容的
public class DefaultStrSerializer implements RedisSerializer
然后你就可以开始快乐地玩耍了。,执行后测试
keys *
1) "xacxedx00x05tx00x04hash"
2) "hash"
127.0.0.1:6379> hgetAll hash
1) "123"
2) "hello"
3) "abc"
4) "456"
复制代码
III. RedisTemplate使用姿势
1. opsForXXX
短暂地来这里。RedisTemplate不同数据结构的姿势使用(String, List, ZSet, Hash)Read封装了比较使用的调用方法。 opsForXXX
// hash 数据结构操作
org.springframework.data.redis.core.RedisTemplate#opsForHash
// list
org.springframework.data.redis.core.RedisTemplate#opsForList
// string
org.springframework.data.redis.core.RedisTemplate#opsForValue
// set
org.springframework.data.redis.core.RedisTemplate#opsForSet
// zset
org.springframework.data.redis.core.RedisTemplate#opsForZSet
复制代码
2. execute
除了上述这种方式的使用之外,另一种常见的方式是直接使用execute一个简单的。case如下
@Test
public void testRedis() {
String key = "hello";
String value = "world";
redisTemplate.execute((RedisCallback) con -> {
con.set(key.getBytes(), value.getBytes());
return null;
});
String asn = redisTemplate.execute((RedisCallback) con -> new String(con.get(key.getBytes())));
System.out.println(asn);
String hkey = "hKey";
redisTemplate.execute((RedisCallback) con -> {
con.hSet(hkey.getBytes(), "23".getBytes(), "what".getBytes());
return null;
});
Map map = redisTemplate.execute((RedisCallback
输出结果如下
world
key: 23 | value: what
复制代码
3. 区别
一个自然的问题是,上述两种方法之间的区别是什么?
opsForXXX 的底层称为。execute实现这一点的主要方法是封装一些使用姿势并定义序列化,这更简单,使用更方便。通过这种方式,引入的小号每次都会创建一个新的小号。 DefaultXXXOperations
对象,在此基础上再转一步会带来额外的性能和内存开销吗?没有测试,但在个人感觉小的情况下,应该没有明显的影响。和qps在非常高的情况下,这种方便的优化可以带来的帮助估计不会太大。
作者:A Grey
链接:https://juejin.im/post/5b1e5d4ee51d4506c60e4275
来源:掘金
版权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
版权声明
所有资源都来源于爬虫采集,如有侵权请联系我们,我们将立即删除