redis学习记录(四)-SpringDataRedis分析
标签(空格分隔): redis
个人独立博客: http://mrdear.cn
Redis学习记录(一)–入门知识
Redis学习记录(二)–使用Jedis连接
redis学习记录(三)-redis中的数据结构
1.简介
Spring Data Redis是对redis客户端(如jedis)的高度封装,支持多种客户端,因其高抽象,所以在某一个客户端不支持更新的时候可以容易切换到其他客户端.
本文是在Spring boot 1.5.2版本下测试.
需要引入架包
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.2.RELEASE</version>
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<!--spring boot start-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
2.配置
在Spring Boot下默认使用jedis作为客户端,并在包org.springframework.boot.autoconfigure.data.redis
下,提供自动配置类RedisProperties
,RedisAutoConfiguration
等.
根据RedisProperties
可以定位到可配置的属性,如:
# Redis数据库索引(默认为0)
spring.redis.database=0
# Redis服务器地址
spring.redis.host=115.159.185.14
# Redis服务器连接端口
spring.redis.port=6379
# Redis服务器连接密码(默认为空)
spring.redis.password=
# 连接池最大连接数(使用负值表示没有限制)
spring.redis.pool.max-active=8
# 连接池最大阻塞等待时间(使用负值表示没有限制)
spring.redis.pool.max-wait=-1
# 连接池中的最大空闲连接
spring.redis.pool.max-idle=8
# 连接池中的最小空闲连接
spring.redis.pool.min-idle=0
# 连接超时时间(毫秒)
spring.redis.timeout=2000
在application.properties中配置即可,另外还有Sentinel
和Cluster
说明支持分布式和集群,博主研究不多就不瞎说这个了.
自动配置主要在RedisAutoConfiguration
中,该类会提供三个bean:
1. JedisConnectionFactory : jedis连接控制工厂
2. RedisTemplate
3.RedisTemplate
3.1 Test case
那么具体操作过程是怎么样子的呢?写一个简单的测试去跟踪代码,如下代码,往redis中设置key为ping的字串.
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = Application.class)
public class RedisConnectTest {
@Resource
private RedisTemplate<String,String> redisTemplate;
@Test
public void testSetAndGet() {
redisTemplate.opsForValue().set("ping","pong");
System.out.println(redisTemplate.opsForValue().get("ping"));
}
}
运行之后查看redis数据库,你会发现很奇怪的事情,如下图,代码中存入的是ping,但是到redis中后却是一堆字符+ping,这个原因是什么呢?接着跟踪代码.
3.2 XXXOperations
3.3 XXXSerializer
那测试代码中第一步是获取了string类型的redis操作入口,然后执行set方法设置键和值,接着分析set方法.
public void set(K key, V value) {
final byte[] rawValue = rawValue(value);
execute(new ValueDeserializingRedisCallback(key) {
protected byte[] inRedis(byte[] rawKey, RedisConnection connection) {
connection.set(rawKey, rawValue);
return null;
}
}, true);
}
可以发现rawKey()
方法和rawValue()
方法对key和value进行了一次序列化操作.该序列化使用的类为RedisTemplate中的XXXSerializer
,那么回到RedisTemplate,在afterPropertiesSet()
方法中有以下初始化方法,默认使用的序列化方式为JdkSerializationRedisSerializer
,也就是ObjectInputStream和ObjectOutputStream写入和读取.这也是写入到redis中却在redis数据库通过”ping”访问不到的原因.
if (defaultSerializer == null) {
defaultSerializer = new JdkSerializationRedisSerializer(
classLoader != null ? classLoader : this.getClass().getClassLoader());
}
if (enableDefaultSerializer) {
if (keySerializer == null) {
keySerializer = defaultSerializer;
defaultUsed = true;
}
if (valueSerializer == null) {
valueSerializer = defaultSerializer;
defaultUsed = true;
}
if (hashKeySerializer == null) {
hashKeySerializer = defaultSerializer;
defaultUsed = true;
}
if (hashValueSerializer == null) {
hashValueSerializer = defaultSerializer;
defaultUsed = true;
}
}
那么SpringDataRedis支持哪些序列化呢?从官网可以看到:
StringRedisSerializer: string类型序列化,也是最常用的类型
JdkSerializationRedisSerializer: jdk默认序列化
OxmSerializer : xml格式
JacksonJsonRedisSerializer : json格式
通过手动注入RedisTemplate,更改所选择的序列化方式.另外Spring提供了最常使用的StringRedisTemplate
,实现了StringRedisSerializer
序列化方式.
public StringRedisTemplate() {
RedisSerializer<String> stringSerializer = new StringRedisSerializer();
setKeySerializer(stringSerializer);
setValueSerializer(stringSerializer);
setHashKeySerializer(stringSerializer);
setHashValueSerializer(stringSerializer);
}
更改成StringRedisTemplate
,再次执行,正常了.
3.4 总结过程
- 获取RedisTemplate
- 获取操作入口XXXOperations
- 使用RedisSerializer序列化key和value
- 获取conn连接
- 执行命令
4.发布与订阅
发布与订阅过程需要发布者,订阅者,以及把两者连在一起的桥梁.那么在SpringRedis中怎么实现呢?
订阅者:里面有一个处理方法即可.
@Component
public class Listen {
private static Logger logger = LoggerFactory.getLogger(Listen.class);
private CountDownLatch latch = new CountDownLatch(1);
public void handleMsg(String message) {
logger.info("reciver msg :" + message);
latch.countDown();
}
public CountDownLatch getLatch() {
return latch;
}
}
发布者:XXXRedisTemplate.convertAndSend(chanel,msg)即作为发布者存在.
连接桥梁:RedisMessageListenerContainer,该container监听Redis的消息,分发给各自的监听者.关键代码为
@Configuration
public class PublishConfig {
/**
* 注入消息容器
* @param jedisConnectionFactory jedis连接池
* @param listenerAdapter 监听适配器
* @return bean
*/
@Bean
public RedisMessageListenerContainer container(RedisConnectionFactory jedisConnectionFactory,
MessageListenerAdapter listenerAdapter){
RedisMessageListenerContainer container = new RedisMessageListenerContainer();
container.setConnectionFactory(jedisConnectionFactory);
//绑定监听者与信道的管理
container.addMessageListener(listenerAdapter,new PatternTopic("java"));
return container;
}
@Bean
public MessageListenerAdapter adapter(Listen listen){
//指定监听者和监听方法
return new MessageListenerAdapter(listen,"handleMsg");
}
}
测试:
@Test
public void testPublish() throws InterruptedException {
stringRedisTemplate.convertAndSend("java","hello world");
listen.getLatch().await();
}
5.事务
对于事务的操作是通过SessionCallback实现,该接口保证其内部所有操作都是在同一个Session中的,在最后exec的时候执行全部操作.关键代码如下
RedisConnectionUtils.bindConnection(factory, enableTransactionSupport);
execute(this)
@Test
public void testMulti() {
boolean isThrow = false;
List<Object> result = null;
try {
result = stringRedisTemplate.execute(new SessionCallback<List<Object>>() {
@Override
public List<Object> execute(RedisOperations operations) throws
DataAccessException {
operations.multi();
ValueOperations<String,String> ops = operations.opsForValue();
ops.set("ping1","pong1");
ops.set("ping2","pong2");
if (isThrow){
throw new IllegalArgumentException("测试异常");
}
return operations.exec();
}
});
} catch (Exception e) {
e.printStackTrace();
}
System.out.println(result);
}
6.管道
直接引用官方案例
//pop a specified number of items from a queue
List<Object> results = stringRedisTemplate.executePipelined(
new RedisCallback<Object>() {
public Object doInRedis(RedisConnection connection) throws DataAccessException {
StringRedisConnection stringRedisConn = (StringRedisConnection)connection;
for(int i=0; i< batchSize; i++) {
stringRedisConn.rPop("myqueue");
}
return null;
}
});
还有脚本执行等,在官方文档中都有案例,这里就不复制粘贴了,如有错误请指出,不胜感激.
参考文档:
http://docs.spring.io/spring-data/redis/docs/1.8.1.RELEASE/reference/html/#redis:template
github: