前言
互联网应用中,NoSQL已经广泛应用。Redis是一种内存数据库,Redis可以在1S内完成10万次的操作,针对查询比较大的应用,使用redis作为缓存,可以极大程度提高应用运行效率,为了总结这篇博客,之前已经花了时间进行了redis入门级别的学习,弄清了redis底层的相关操作命令,这个在后面进行总结。但是redis的内容不止有这些,还有redis事务,redis的事务机制可以有效保证在高并发的场景下数据的一致性,针对redis事务的操作,这篇博客后续再进行更新,目前这篇博客只能对spring和springboot中对redis的操作进行总结。
spring中针对redis的操作
redis是一种键值对数据库。但是不管如何,其依旧是一种数据库,spring针对数据访问的操作其实都大同小异,提供对应的ConnectionFactory接口,然后获取对应的connection对象,之后利用对应的connection对象进行数据访问的操作。
但是针对redis的数据访问又有些不同,这个也是因为redis本身的数据类型所致
大体上与其他的数据访问层没有二异,但是我们操作的时候有三种可以选择的连接对象,JedisConnection、JedisClusterConnection、DefaultStringRedisConnection三个,这个也是因为redis本身的特性和功能决定的。spring同时为我们屏蔽了烦人的操作,最终提供的只有一个RedisTemplate对象,我们在实际开发中只需要与这个对象打交道就可以了。
RedisTemplate对象
这个和JPA中的template对象一样。spring已经帮我们做了很多了,RedisTemplate是一个强大的类,它会自动从RedisConnectionFactory中获取连接对象,然后执行对应的Redis命令,在最后还会关闭Redis的连接,基本不需要我们做什么。但是需要理清楚JedisConnectionFactory,JedisPoolConfig,RedisTemplate三个对象之间的关系。JedisConnectionFactory中需要注入JedisPoolConfig,RedisTemplate对象中需要注入JedisConnectionFactory。因为RedisTemplate已经自动完成了连接对象的获取和销毁,所以这里我们不需要与连接对象打交道。
实例代码:
package com.learn.chapter07.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.RedisPassword;
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.RedisSerializer;
import redis.clients.jedis.JedisPoolConfig;
/**
* autor:liman
* mobilNo:15528212893
* mail:657271181@qq.com
* comment:
*/
@Configuration
public class RedisConfig {
private JedisConnectionFactory connectionFactory;
public RedisConfig() {
connectionFactory = null;
}
@Bean(name = "redisConnectionFactory")
public RedisConnectionFactory initConnectionFactory() {
if (this.connectionFactory != null) {
return this.connectionFactory;
}
JedisPoolConfig poolConfig = new JedisPoolConfig();
// 最大空闲数
poolConfig.setMaxIdle(50);
// 最大连接数
poolConfig.setMaxTotal(100);
// 最大等待毫秒数
poolConfig.setMaxWaitMillis(2000);
// 创建Jedis连接工厂
JedisConnectionFactory connectionFactory = new JedisConnectionFactory(poolConfig);
// 配置Redis连接服务器
RedisStandaloneConfiguration rsc = connectionFactory.getStandaloneConfiguration();
rsc.setHostName("127.0.0.1");
rsc.setPort(6379);
// rsc.setPassword(RedisPassword.of("123456"));
this.connectionFactory = connectionFactory;
return connectionFactory;
}
@Bean(name="redisTemplate")
public RedisTemplate<Object, Object> initRedisTemplate() {
RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(initConnectionFactory());
//自动初始化String字符串的序列化转换器,这里直接获取。
RedisSerializer<String> stringRedisSerializer = redisTemplate.getStringSerializer();
//设置字符串序列化器,这样spring就可以把Redis的key当做字符串处理了
redisTemplate.setKeySerializer(stringRedisSerializer);
redisTemplate.setHashKeySerializer(stringRedisSerializer);
redisTemplate.setHashValueSerializer(stringRedisSerializer);
return redisTemplate;
}
}
针对RedisConfig的测试
/**
* 入门测试
*/
@Test
public void testRedisConfig(){
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(RedisConfig.class);
RedisTemplate redisTemplate = ctx.getBean(RedisTemplate.class);
redisTemplate.opsForValue().set("key1", "value1");
redisTemplate.opsForHash().put("hash", "field", "hvalue");
// useSessionCallback(redisTemplate);
// useRedisCallback(redisTemplate);
ctx.close();
}
上述代码只是测试了key-value的数据结构和hash两种数据结构。最终的在redis中数据显示如下:
出现这种情况,其实是由于Redis是一种基于字符串存储的NoSQL,而Java是基于对象的语言,对象是无法存储到redis的数据库中的,但是Java还是个叫Serializable的标记接口,实现了这个接口的对象都能完成序列化,对象序列化之后就会转换成二进制字符串,这样redis就可以将这些类按照字符串存储,但是Java本身的序列化机制转换的并不是正常的字符串。毕竟实现Serializable接口得到的字符串和实现toString()方法得到的字符串是不一样的(不知道这么理解对不对)。
spring提供了对应的序列化器,并且实现了几个序列化器
针对redis数据库中不同的数据类型,spring提供了不同的序列化器,这些序列化器都注入到了RedisTemplate中,同时RedisTemplate还预留了一个默认的序列化器,整个RedisTemplate中的序列化器的属性如下所示:
在之前,没有配置任何序列化器的时候,RedisTemplate会自动使用JdkSerializationRedisSerializer序列化器,这样就产生了不正确的字符串,因此,为了让Redis的键和值以普通的字符串形式保存,我们只需要换一个序列化器就可以了。因此原来的代码中就多了以下几行
//自动初始化String字符串的序列化转换器,这里直接获取。
RedisSerializer<String> stringRedisSerializer = redisTemplate.getStringSerializer();
//设置字符串序列化器,这样spring就可以把Redis的key当做字符串处理了
redisTemplate.setKeySerializer(stringRedisSerializer);
redisTemplate.setHashKeySerializer(stringRedisSerializer);
redisTemplate.setHashValueSerializer(stringRedisSerializer);
SessionCallBack和RedisCallBack接口
上述的实例,的两个操作,其实并不是在一个会话操作中完成的,可以看到前后执行的结果如下图所示:
RedisTemplate在操作redis的时候,会先从Factory中获取一个连接,然后执行对应的Redis命令,再关闭这条连接,执行后面的hash操作的时候,会从Factory中获取另一个连接,然后执行命令,再关闭连接。这样明显存在资源浪费。为了克服这个问题,Spring为我们提供了RedisCallback接口和SessionCallBack接口,同时为了减少序列化转换的问题,Spring还专门提供了SpringRedisTemplate对象,这个类继承至RedisTemplate,但是StringRedisTemplate只是提供了字符串的操作而已,对于复杂的Java对象还需要自行处理,这个都是后话了。
RedisCallBack和SessionCallBack两个接口的区别并不是很大,RedisCallBack相比SessionCallBack,前者更加接近底层,但是他能改写一些底层的东西,在需要改写底层的东西可以使用它。SessionCallBack接口则更加友好。两者实例如下:
@Test
public void testRedisCallBack(){
//1.获取redisTemplate.
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(RedisConfig.class);
RedisTemplate redisTemplate = ctx.getBean(RedisTemplate.class);
//内部类的形式执行redisCallback
redisTemplate.execute(new RedisCallback() {
@Override
public Object doInRedis(RedisConnection redisConnection) throws DataAccessException {
redisConnection.set("key2".getBytes(),"RedisCallBack".getBytes());
redisConnection.hSet("hash2".getBytes(),"field".getBytes(),"redisCallBackhash".getBytes());
return null;
}
});
ctx.close();
}
@Test
public void testSessionCallBack(){
//1.获取redisTemplate.
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(RedisConfig.class);
RedisTemplate redisTemplate = ctx.getBean(RedisTemplate.class);
redisTemplate.execute(new SessionCallback() {
@Override
public Object execute(RedisOperations redisOperations) throws DataAccessException {
redisOperations.opsForValue().set("keyForSessionCallback","sessionCallBackValue".getBytes());
redisOperations.opsForHash().put("hashForSessionCallback","sessionCallbackField","sessionCallbackValue");
return null;
}
});
}
在SessionCallBack使用的时候,使用了spring针对redis数据类型进行操作的接口,具体如下:
RedisTemplate获取对应的接口的操作也比较简单,如下所示:
spring boot中使用redis
在spring中我们需要手动配置ConnectionFactory,但是在springboot中,已经为我们自动注入了这个对象,因此我们在springboot中只需要指定redis的配置即可:
之前针对序列化的问题,已经专门探讨过,在springboot中为了修改RedisTemplate对象的序列化属性,需要利用该对象的后置生命周期函数,修改该对象的序列化属性。
如下所示:
package com.learn.chapter07;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.RedisSerializer;
import javax.annotation.PostConstruct;
@SpringBootApplication(scanBasePackages = "com.learn.chapter07")
public class Chapter07Application {
@Autowired
private RedisTemplate redisTemplate;
@PostConstruct
public void init(){
initRedisTemplate();
}
private void initRedisTemplate(){
RedisSerializer stringSerializer = redisTemplate.getStringSerializer();
redisTemplate.setKeySerializer(stringSerializer);
redisTemplate.setHashKeySerializer(stringSerializer);
}
public static void main(String[] args) {
SpringApplication.run(Chapter07Application.class, args);
}
}
其中的@PostConstruct注解指定的后置bean函数,完成了序列化器的设置。
下面是一下操作redis三种常用数据结构的代码。这里直接贴出
package com.learn.chapter07.redis.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.connection.RedisZSetCommands;
import org.springframework.data.redis.core.*;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import redis.clients.jedis.Jedis;
import java.util.*;
/**
* autor:liman
* mobilNo:15528212893
* mail:657271181@qq.com
* comment:
*/
@Controller
@RequestMapping(value="/redis")
public class RedisController {
@Autowired
private RedisTemplate redisTemplate = null;
@Autowired
private StringRedisTemplate stringRedisTemplate = null;
@RequestMapping("/stringAndHash")
@ResponseBody
public Map<String,Object> redisStringAndHash(){
redisTemplate.opsForValue().set("keyForSpringboot","springbootredisvalue");
//这里没设置序列化器,只是使用了Jdk的序列化器,所以redis保存的不是指定的类型,因此不能运算
redisTemplate.opsForValue().set("int_key","1");
stringRedisTemplate.opsForValue().set("int","1");
stringRedisTemplate.opsForValue().increment("int",1);
//通过原始的jedis操作redis,减法操作RedisTemplate不支持,所以需要通过底层的jedis进行操作。
Jedis jedis = (Jedis)stringRedisTemplate.getConnectionFactory().getConnection().getNativeConnection();
jedis.decr("int");
//构建散列类型的数据,准备存入
Map<String,String> hash = new HashMap<>();
hash.put("field1","value1");
hash.put("field2","value2");
//存入一个散列的数据类型
stringRedisTemplate.opsForHash().putAll("hash",hash);
stringRedisTemplate.opsForHash().put("hash","field1","value1");
//利用BoundHashOperations对象操作hash数据类型,可以连续对同一个散列数据类型进行操作
BoundHashOperations hashOps = stringRedisTemplate.boundHashOps("hash");
//删除两个指定的字段
hashOps.delete("field1","field2");
//新增一个字段
hashOps.put("field5","value5");
Map<String,Object> result = new HashMap<String,Object>();
result.put("success",true);
return result;
}
@RequestMapping("/list")
@ResponseBody
public Map<String,Object> ListOperation(){
stringRedisTemplate.opsForList().leftPushAll("list1","v2","v4","v6","v8","v10");
stringRedisTemplate.opsForList().rightPushAll("list2","v1","v3","v5","v7","v9");
//绑定list2链表操作
BoundListOperations listOps = stringRedisTemplate.boundListOps("list2");
//从右边弹出一个元素
Object element = listOps.rightPop();
Object posElement = listOps.index(2);
listOps.leftPush("v0");
Long size = listOps.size();
List elements = listOps.range(0,size-2);
for(Object obj : elements){
System.out.println(obj);
}
Map<String,Object> result = new HashMap<String,Object>();
result.put("success",true);
return result;
}
@RequestMapping("/set")
@ResponseBody
public Map<String,Object> setOperation(){
stringRedisTemplate.opsForSet().add("set1","v1","v1","v2","v3","v4","v5","v6");
stringRedisTemplate.opsForSet().add("set2","v2","v4","v6","v8");
//绑定set1集合
BoundSetOperations setOps = stringRedisTemplate.boundSetOps("set1");
//增加两个元素
setOps.add("v7","v8");
setOps.remove("v1","v7");
//返回所有元素
Set set1 = setOps.members();
Long size = setOps.size();
//求交集
Set inter = setOps.intersect("set2");
for(Object obj:inter){
System.out.println(obj);
}
//求交集,并用新集合inter保存
setOps.intersectAndStore("set2","inter");
//求差集
Set diff = setOps.diff("set2");
for(Object obj:inter){
System.out.println(obj);
}
//求差集,并用新集合diff保存
setOps.diffAndStore("set2","diff");
Set union = setOps.union("set2");
for(Object obj:inter){
System.out.println(obj);
}
//求并集,并用新集合union保存
setOps.unionAndStore("set2","union");
Map<String,Object> result = new HashMap<String,Object>();
result.put("success",true);
return result;
}
@RequestMapping("/zset")
@ResponseBody
public Map<String,Object> zSetOperation(){
//存储分数的集合
Set<ZSetOperations.TypedTuple<String>> typedTupleSet = new HashSet<>();
for(int i=1;i<=9;i++){
double score = i*0.1;
ZSetOperations.TypedTuple<String> typedTuple = new DefaultTypedTuple<String>("value"+i,score);
typedTupleSet.add(typedTuple);
}
//往有序集合中插入元素
stringRedisTemplate.opsForZSet().add("zset1",typedTupleSet);
BoundZSetOperations<String,String> boundZSetOperations = stringRedisTemplate.boundZSetOps("zset1");
boundZSetOperations.add("value10",0.26);
Set<String> setRange = boundZSetOperations.range(1,6);
//将分数排序获取有序集合
Set<String> setScore = boundZSetOperations.rangeByScore(0.2,0.6);
//定义取值范围
RedisZSetCommands.Range range = new RedisZSetCommands.Range();
//大于value3
range.gt("value3");
//小于等于value8
range.lte("value8");
//按值排序
Set<String> setLex = boundZSetOperations.rangeByLex(range);
//删除元素
boundZSetOperations.remove("value9","value2");
//求分数
Double score = boundZSetOperations.score("value8");
Set<ZSetOperations.TypedTuple<String>> rangSet = boundZSetOperations.rangeWithScores(1,6);
Set<String> reverseSet = boundZSetOperations.reverseRange(2,8);
Map<String,Object> result = new HashMap<String,Object>();
result.put("success",true);
return result;
}
}
说明:这里直接采用的是Controller形式进行的测试,当然依旧还是可以用测试脚本进行测试。
总结
该篇文章只是针对spring中对redis的操作进行了一个简单的总结,针对redis事务的操作却没有进一步总结,针对redis的数据结构,也没有进行详细的归纳,后面会有相关的博客进行补充。同时该篇博客依旧参考了《深入浅出 spring boot 2.X》一书。
---------------------
版权声明:本文为CSDN博主「谜一样的Coder」的原创文章,遵循CC 4.0 by-sa版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/liman65727/article/details/84727017