java操作redis学习笔记
一、jedis操作:
1、POM依赖:
1 <dependency> 2 <groupId>redis.clients</groupId> 3 <artifactId>jedis</artifactId> 4 <version>2.5.0</version> 5 </dependency>
2、建一个连接redis数据库的工具类:
1 public class RedisUtil { 2 3 //服务器IP地址 4 private static String ADDR = "x.x.x.x"; 5 6 //端口 7 private static int PORT = 6379; 8 //密码 9 private static String AUTH = "123456"; 10 //连接实例的最大连接数 11 private static int MAX_ACTIVE = 1024; 12 13 //控制一个pool最多有多少个状态为idle(空闲的)的jedis实例,默认值也是8。 14 private static int MAX_IDLE = 200; 15 16 //等待可用连接的最大时间,单位毫秒,默认值为-1,表示永不超时。如果超过等待时间,则直接抛出JedisConnectionException 17 private static int MAX_WAIT = 10000; 18 19 //连接超时的时间 20 private static int TIMEOUT = 10000; 21 22 // 在borrow一个jedis实例时,是否提前进行validate操作;如果为true,则得到的jedis实例均是可用的; 23 private static boolean TEST_ON_BORROW = true; 24 25 private static JedisPool jedisPool = null; 26 27 /** 28 * 初始化Redis连接池 29 */ 30 31 static { 32 33 try { 34 35 JedisPoolConfig config = new JedisPoolConfig(); 36 config.setMaxTotal(MAX_ACTIVE); 37 config.setMaxIdle(MAX_IDLE); 38 config.setMaxWaitMillis(MAX_WAIT); 39 config.setTestOnBorrow(TEST_ON_BORROW); 40 jedisPool = new JedisPool(config, ADDR, PORT, TIMEOUT, AUTH); 41 42 } catch (Exception e) { 43 44 e.printStackTrace(); 45 } 46 47 } 48 49 /** 50 * 获取Jedis实例 51 */ 52 53 public synchronized static Jedis getJedis() { 54 55 try { 56 57 if (jedisPool != null) { 58 Jedis resource = jedisPool.getResource(); 59 return resource; 60 } else { 61 return null; 62 } 63 64 } catch (Exception e) { 65 e.printStackTrace(); 66 return null; 67 } 68 69 } 70 71 /*** 72 * 73 * 释放资源 74 */ 75 76 public static void returnResource(final Jedis jedis) { 77 if(jedis != null) { 78 jedisPool.returnResource(jedis); 79 } 80 81 } 82 }
3、实现redis的增删改查:
1 public class TestRedis { 2 3 private Jedis jedis; 4 5 /** 6 * 连接redis服务器 7 */ 8 public void connectRedis() { 9 jedis=RedisUtil.getJedis(); 10 } 11 12 /** 13 * redis操作字符串 14 */ 15 public void testString() { 16 //添加数据 17 jedis.set("name", "youcong"); 18 System.out.println(jedis.get("name")); 19 20 //拼接字符串 21 jedis.append("name", ".com"); 22 System.out.println(jedis.get("name")); 23 24 //删除数据 25 jedis.del("name"); 26 System.out.println(jedis.get("name")); 27 28 //设置多个键值对 29 jedis.mset("name","yc","age","22","qq","1933108196"); 30 jedis.incr("age");//加1操作 31 System.out.println(jedis.get("name") + "-" + jedis.get("age") + "-" +jedis.get("qq")); 32 } 33 34 35 /** 36 * redis操作map集合 37 */ 38 public void testMap() { 39 //添加数据 40 Map<String,String> map = new HashMap<String,String>(); 41 42 map.put("name", "yc"); 43 map.put("age", "22"); 44 map.put("qq", "1933108196"); 45 jedis.hmset("user", map); 46 47 //取出users中的Name,执行结果:[minxr]-->注意结果是一个泛型的List 48 //第一个参数是存入redis中map对象的key,后面跟的是放入map中对象的key,后面的key可以是多个,是可变的 49 List<String> rsmap = jedis.hmget("user", "name","age","qq"); 50 System.out.println(rsmap); 51 52 53 //删除map中的某个键值 54 jedis.hdel("user", "age"); 55 System.out.println(jedis.hmget("user", "age"));//因为删除了,所以返回的是Null 56 System.out.println(jedis.hlen("user"));//返回key为user的键中存放的值的个数2 57 System.out.println(jedis.exists("user"));//是否存在key为user的记录,返回true 58 System.out.println(jedis.hkeys("user"));//返回map对象中的所有key 59 System.out.println(jedis.hvals("user"));//返回map对象中的所有value 60 61 Iterator<String> iter = jedis.hkeys("user").iterator(); 62 while(iter.hasNext()) { 63 String key = iter.next(); 64 System.out.println(key+":" + jedis.hmget("user", key)); 65 } 66 67 } 68 69 /** 70 * redis操作List集合 71 */ 72 public void testList() { 73 //开始前,先移除所有的内容 74 jedis.del("java framework"); 75 System.out.println(jedis.lrange("java framework", 0, -1)); 76 77 //先向key java framework 中存放三条数据 78 jedis.lpush("java framework","spring"); 79 jedis.lpush("java framework", "struts"); 80 jedis.lpush("java framework", "hibernate"); 81 82 //再取出所有数据jedis.lrange是按范围取出 83 //第一个是key,第二个是起始位置,第三个是结束位置,jedis.llen获取长度 -1表示取得所有 84 System.out.println(jedis.lrange("java framework", 0, -1)); 85 86 jedis.del("java framework"); 87 jedis.rpush("java framework", "spring"); 88 jedis.rpush("java framework", "struts"); 89 jedis.rpush("java framework","hibernate"); 90 System.out.println(jedis.lrange("java framework", 0, -1)); 91 92 93 } 94 95 96 /** 97 * redis操作set集合 98 * 99 */ 100 101 public void testSet() { 102 103 //添加 104 jedis.sadd("user", "liuling"); 105 jedis.sadd("user", "xinxin"); 106 jedis.sadd("user","ling"); 107 jedis.sadd("user", "zhangxinxin"); 108 jedis.sadd("user", "who"); 109 110 //删除 111 jedis.srem("user", "who"); 112 System.out.println(jedis.smembers("user"));//获取所有加入的value 113 System.out.println(jedis.sismember("user", "who"));//判断who是否是user集合的元素 114 System.out.println(jedis.srandmember("user")); 115 System.out.println(jedis.scard("user"));//返回集合的元素个数 116 } 117 118 119 /** 120 * redis排序 121 */ 122 123 public void testSort() { 124 125 //jedis 排序 126 //注意,此处的rpush和lpush是List的操作。是一个双向链表(但从表现来看的) 127 jedis.del("a");//先清除数据,再加入数据进行测试 128 jedis.rpush("a", "1"); 129 jedis.lpush("a", "6"); 130 jedis.lpush("a", "3"); 131 jedis.lpush("a", "9"); 132 System.out.println(jedis.lrange("a", 0, -1)); 133 System.out.println(jedis.sort("a"));//[1,3,6,9] //输入排序后结果 134 System.out.println(jedis.lrange("a", 0, -1)); 135 } 136 137 138 /** 139 * redis连接池 140 */ 141 142 public void testRedisPool() { 143 144 RedisUtil.getJedis().set("newname", "test"); 145 146 System.out.println(RedisUtil.getJedis().get("newname")); 147 } 148 149 public static void main(String[] args) { 150 TestRedis test = new TestRedis(); 151 test.connectRedis(); 152 test.testSort(); 153 } 154 }
二、关于spring-data-redis:
1. 连接池自动管理,提供了一个高度封装的“RedisTemplate”类
2. 针对jedis客户端中大量api进行了归类封装,将同一类型操作封装为operation接口
ValueOperations:简单K-V操作
SetOperations:set类型数据操作
ZSetOperations:zset类型数据操作
HashOperations:针对map类型的数据操作
ListOperations:针对list类型的数据操作
3. 提供了对key的“bound”(绑定)便捷化操作API,可以通过bound封装指定的key,然后进行一系列的操作而无须“显式”的再次指定Key,即BoundKeyOperations:
BoundValueOperations
BoundSetOperations
BoundListOperations
BoundSetOperations
BoundHashOperations
4. 将事务操作封装,有容器控制。
5. 针对数据的“序列化/反序列化”,提供了多种可选择策略(RedisSerializer)
JdkSerializationRedisSerializer:POJO对象的存取场景,使用JDK本身序列化机制,将pojo类通过ObjectInputStream/ObjectOutputStream进行序列化操作,最终redis-server中将存储字节序列。是目前最常用的序列化策略。
StringRedisSerializer:Key或者value为字符串的场景,根据指定的charset对数据的字节序列编码成string,是“new String(bytes, charset)”和“string.getBytes(charset)”的直接封装。是最轻量级和高效的策略。
JacksonJsonRedisSerializer:jackson-json工具提供了javabean与json之间的转换能力,可以将pojo实例序列化成json格式存储在redis中,也可以将json格式的数据转换成pojo实例。因为jackson工具在序列化和反序列化时,需要明确指定Class类型,因此此策略封装起来稍微复杂。【需要jackson-mapper-asl工具支持】
OxmSerializer:提供了将javabean与xml之间的转换能力,目前可用的三方支持包括jaxb,apache-xmlbeans;redis存储的数据将是xml工具。不过使用此策略,编程将会有些难度,而且效率最低;不建议使用。【需要spring-oxm模块的支持】
三、Spring boot使用RestTemplate:
1、POM依赖:
1 <dependencies> 2 <!-- spring boot 配置 --> 3 <dependency> 4 <groupId>org.springframework.boot</groupId> 5 <artifactId>spring-boot-starter-web</artifactId> 6 </dependency> 7 8 <dependency> 9 <groupId>org.springframework.boot</groupId> 10 <artifactId>spring-boot-starter-thymeleaf</artifactId> 11 </dependency> 12 13 <dependency> 14 <groupId>org.springframework.boot</groupId> 15 <artifactId>spring-boot-starter-test</artifactId> 16 <scope>test</scope> 17 </dependency> 18 19 <dependency> 20 <groupId>org.springframework.boot</groupId> 21 <artifactId>spring-boot-starter-data-redis</artifactId> 22 </dependency> 23 </dependencies>
2、配置文件application.properties:
1 # Redis数据库索引(默认为0) 2 spring.redis.database=0 3 # Redis服务器地址 4 spring.redis.host=127.0.0.1 5 # Redis服务器连接端口 6 spring.redis.port=6379 7 # Redis服务器连接密码(默认为空) 8 spring.redis.password= 9 # 连接池最大连接数(使用负值表示没有限制) 10 spring.redis.pool.max-active=8 11 # 连接池最大阻塞等待时间(使用负值表示没有限制) 12 spring.redis.pool.max-wait=-1 13 # 连接池中的最大空闲连接 14 spring.redis.pool.max-idle=8 15 # 连接池中的最小空闲连接 16 spring.redis.pool.min-idle=0 17 # 连接超时时间(毫秒) 18 spring.redis.timeout=0
3、redis操作工具类:
1 @Component 2 public class RedisService { 3 @Autowired 4 private RedisTemplate<String, String> redisTemplate; 5 6 /** 7 * 默认过期时长,单位:秒 8 */ 9 public static final long DEFAULT_EXPIRE = 60 * 60 * 24; 10 11 /** 12 * 不设置过期时长 13 */ 14 public static final long NOT_EXPIRE = -1; 15 16 17 18 19 public boolean existsKey(String key) { 20 return redisTemplate.hasKey(key); 21 } 22 23 /** 24 * 重名名key,如果newKey已经存在,则newKey的原值被覆盖 25 * 26 * @param oldKey 27 * @param newKey 28 */ 29 public void renameKey(String oldKey, String newKey) { 30 redisTemplate.rename(oldKey, newKey); 31 } 32 33 /** 34 * newKey不存在时才重命名 35 * 36 * @param oldKey 37 * @param newKey 38 * @return 修改成功返回true 39 */ 40 public boolean renameKeyNotExist(String oldKey, String newKey) { 41 return redisTemplate.renameIfAbsent(oldKey, newKey); 42 } 43 44 /** 45 * 删除key 46 * 47 * @param key 48 */ 49 public void deleteKey(String key) { 50 redisTemplate.delete(key); 51 } 52 53 /** 54 * 删除多个key 55 * 56 * @param keys 57 */ 58 public void deleteKey(String... keys) { 59 Set<String> kSet = Stream.of(keys).map(k -> k).collect(Collectors.toSet()); 60 redisTemplate.delete(kSet); 61 } 62 63 /** 64 * 删除Key的集合 65 * 66 * @param keys 67 */ 68 public void deleteKey(Collection<String> keys) { 69 Set<String> kSet = keys.stream().map(k -> k).collect(Collectors.toSet()); 70 redisTemplate.delete(kSet); 71 } 72 73 /** 74 * 设置key的生命周期 75 * 76 * @param key 77 * @param time 78 * @param timeUnit 79 */ 80 public void expireKey(String key, long time, TimeUnit timeUnit) { 81 redisTemplate.expire(key, time, timeUnit); 82 } 83 84 /** 85 * 指定key在指定的日期过期 86 * 87 * @param key 88 * @param date 89 */ 90 public void expireKeyAt(String key, Date date) { 91 redisTemplate.expireAt(key, date); 92 } 93 94 /** 95 * 查询key的生命周期 96 * 97 * @param key 98 * @param timeUnit 99 * @return 100 */ 101 public long getKeyExpire(String key, TimeUnit timeUnit) { 102 return redisTemplate.getExpire(key, timeUnit); 103 } 104 105 /** 106 * 将key设置为永久有效 107 * 108 * @param key 109 */ 110 public void persistKey(String key) { 111 redisTemplate.persist(key); 112 } 113 }
4、缓存注解的使用:
(1) @Cacheable:在方法执行前Spring先查看缓存中是否有数据,如果有数据,则直接返回缓存数据;没有则调用方法并将方法返回值放进缓存。
(2) @CachePut:将方法的返回值放到缓存中。
(3) @CacheEvict:删除缓存中的数据。
注意:
1)保存到缓存的DTO实体对象要实现Serializable接口,否则会报序列化的错误;
2)@CachePut注解的cacheNames 和 key 要跟 @Cacheable() 里的一致,才会正确更新,并且和@Cacheable() 注解的方法返回值要一致;
5、两个redis命令:
(1) SETNX:将key设置值为value,如果key不存在,这种情况下等同SET命令。 当key存在时,什么也不做。SETNX是”SET if Not eXists”的简写。
(2) GETSET:GETSET可以和INCR一起使用实现支持重置的计数功能。举个例子:每当有事件发生的时候,一段程序都会调用INCR给key mycounter加1,但是有时我们需要获取计数器的值,并且自动将其重置为0。这可以通过GETSET mycounter “0”来实现;
如:
redis> INCR mycounter (integer) 1 redis> GETSET mycounter "0" "1" redis> GET mycounter "0"
6、redis实现的分布式锁:
1 @Component 2 @Slf4j 3 public class RedisLock { 4 5 @Autowired 6 StringRedisTemplate redisTemplate; 7 8 /** 9 * 加锁 10 * @param key 11 * @param value 当前时间 + 超时时间 12 * @return 13 */ 14 public boolean lock(String key, String value){ 15 if (redisTemplate.opsForValue().setIfAbsent(key, value)){ 16 return true; 17 } 18 19 //解决死锁,且当多个线程同时来时,只会让一个线程拿到锁 20 String currentValue = redisTemplate.opsForValue().get(key); 21 //如果过期 22 if (!StringUtils.isEmpty(currentValue) && 23 Long.parseLong(currentValue) < System.currentTimeMillis()){ 24 //获取上一个锁的时间 25 String oldValue = redisTemplate.opsForValue().getAndSet(key, value); 26 if (StringUtils.isEmpty(oldValue) && oldValue.equals(currentValue)){ 27 return true; 28 } 29 } 30 31 return false; 32 } 33 34 /** 35 * 解锁 36 * @param key 37 * @param value 38 */ 39 public void unlock(String key, String value){ 40 41 try { 42 String currentValue = redisTemplate.opsForValue().get(key); 43 if (!StringUtils.isEmpty(currentValue) && currentValue.equals(value)){ 44 redisTemplate.opsForValue().getOperations().delete(key); 45 } 46 }catch (Exception e){ 47 log.error("【redis锁】解锁失败, {}", e); 48 } 49 } 50 }
四、参考资料:
https://www.cnblogs.com/youcong/p/8098881.html