4、继续追踪set方法,选择JedisStringCommands实现
5、继续之宗set方法,可以看到底层已经获取到了jedis的实例,再调用set方法已经在调jedis的set了
6、再追踪一步,就会发现,底层最终调用的是jedis的原生API,setCommand方法,这个方法就是jedis提供的对redis的各种操作命令了。
至此,我们的分析完毕。得到的结论就是:
SpringData提供redisTemplate就是在原生的Jedis或者其他操作redis的技术上做的一层封装,它屏蔽掉了这些原生技术的实现细节,统一了调用接口,使得我们的操作更加简单明了。
6 SpringData Redis常见操作
本章节我们来学习如何使用SpringData Redis来操作Redis的各种数据类型.
在Redis中有五种常见类型,SpringData Redis对每一种数据类型都提供了一个xxxOperations的API,他们分别是:
ValueOperations :用来操作字符串类型数据
HashOperations:用来操作hash类型数据
ListOperations:用来操作list类型数据
SetOperations:用来操作set类型数据
ZSetOperations:用来操作zset类型数据
6.1 String类型
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration("classpath:applicationContext-redis.xml") public class RedisStringTest { @Autowired private RedisTemplate redisTemplate; ValueOperations<String, String> operations = null; @Before public void init() { redisTemplate.setKeySerializer(new StringRedisSerializer()); redisTemplate.setValueSerializer(new StringRedisSerializer()); operations = redisTemplate.opsForValue(); } @Test public void testSet() { //向数据库中保存name--oldlu operations.set("name","oldlu"); //相关数据库保存name1--oldlu1 有效时间为10s operations.set("name1", "oldlu1", 10, TimeUnit.SECONDS); //替换 oldlu ---> heXXa offset 索引位置是从0开始 operations.set("name", "XX", 2); //当key不存在的时候,执行保存操作;当key存在的时候,什么都不做 operations.setIfAbsent("name1","oldlu"); //批量保存 Map map = new HashMap(); map.put("name2", "oldlu2"); map.put("name3", "oldlu3"); map.put("name4", "oldlu4"); operations.multiSet(map); //追加 当key存在时,会执行追加操作;当key不存在时,会执行保存操作 operations.append("name5", "oldlu"); } @Test public void testGet() { //根据key获取value String value = operations.get("name"); System.out.println(value);//heXXaoldlu //首先根据key获取value,然后再根据value进行截取,从start位置截取到end位置[包含start和end] String value2 = operations.get("name", 5, 7); System.out.println(value2);//heXXaoldlu-->Hei //批量获取 List<String> keys = new ArrayList<>(); keys.add("name2"); keys.add("name3"); keys.add("name4"); List<String> values = operations.multiGet(keys); for (String s : values) { System.out.println(s); } //根据key获取value的长度 Long size = operations.size("name"); System.out.println(size); } //自增 @Test public void testIncrement() { operations.set("age", "18"); operations.increment("age");//自增1--->19 System.out.println(operations.get("age")); operations.increment("age", 5);//自增5 System.out.println(operations.get("age"));//---->24 //自减 operations.decrement("age") } //删除 @Test public void testDelete() { //单个删除 redisTemplate.delete("name"); List<String> keys = new ArrayList<>(); keys.add("name2"); keys.add("name3"); keys.add("name4"); //批量删除 redisTemplate.delete(keys); } }
6.2 Hash类型
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration("classpath:applicationContext-redis.xml") public class RedisHashTest { @Autowired private RedisTemplate redisTemplate; HashOperations<String, String, Article> operations = null; @Before public void init() { redisTemplate.setKeySerializer(new StringRedisSerializer()); redisTemplate.setHashKeySerializer(new StringRedisSerializer()); redisTemplate.setHashValueSerializer(new JdkSerializationRedisSerializer()); operations = redisTemplate.opsForHash(); } //保存 @Test public void testPut() { Article article = new Article(); article.setTitle("黑马"); article.setAuthor("黑马"); article.setCreateTime(new Date()); operations.put("article", "3", article); } //获取 @Test public void testGet() { //判断hashkey是否存在 Boolean flag = operations.hasKey("article", "3"); System.out.println(flag); //根据key和hashkay获取操作 Article article = operations.get("article", "2"); System.out.println(article); //根据key获取所有的hashkey Set<String> set = operations.keys("article"); for (String s : set) { System.out.println(s); } List<Article> articles = operations.values("article"); for (Article art : articles) { System.out.println(art); } Map<String, Article> map = operations.entries("article"); for (Map.Entry<String, Article> entry : map.entrySet()) { System.out.println(entry.getKey() + ":" + entry.getValue()); } } //删除 @Test public void testDelete() { //当hash中的数据全部被删除后,整个hash就没了 operations.delete("article", "2", "3"); } }
6.3 List类型
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration("classpath:applicationContext.xml") public class ListTest { @Autowired private RedisTemplate<String, String> redisTemplate; private ListOperations<String, String> operations; @Before public void before() { redisTemplate.setKeySerializer(new StringRedisSerializer()); redisTemplate.setValueSerializer(new StringRedisSerializer()); operations = redisTemplate.opsForList(); } //增加 @Test public void testAdd() { // 将所有指定的值插入存储在键的列表的头部(或尾部)。 // 如果键不存在,则在执行推送操作之前将其创建为空列表。 operations.leftPush("names", "zhangsan"); operations.leftPushAll("names", "lisi","wangwu","zhaoliu"); operations.rightPush("names", "sunqi"); operations.rightPushAll("names", "lisi","wangwu","zhaoliu"); } //根据索引查询元素 @Test public void testFind() { //0代表左边开始第一个元素 String name1 = operations.index("names", 0); System.out.println(name1); //-1代表右边开始第一个元素 String name2 = operations.index("names", -1); System.out.println(name2); //range代表一个范围(包含开始索引,结束索引) List<String> names = operations.range("names", 0, 1); for (String name : names) { System.out.println(name); } } //移除某个元素的值 @Test public void testRemove() { //从坐标或者右边弹出第一个元素 operations.rightPop("names"); //弹出指定的元素 // count > 0:删除左边起第几个等于指定值的元素 // count < 0:删除右边起第几个等于指定值的元素 // count = 0:删除所有等于value的元素。 operations.remove("names", 1, "zhangsan"); } }
6.4 Set类型
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration("classpath:applicationContext.xml") public class SetTest { @Autowired private RedisTemplate<String, String> redisTemplate; private SetOperations<String, String> operations; @Before public void before() { redisTemplate.setKeySerializer(new StringRedisSerializer()); redisTemplate.setValueSerializer(new StringRedisSerializer()); operations = redisTemplate.opsForSet(); } //添加 @Test public void testAdd() { operations.add("names", "zhangsan", "li", "wangwu"); } //查看集合中元素 @Test public void testFind() { //查询集合中的所有元素 operations.members("names"); //随机获取集合中的一个元素 String name = operations.randomMember("names"); System.out.println(name); //随机获取集合中的多个元素 operations.randomMembers("names",2).stream().forEach(System.out::println); } //移除元素 @Test public void testRemove() { //根据指定的key--value进行移除 operations.remove("names", "zhangsan", "li"); //根据key随机移除并返回value String name = operations.pop("names"); System.out.println(name); } //多集合的操作 @Test public void testMoreSet() { operations.add("names1", "zhangsan", "li", "wangwu"); operations.add("names2", "zhangsan", "li", "zhaoliu"); //取交集 operations.intersect("names1","names2").stream().forEach(System.out::println); //取并集 operations.union("names1", "names2").stream().forEach(System.out::println); //取差集 operations.difference("names1","names2").stream().forEach(System.out::println); } }
6.5 ZSet类型
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration("classpath:applicationContext-redis.xml") public class ZSetTest { @Autowired private RedisTemplate<String, String> redisTemplate; private ZSetOperations<String, String> operations; @Before public void before() { redisTemplate.setKeySerializer(new StringRedisSerializer()); redisTemplate.setValueSerializer(new StringRedisSerializer()); operations = redisTemplate.opsForZSet(); } //添加 @Test public void testAdd() { operations.add("students", "zhangsan", 100); operations.add("students", "lisi", 80); operations.add("students", "wangwu", 90); } //增减分数 @Test public void testScore() { //根据key-value来增减分数 operations.incrementScore("students", "lisi", -50); } //查询一个元素的信息 @Test public void testFindOne() { //查询个人分数 Double score = operations.score("students", "lisi"); System.out.println(score); //查询个人排名 Long rank = operations.rank("students", "lisi"); System.out.println(rank); } //列表查询:分为两大类,正序和逆序。以下只列表正序的,逆序的只需在方法前加上reverse即可 @Test public void testFindList() { //通过排名区间获取集合元素 Set<String> students1 = operations.range("students", 1, 2); for (String stu : students1) { System.out.println(stu); } //通过排名区间获取集合元素和分数 Set<ZSetOperations.TypedTuple<String>> students2 = operations.rangeWithScores("students", 1, 2); for (ZSetOperations.TypedTuple<String> tuple : students2) { String value = tuple.getValue(); Double score = tuple.getScore(); System.out.println("value-->" + value + ",score-->" + score); } //通过分数区间获取集合元素 Set<String> students3 = operations.rangeByScore("students", 80, 90); for (String stu : students3) { System.out.println(stu); } //通过分数区间获取集合元素和分数 Set<ZSetOperations.TypedTuple<String>> students4 = operations.rangeByScoreWithScores("students", 80, 90); for (ZSetOperations.TypedTuple<String> tuple : students4) { String value = tuple.getValue(); Double score = tuple.getScore(); System.out.println("value-->" + value + ",score-->" + score); } } //统计元素 @Test public void testCount() { //统计集合大小 Long zCard = operations.zCard("students"); System.out.println(zCard); //统计分数区间的元素数量 Long count = operations.count("students", 85, 95); System.out.println(count); } //移除元素 @Test public void testRemove() { //通过key--value删除 operations.remove("students", "zhangsan"); //通过排名区间删除 operations.removeRange("students", 0, 1); //通过分数区间删除 operations.removeRangeByScore("students", 80, 90); } }
7 Repository和Template的选用
经过前面的章节,我们学习了SpringData家族中jpa和redis的使用,在感受到SpringData技术使用方便的同时,也隐隐约约感觉有点问题,那就是jpa和redis的使用思路好像不是很一致。
我们使用SpringDataJpa的时候,采用了继承SpringData提供的一个接口的形式,即public interface ArticleDao extends JpaRepository<Article, Integer>,JpaSpecificationExecutor<Article>,但是使用SpingData Redis的时候,却是使用了在实现类中注入一个redisTemplate的方式,那么这两种方式到底有什么关系,用哪个更好呢?
其实这两种方式都可以完成我们对持久层的操作,但是对比两种方式的使用,就会发现:
第一种方式,直接继承xxxRepository接口,可以不必自己去写实现类,而轻松实现简单的增删改查、分页、排序操作,但是对于非常复杂的查询,用起来就比较的费力了;
第二种方式,直接使用xxxTemplate,就需要自己写实现类,但是这样增删改查可以自己控制,对于复杂查询来说,用起来更加得心应手。
所以,两种方式在企业开发中都可能用到,甚至有的项目开发中会同时使用两种方式:对于简单的操作,直接继承Repository接口,对于复杂操作,使用Template完成。所以我们用的时候也要根据实际场景进行灵活选用。
8 开发中遇到的常见问题
无法创建jedis,一些列一看就是配置文件的问题,大部分可能就是jedis于redis版本不符.
9 redis开发中的需求解决
9.1 expire命令可以设置key的存活时间
如果需要把有过期时间的key设置为不过期的,可以使用persist命令来持久化key。 命令 : PERSIST key
持久化key,默认不赋值就是永久为-1
9.2 过期时间查看
redis过期时间相关命令
1.EXPIRE PEXPIREEXPIRE接口定义:EXPIRE key “seconds"接口描述:设置一个key在当前时间"seconds”(秒)之后过期。返回1代表设置成功,返回0代表key不存在或者无法设置过期时间。PEXPIRE接口定义:PEXPIRE key “milliseconds"接口描述:设置一个key在当前时间"milliseconds”(毫秒)之后过期。返回1代表设置成功,返回0代表key不存在或者无法设置过期时间。
2.EXPIREAT PEXPIREATEXPIREAT接口定义:EXPIREAT key “timestamp"接口描述:设置一个key在"timestamp”(时间戳(秒))之后过期。返回1代表设置成功,返回0代表key不存在或者无法设置过期时间。PEXPIREAT接口定义:PEXPIREAT key “milliseconds-timestamp"接口描述:设置一个key在"milliseconds-timestamp”(时间戳(毫秒))之后过期。返回1代表设置成功,返回0代表key不存在或者无法设置过期时间
3.TTL PTTLTTL接口定义:TTL key接口描述:获取key的过期时间。如果key存在过期时间,返回剩余生存时间(秒);如果key是永久的,返回-1;如果key不存在或者已过期,返回-2。PTTL接口定义:PTTL key接口描述:获取key的过期时间。如果key存在过期时间,返回剩余生存时间(毫秒);如果key是永久的,返回-1;如果key不存在或者已过期,返回-2。