评论

收藏

[Java] 在Java中使用redisTemplate操作缓存的方法示例

编程语言 编程语言 发布于:2021-10-05 19:23 | 阅读数:354 | 评论:0

这篇文章主要介绍了在Java中使用redisTemplate操作缓存的方法示例,在Redis中可以存储String、List、Set、Hash、Zset。感兴趣的可以了解一下
背景
在最近的项目中,有一个需求是对一个很大的数据库进行查询,数据量大概在几千万条。但同时对查询速度的要求也比较高。
这个数据库之前在没有使用presto的情况下,使用的是hive,使用hive进行一个简单的查询,速度可能在几分钟。当然几分钟也并不完全是跑sql的时间,这里面包含发请求,查询数据并且返回数据的时间的总和。但是即使这样,这样的速度明显不能满足交互式的查询需求。
我们的下一个解决方案就是presto,在使用了presto之后,查询速度降到了秒级。但是对于一个前端查询界面的交互式查询来说,十几秒仍然是一个不能接受的时间。
虽然presto相比hive已经快了很多(facebook官方宣称的是10倍),但是对分页的支持不是很友好。我在使用的时候是自己在后端实现的分页。
在这种情况下应用缓存实属无奈之举。讲道理,优化应从底层开始,自底而上。上层优化的方式和效率感觉都很有局限。
<!--more-->
为什么要使用缓存
前端查询中,单次查询的匹配数据量有可能会达到上百甚至上千条,在前端中肯定是需要分页展示的。就算每次查询10条数据,整个查询也要耗时6-8s的时间。想象一下,每翻一页等10s的场景。
所以,此时使用redis缓存。减少请求数据库的次数。将匹配的数据一并存入数据库。这样只有在第一次查询时耗费长一点,一旦查询完成,用户点击下一页就是毫秒级别的操作了。
使用redistemplate
spring封装了一个比较强大的模板,也就是redistemplate,方便在开发的时候操作redis缓存。在redis中可以存储string、list、set、hash、zset。下面将针对list和hash分别介绍。
list
redis中的list为简单的字符串列表,常见的有下面几种操作。
haskey
判断一个键是否存在,只需要调用haskey就可以了。假设这个key是test,具体用法如下。
if (redistemplate.haskey("test")) {
  system.out.println("存在");
} else {
  system.out.println("不存在");
}
range
该函数用于从redis缓存中获取指定区间的数据。具体用法如下。
if (redistemplate.haskey("test")) {
  // 该键的值为 [4, 3, 2, 1]
  system.out.println(redistemplate.opsforlist().range("test", 0, 0)); // [4]
  system.out.println(redistemplate.opsforlist().range("test", 0, 1)); // [4, 3]
  system.out.println(redistemplate.opsforlist().range("test", 0, 2)); // [4, 3, 2]
  system.out.println(redistemplate.opsforlist().range("test", 0, 3)); // [4, 3, 2, 1]
  system.out.println(redistemplate.opsforlist().range("test", 0, 4)); // [4, 3, 2, 1]
  system.out.println(redistemplate.opsforlist().range("test", 0, 5)); // [4, 3, 2, 1]
  
  system.out.println(redistemplate.opsforlist().range("test", 0, -1)); // [4, 3, 2, 1] 如果结束位是-1, 则表示取所有的值
}
delete
删除某个键。
list<string> test = new arraylist<>();
test.add("1");
test.add("2");
test.add("3");
test.add("4");
 
redistemplate.opsforlist().rightpushall("test", test);
system.out.println(redistemplate.opsforlist().range("test", 0, -1)); // [1, 2, 3, 4]
redistemplate.delete("test");
system.out.println(redistemplate.opsforlist().range("test", 0, -1)); // []
size
获取该键的集合长度。
list<string> test = new arraylist<>();
test.add("1");
test.add("2");
test.add("3");
test.add("4");
 
redistemplate.opsforlist().rightpushall("test", test);
system.out.println(redistemplate.opsforlist().size("test")); // 4
leftpush
我们把存放这个值的地方想象成如图所示的容器。
container
DSC0000.png

并且取数据总是从左边取,但是存数据可以从左也可以从右。左就是leftpush,右就是rightpush。leftpush如下图所示。
left-push
DSC0001.png

用法如下。
for (int i = 0; i < 4; i++) {
  integer value = i + 1;
  redistemplate.opsforlist().leftpush("test", value.tostring());
  system.out.println(redistemplate.opsforlist().range("test", 0, -1));
}
控制台输出的结果如下。
[1]
[2, 1]
[3, 2, 1]
[4, 3, 2, 1]
leftpushall
基本和leftpush一样,只不过是一次性的将list入栈。
list<string> test = new arraylist<>();
test.add("1");
test.add("2");
test.add("3");
test.add("4");
redistemplate.opsforlist().leftpushall("test", test);
system.out.println(redistemplate.opsforlist().range("test", 0, -1)); // [4, 3, 2, 1]
当然你也可以这样
redistemplate.opsforlist().leftpushall("test", test);
system.out.println(redistemplate.opsforlist().range("test", 0, -1)); // [4, 3, 2, 1]
leftpushifpresent
跟leftpush是同样的操作,唯一的不同是,当且仅当key存在时,才会更新key的值。如果key不存在则不会对数据进行任何操作。
redistemplate.delete("test");
 
redistemplate.opsforlist().leftpushifpresent("test", "1");
redistemplate.opsforlist().leftpushifpresent("test", "2");
system.out.println(redistemplate.opsforlist().range("test", 0, -1)); // []
leftpop
该函数用于移除上面我们抽象的容器中的最左边的一个元素。
list<string> test = new arraylist<>();
test.add("1");
test.add("2");
test.add("3");
test.add("4");
redistemplate.opsforlist().rightpushall("test", test);
 
redistemplate.opsforlist().leftpop("test"); // [2, 3, 4]
redistemplate.opsforlist().leftpop("test"); // [3, 4]
redistemplate.opsforlist().leftpop("test"); // [4]
redistemplate.opsforlist().leftpop("test"); // []
redistemplate.opsforlist().leftpop("test"); // []
值得注意的是,当返回为空后,在redis中这个key也不复存在了。如果此时再调用leftpushifpresent,是无法再添加数据的。有代码有真相。
list<string> test = new arraylist<>();
test.add("1");
test.add("2");
test.add("3");
test.add("4");
redistemplate.opsforlist().rightpushall("test", test);
 
redistemplate.opsforlist().leftpop("test"); // [2, 3, 4]
redistemplate.opsforlist().leftpop("test"); // [3, 4]
redistemplate.opsforlist().leftpop("test"); // [4]
redistemplate.opsforlist().leftpop("test"); // []
redistemplate.opsforlist().leftpop("test"); // []
 
redistemplate.opsforlist().leftpushifpresent("test", "1"); // []
redistemplate.opsforlist().leftpushifpresent("test", "1"); // []
rightpush
rightpush如下图所示。
right-push
DSC0002.png

用法如下。
for (int i = 0; i < 4; i++) {
  integer value = i + 1;
  redistemplate.opsforlist().leftpush("test", value.tostring());
  system.out.println(redistemplate.opsforlist().range("test", 0, -1));
}
控制台输出的结果如下。
[1]
[1, 2]
[1, 2, 3]
[1, 2, 3, 4]
rightpushall
同rightpush,一次性将list存入。
list<string> test = new arraylist<>();
test.add("1");
test.add("2");
test.add("3");
test.add("4");
redistemplate.opsforlist().leftpushall("test", test);
system.out.println(redistemplate.opsforlist().range("test", 0, -1)); // [1, 2, 3, 4]
当然你也可以这样。
redistemplate.opsforlist().rightpushall("test", "1", "2", "3", "4");
system.out.println(redistemplate.opsforlist().range("test", 0, -1)); // [1, 2, 3, 4]
rightpushifpresent
跟rightpush是同样的操作,唯一的不同是,当且仅当key存在时,才会更新key的值。如果key不存在则不会对数据进行任何操作。
redistemplate.delete("test");
 
redistemplate.opsforlist().rightpushifpresent("test", "1");
redistemplate.opsforlist().rightpushifpresent("test", "2");
system.out.println(redistemplate.opsforlist().range("test", 0, -1)); // []
rightpop
该函数用于移除上面我们抽象的容器中的最右边的一个元素。
list<string> test = new arraylist<>();
test.add("1");
test.add("2");
test.add("3");
test.add("4");
redistemplate.opsforlist().rightpushall("test", test);
 
redistemplate.opsforlist().rightpop("test"); // [1, 2, 3]
redistemplate.opsforlist().rightpop("test"); // [1, 2]
redistemplate.opsforlist().rightpop("test"); // [1]
redistemplate.opsforlist().rightpop("test"); // []
redistemplate.opsforlist().rightpop("test"); // []
与leftpop一样,返回空之后,再调用rightpushifpresent,是无法再添加数据的。
index
获取list中指定位置的元素。
if (redistemplate.haskey("test")) {
  // 该键的值为 [1, 2, 3, 4]
  system.out.println(redistemplate.opsforlist().index("test", -1)); // 4
  system.out.println(redistemplate.opsforlist().index("test", 0)); // 1
  system.out.println(redistemplate.opsforlist().index("test", 1)); // 2
  system.out.println(redistemplate.opsforlist().index("test", 2)); // 3
  system.out.println(redistemplate.opsforlist().index("test", 3)); // 4
  system.out.println(redistemplate.opsforlist().index("test", 4)); // null
  system.out.println(redistemplate.opsforlist().index("test", 5)); // null
}
值得注意的有两点。一个是如果下标是-1的话,则会返回list最后一个元素,另一个如果数组下标越界,则会返回null。
trim
用于截取指定区间的元素,可能你会理解成与range是一样的作用。看了下面的代码之后应该就会立刻理解。
list<string> test = new arraylist<>();
test.add("1");
test.add("2");
test.add("3");
test.add("4");
redistemplate.opsforlist().rightpushall("test", test); // [1, 2, 3, 4]
 
redistemplate.opsforlist().trim("test", 0, 2); // [1, 2, 3]
其实作用完全不一样。range是获取指定区间内的数据,而trim是留下指定区间的数据,删除不在区间的所有数据。trim是void,不会返回任何数据。
remove
用于移除键中指定的元素。接受3个参数,分别是缓存的键名,计数事件,要移除的值。计数事件可以传入的有三个值,分别是-1、0、1。
-1代表从存储容器的最右边开始,删除一个与要移除的值匹配的数据;0代表删除所有与传入值匹配的数据;1代表从存储容器的最左边开始,删除一个与要移除的值匹配的数据。
list<string> test = new arraylist<>();
test.add("1");
test.add("2");
test.add("3");
test.add("4");
test.add("4");
test.add("3");
test.add("2");
test.add("1");
 
redistemplate.opsforlist().rightpushall("test", test); // [1, 2, 3, 4, 4, 3, 2, 1]
 
// 当计数事件是-1、传入值是1时
redistemplate.opsforlist().remove("test", -1, "1"); // [1, 2, 3, 4, 4, 3, 2]
 
// 当计数事件是1,传入值是1时
redistemplate.opsforlist().remove("test", 1, "1"); // [2, 3, 4, 4, 3, 2]
 
// 当计数事件是0,传入值是4时
redistemplate.opsforlist().remove("test", 0, "4"); // [2, 3, 3, 2]
rightpopandleftpush
该函数用于操作两个键之间的数据,接受三个参数,分别是源key、目标key。该函数会将源key进行rightpop,再将返回的值,作为输入参数,在目标key上进行leftpush。具体代码如下。
list<string> test = new arraylist<>();
test.add("1");
test.add("2");
test.add("3");
test.add("4");
 
list<string> test2 = new arraylist<>();
test2.add("1");
test2.add("2");
test2.add("3");
 
redistemplate.opsforlist().rightpushall("test", test); // [1, 2, 3, 4]
redistemplate.opsforlist().rightpushall("test2", test2); // [1, 2, 3]
 
redistemplate.opsforlist().rightpopandleftpush("test", "test2");
 
system.out.println(redistemplate.opsforlist().range("test", 0, -1)); // [1, 2, 3]
system.out.println(redistemplate.opsforlist().range("test2", 0, -1)); // [4, 1, 2, 3]
hash
存储类型为hash其实很好理解。在上述的list中,一个redis的key可以理解为一个list,而在hash中,一个redis的key可以理解为一个hashmap。
put
用于写入数据。
list<string> list = new arraylist<>();
list.add("1");
list.add("2");
list.add("3");
list.add("4");
 
redistemplate.opsforhash().put("test", "map", list.tostring()); // [1, 2, 3, 4]
redistemplate.opsforhash().put("test", "isadmin", true); // true
putall
用于一次性向一个hash键中添加多个key。
list<string> list = new arraylist<>();
list.add("1");
list.add("2");
list.add("3");
list.add("4");
list<string> list2 = new arraylist<>();
list2.add("5");
list2.add("6");
list2.add("7");
list2.add("8");
map<string, string> valuemap = new hashmap<>();
valuemap.put("map1", list.tostring());
valuemap.put("map2", list2.tostring());
 
redistemplate.opsforhash().putall("test", valuemap); // {map2=[5, 6, 7, 8], map1=[1, 2, 3, 4]}
putifabsent
用于向一个hash键中写入数据。当key在hash键中已经存在时,则不会写入任何数据,只有在hash键中不存在这个key时,才会写入数据。
同时,如果连这个hash键都不存在,redistemplate会新建一个hash键,再写入key。
list<string> list = new arraylist<>();
list.add("1");
list.add("2");
list.add("3");
list.add("4");
redistemplate.opsforhash().putifabsent("test", "map", list.tostring());
system.out.println(redistemplate.opsforhash().entries("test")); // {map=[1, 2, 3, 4]}
get
用于获取数据。
list<string> list = new arraylist<>();
list.add("1");
list.add("2");
list.add("3");
list.add("4");
 
redistemplate.opsforhash().put("test", "map", list.tostring());
redistemplate.opsforhash().put("test", "isadmin", true);
 
system.out.println(redistemplate.opsforhash().get("test", "map")); // [1, 2, 3, 4]
system.out.println(redistemplate.opsforhash().get("test", "isadmin")); // true
 
boolean bool = (boolean) redistemplate.opsforhash().get("test", "isadmin");
system.out.println(bool); // true
 
string str = redistemplate.opsforhash().get("test", "map").tostring();
list<string> array = jsonarray.parsearray(str, string.class);
system.out.println(array.size()); // 4
值得注意的是,使用get函数获取的数据都是object类型。
所以需要使用类型与上述例子中的布尔类型的话,则需要强制转换一次。list类型则可以使用fastjson这种工具来进行转换。转换的例子已列举在上述代码中。
delete
用于删除一个hash键中的key。可以理解为删除一个map中的某个key。
list<string> list = new arraylist<>();
list.add("1");
list.add("2");
list.add("3");
list.add("4");
list<string> list2 = new arraylist<>();
list2.add("5");
list2.add("6");
list2.add("7");
list2.add("8");
map<string, string> valuemap = new hashmap<>();
valuemap.put("map1", list.tostring());
valuemap.put("map2", list2.tostring());
 
redistemplate.opsforhash().putall("test", valuemap); // {map2=[5, 6, 7, 8], map1=[1, 2, 3, 4]}
redistemplate.opsforhash().delete("test", "map1"); // {map2=[5, 6, 7, 8]}
values
用于获取一个hash类型的键的所有值。
list<string> list = new arraylist<>();
list.add("1");
list.add("2");
list.add("3");
list.add("4");
 
redistemplate.opsforhash().put("test", "map", list.tostring());
redistemplate.opsforhash().put("test", "isadmin", true);
 
system.out.println(redistemplate.opsforhash().values("test")); // [[1, 2, 3, 4], true]
entries
用于以map的格式获取一个hash键的所有值。
list<string> list = new arraylist<>();
list.add("1");
list.add("2");
list.add("3");
list.add("4");
 
redistemplate.opsforhash().put("test", "map", list.tostring());
redistemplate.opsforhash().put("test", "isadmin", true);
 
map<string, string> map = redistemplate.opsforhash().entries("test");
system.out.println(map.get("map")); // [1, 2, 3, 4]
system.out.println(map.get("map") instanceof string); // true
system.out.println(redistemplate.opsforhash().entries("test")); // {a=[1, 2, 3, 4], isadmin=true}
haskey
用于获取一个hash键中是否含有某个键。
ist<string> list = new arraylist<>();
list.add("1");
list.add("2");
list.add("3");
list.add("4");
 
redistemplate.opsforhash().put("test", "map", list.tostring());
redistemplate.opsforhash().put("test", "isadmin", true);
 
system.out.println(redistemplate.opsforhash().haskey("test", "map")); // true
system.out.println(redistemplate.opsforhash().haskey("test", "b")); // false
system.out.println(redistemplate.opsforhash().haskey("test", "isadmin")); // true
keys
用于获取一个hash键中所有的键。
list<string> list = new arraylist<>();
list.add("1");
list.add("2");
list.add("3");
list.add("4");
 
redistemplate.opsforhash().put("test", "map", list.tostring());
redistemplate.opsforhash().put("test", "isadmin", true);
 
system.out.println(redistemplate.opsforhash().keys("test")); // [a, isadmin]
size
用于获取一个hash键中包含的键的数量。
list<string> list = new arraylist<>();
list.add("1");
list.add("2");
list.add("3");
list.add("4");
 
redistemplate.opsforhash().put("test", "map", list.tostring());
redistemplate.opsforhash().put("test", "isadmin", true);
 
system.out.println(redistemplate.opsforhash().size("test")); // 2
increment
用于让一个hash键中的某个key,根据传入的值进行累加。传入的数值只能是double或者long,不接受浮点型
redistemplate.opsforhash().increment("test", "a", 3);
redistemplate.opsforhash().increment("test", "a", -3);
redistemplate.opsforhash().increment("test", "a", 1);
redistemplate.opsforhash().increment("test", "a", 0);
 
system.out.println(redistemplate.opsforhash().entries("test")); // {a=1}
multiget
用于批量的获取一个hash键中多个key的值。
list<string> list = new arraylist<>();
list.add("1");
list.add("2");
list.add("3");
list.add("4");
list<string> list2 = new arraylist<>();
list2.add("5");
list2.add("6");
list2.add("7");
list2.add("8");
 
redistemplate.opsforhash().put("test", "map1", list.tostring()); // [1, 2, 3, 4]
redistemplate.opsforhash().put("test", "map2", list2.tostring()); // [5, 6, 7, 8]
 
list<string> keys = new arraylist<>();
keys.add("map1");
keys.add("map2");
 
system.out.println(redistemplate.opsforhash().multiget("test", keys)); // [[1, 2, 3, 4], [5, 6, 7, 8]]
system.out.println(redistemplate.opsforhash().multiget("test", keys) instanceof list); // true
scan
获取所以匹配条件的hash键中key的值。我查过一些资料,大部分写的是无法模糊匹配,我自己尝试了一下,其实是可以的。如下,使用scan模糊匹配hash键的key中,带scan的key。
list<string> list = new arraylist<>();
list.add("1");
list.add("2");
list.add("3");
list.add("4");
list<string> list2 = new arraylist<>();
list2.add("5");
list2.add("6");
list2.add("7");
list2.add("8");
list<string> list3 = new arraylist<>();
list3.add("9");
list3.add("10");
list3.add("11");
list3.add("12");
map<string, string> valuemap = new hashmap<>();
valuemap.put("map1", list.tostring());
valuemap.put("scan_map2", list2.tostring());
valuemap.put("map3", list3.tostring());
 
redistemplate.opsforhash().putall("test", valuemap); // {scan_map2=[5, 6, 7, 8], map3=[9, 10, 11, 12], map1=[1, 2, 3, 4]}
 
cursor<map.entry<string, string>> cursor = redistemplate.opsforhash().scan("test", scanoptions.scanoptions().match("*scan*").build());
if (cursor.hasnext()) {
  while (cursor.hasnext()) {
  map.entry<string, string> entry = cursor.next();
  system.out.println(entry.getvalue()); // [5, 6, 7, 8]
  }
}
引入redistemplate
如果大家看懂了怎么用,就可以将redistemplate引入项目中了。
引入pom依赖
<dependency>
  <groupid>org.springframework.boot</groupid>
  <artifactid>spring-boot-starter-data-redis</artifactid>
  <version>2.0.5.release</version>
</dependency>
新建配置文件
然后需要新建一个redisconfig配置文件。
package com.detectivehlh;
 
import com.fasterxml.jackson.annotation.jsonautodetect;
import com.fasterxml.jackson.annotation.propertyaccessor;
import com.fasterxml.jackson.databind.objectmapper;
import org.springframework.context.annotation.bean;
import org.springframework.context.annotation.configuration;
import org.springframework.data.redis.connection.redisconnectionfactory;
import org.springframework.data.redis.core.redistemplate;
import org.springframework.data.redis.core.stringredistemplate;
import org.springframework.data.redis.serializer.jackson2jsonredisserializer;
 
/**
 * redisconfig
 *
 * @author lunhao hu
 * @date 2019-01-17 15:12
 **/
@configuration
public class redisconfig {
  @bean
  public redistemplate<string, string> redistemplate(redisconnectionfactory factory) {
  objectmapper om = new objectmapper();
  om.setvisibility(propertyaccessor.all, jsonautodetect.visibility.any);
  om.enabledefaulttyping(objectmapper.defaulttyping.non_final);
  //redis序列化
  jackson2jsonredisserializer jackson2jsonredisserializer = new jackson2jsonredisserializer(object.class);
  jackson2jsonredisserializer.setobjectmapper(om);
 
  stringredistemplate template = new stringredistemplate(factory);
  template.setvalueserializer(jackson2jsonredisserializer);
  template.sethashkeyserializer(jackson2jsonredisserializer);
  template.sethashvalueserializer(jackson2jsonredisserializer);
  template.setvalueserializer(jackson2jsonredisserializer);
  template.afterpropertiesset();
  return template;
  }
}
注入
将redistemplate注入到需要使用的地方。
@autowired
private redistemplate redistemplate;
写在后面
github
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持CodeAE代码之家
原文链接:https://segmentfault.com/a/1190000017940126#leftPush

关注下面的标签,发现更多相似文章