SpringBoot整合Redis & RedisTemplate 的使用
======================手动整合=====================
1.手动整合,也就是工具类自己添加缓存
手动整合只需要三步骤:pom.xml引入依赖、配置redis相关设置、 引入redis工具类:
(1)只需要引入下面这个工具类,会自动引入相关依赖的jar包:
<!-- 引入 redis 依赖 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency>
(2)applications.properties添加如下配置
如果查看默认值我们可以去查看自动配置的类:RedisAutoConfiguration.class。里面的RedisProperties是相关的默认配置以及生效的配置。
############################################################
#
# REDIS 配置
#
############################################################
# Redis数据库索引(默认为0)
spring.redis.database=1
# Redis服务器地址
spring.redis.host=localhost
# Redis服务器连接端口
spring.redis.port=6379
# Redis服务器连接密码(默认为空)
spring.redis.password=
# 连接池最大连接数(使用负值表示没有限制)
spring.redis.pool.max-active=1000
# 连接池最大阻塞等待时间(使用负值表示没有限制)
spring.redis.pool.max-wait=-1
# 连接池中的最大空闲连接
spring.redis.pool.max-idle=10
# 连接池中的最小空闲连接
spring.redis.pool.min-idle=2
# 连接超时时间(毫秒)
spring.redis.timeout=0
(3)引入redis工具类==这里使用spring-redis-data自带的StringRedisTemplate
package cn.qlq.controller; import java.util.Date; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import com.alibaba.fastjson.JSONObject; import cn.qlq.bean.user.User; import cn.qlq.utils.JSONResultUtil; @RestController @RequestMapping("redis") public class RedisController { @Autowired private StringRedisTemplate strRedis; @RequestMapping("/set") public JSONResultUtil test() { strRedis.opsForValue().set("mycache", "我存入的第一个cache"); return JSONResultUtil.ok(); } @RequestMapping("/setUser") public JSONResultUtil setUser() { User user = new User(); user.setAddress("地址"); user.setCreatetime(new Date()); strRedis.opsForValue().set("user", JSONObject.toJSONString(user)); return JSONResultUtil.ok(); } @RequestMapping("/getUser") public JSONResultUtil getUser() { String string = strRedis.opsForValue().get("user"); User user = JSONObject.parseObject(string, User.class); System.out.println(user); return JSONResultUtil.ok(); } }
这里使用StringRedisTemplate类,该类位于spring-data-redis-xxx.jar中,其有五种操作方式:
redisTemplate.opsForValue();//操作字符串 redisTemplate.opsForHash();//操作hash redisTemplate.opsForList();//操作list redisTemplate.opsForSet();//操作set redisTemplate.opsForZSet();//操作有序set
该类在操作的时候也可以指定key的失效时间,如下:
strRedis.opsForValue().set("mycache", "我存入的第一个cache", 660,TimeUnit.SECONDS);
关于该类的详细使用参考:https://www.jianshu.com/p/7bf5dc61ca06
(4)访问后通过redis客户端查看内容
补充:编写一个简单的工具类对上面的操作进行封装:
package cn.qlq.utils; import java.util.Map; import java.util.Set; import java.util.concurrent.TimeUnit; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.stereotype.Component; @Component public class RedisUtils { @Autowired private StringRedisTemplate redisTemplate; // Key(键),简单的key-value操作 /** * 实现命令:TTL key,以秒为单位,返回给定 key的剩余生存时间(TTL, time to live)。 * * @param key * @return */ public long ttl(String key) { return redisTemplate.getExpire(key); } /** * 实现命令:expire 设置过期时间,单位秒 * * @param key * @return */ public void expire(String key, long timeout) { redisTemplate.expire(key, timeout, TimeUnit.SECONDS); } /** * 实现命令:INCR key,增加key一次 * * @param key * @return */ public long incr(String key, long delta) { return redisTemplate.opsForValue().increment(key, delta); } /** * 实现命令:KEYS pattern,查找所有符合给定模式 pattern的 key */ public Set<String> keys(String pattern) { return redisTemplate.keys(pattern); } /** * 实现命令:DEL key,删除一个key * * @param key */ public void del(String key) { redisTemplate.delete(key); } // String(字符串) /** * 实现命令:SET key value,设置一个key-value(将字符串值 value关联到 key) * * @param key * @param value */ public void set(String key, String value) { redisTemplate.opsForValue().set(key, value); } /** * 实现命令:SET key value EX seconds,设置key-value和超时时间(秒) * * @param key * @param value * @param timeout * (以秒为单位) */ public void set(String key, String value, long timeout) { redisTemplate.opsForValue().set(key, value, timeout, TimeUnit.SECONDS); } /** * 实现命令:GET key,返回 key所关联的字符串值。 * * @param key * @return value */ public String get(String key) { return (String) redisTemplate.opsForValue().get(key); } // Hash(哈希表) /** * 实现命令:HSET key field value,将哈希表 key中的域 field的值设为 value * * @param key * @param field * @param value */ public void hset(String key, String field, Object value) { redisTemplate.opsForHash().put(key, field, value); } /** * 实现命令:HGET key field,返回哈希表 key中给定域 field的值 * * @param key * @param field * @return */ public String hget(String key, String field) { return (String) redisTemplate.opsForHash().get(key, field); } /** * 实现命令:HDEL key field [field ...],删除哈希表 key 中的一个或多个指定域,不存在的域将被忽略。 * * @param key * @param fields */ public void hdel(String key, Object... fields) { redisTemplate.opsForHash().delete(key, fields); } /** * 实现命令:HGETALL key,返回哈希表 key中,所有的域和值。 * * @param key * @return */ public Map<Object, Object> hgetall(String key) { return redisTemplate.opsForHash().entries(key); } // List(列表) /** * 实现命令:LPUSH key value,将一个值 value插入到列表 key的表头 * * @param key * @param value * @return 执行 LPUSH命令后,列表的长度。 */ public long lpush(String key, String value) { return redisTemplate.opsForList().leftPush(key, value); } /** * 实现命令:LPOP key,移除并返回列表 key的头元素。 * * @param key * @return 列表key的头元素。 */ public String lpop(String key) { return (String) redisTemplate.opsForList().leftPop(key); } /** * 实现命令:RPUSH key value,将一个值 value插入到列表 key的表尾(最右边)。 * * @param key * @param value * @return 执行 LPUSH命令后,列表的长度。 */ public long rpush(String key, String value) { return redisTemplate.opsForList().rightPush(key, value); } }
使用方法如下: 注入到需要的缓存对象中
package cn.qlq.controller; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import com.alibaba.fastjson.JSONObject; import cn.qlq.bean.user.User; import cn.qlq.utils.JSONResultUtil; import cn.qlq.utils.RedisUtils; @RestController @RequestMapping("redis") public class RedisController { @Autowired private RedisUtils redisUtils; @RequestMapping("/getUserByUtils") public JSONResultUtil getUserByUtils() { String string = (String) redisUtils.get("user"); User user = JSONObject.parseObject(string, User.class); System.out.println(user); return JSONResultUtil.ok(); } }
2.整合注解redis缓存
这个整合只不过是将xml配置的方式改为基于java配置的方式进行,之前的xml整合过程已经写的非常详细,参考:https://www.cnblogs.com/qlqwjy/p/8574121.html
定义cacheManager、redisTemplate、keyGenerator,并用注解声明开启缓存。
package cn.qlq.config; import java.lang.reflect.Method; import java.util.Arrays; import org.springframework.cache.CacheManager; import org.springframework.cache.annotation.EnableCaching; import org.springframework.cache.interceptor.KeyGenerator; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.cache.RedisCacheManager; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer; import org.springframework.data.redis.serializer.StringRedisSerializer; @Configuration // 声明开启缓存 @EnableCaching public class RedisCacheConfig { @Bean public CacheManager cacheManager(RedisTemplate redisTemplate) { RedisCacheManager rcm = new RedisCacheManager(redisTemplate); // 多个缓存的名称,目前只定义了一个(如果这里指定了缓存,后面的@Cacheable的value必须是这里的值) rcm.setCacheNames(Arrays.asList("usersCache", "logsCache")); // 设置缓存过期时间(秒) rcm.setDefaultExpiration(600); return rcm; } @Bean public RedisTemplate<String, String> redisTemplate(RedisConnectionFactory factory) { RedisTemplate template = new RedisTemplate(); template.setConnectionFactory(factory); StringRedisSerializer stringRedisSerializer = new StringRedisSerializer(); GenericJackson2JsonRedisSerializer genericJackson2JsonRedisSerializer = new GenericJackson2JsonRedisSerializer(); template.setKeySerializer(stringRedisSerializer); template.setValueSerializer(genericJackson2JsonRedisSerializer); template.setHashKeySerializer(stringRedisSerializer); template.setHashValueSerializer(genericJackson2JsonRedisSerializer); template.setEnableTransactionSupport(true); return template; } @Bean public KeyGenerator keyGenerator() { return new KeyGenerator() { @Override public Object generate(Object o, Method method, Object... params) { // 规定 本类名+方法名+参数名 为key StringBuilder sb = new StringBuilder(); sb.append(o.getClass().getName()); sb.append("-"); sb.append(method.getName()); sb.append("-"); for (Object param : params) { sb.append(param.toString()); } return sb.toString(); } }; } }
使用方法如下:在使用的地方注解加缓存即可,注意使用更换后的keyGenerator
/** * 分页查询user * * @param condition * @return */ @RequestMapping("getUsers") @Cacheable(value = "usersCache", keyGenerator = "keyGenerator") // 在redis中开启key为findAllUser开头的存储空间 @MyLogAnnotation(operateDescription = "分页查询用户") @ResponseBody public PageInfo<User> getUsers(@RequestParam Map condition) { int pageNum = 1; if (ValidateCheck.isNotNull(MapUtils.getString(condition, "pageNum"))) { // 如果不为空的话改变当前页号 pageNum = Integer.parseInt(MapUtils.getString(condition, "pageNum")); } int pageSize = DefaultValue.PAGE_SIZE; if (ValidateCheck.isNotNull(MapUtils.getString(condition, "pageSize"))) { // 如果不为空的话改变当前页大小 pageSize = Integer.parseInt(MapUtils.getString(condition, "pageSize")); } // 开始分页 PageHelper.startPage(pageNum, pageSize); List<User> users = new ArrayList<User>(); try { users = userService.getUsers(condition); } catch (Exception e) { logger.error("getUsers error!", e); } PageInfo<User> pageInfo = new PageInfo<User>(users); return pageInfo; }
注意@Cacheable的key和keyGenerator是互斥的,两个只能使用一个,查看@Cacheable的源码也可以知道。
结果:
补充:在定义了template之后整合了一个更强大的redis工具类
在RedisCacheConfig中声明bean:
@Bean public RedisTemplateUtils redisTemplateUtils(RedisTemplate redisTemplate) { RedisTemplateUtils redisTemplateUtils = new RedisTemplateUtils(); redisTemplateUtils.setRedisTemplate(redisTemplate); return redisTemplateUtils; }
工具类如下:
package cn.qlq.utils; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.TimeUnit; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.util.CollectionUtils; /** * * @author QLQ 基于spring和redis的redisTemplate工具类 针对所有的hash 都是以h开头的方法 针对所有的Set * 都是以s开头的方法 不含通用方法 针对所有的List 都是以l开头的方法 */ public class RedisTemplateUtils { private RedisTemplate<String, Object> redisTemplate; public RedisTemplate<String, Object> getRedisTemplate() { return redisTemplate; } public void setRedisTemplate(RedisTemplate<String, Object> redisTemplate) { this.redisTemplate = redisTemplate; } // =============================common============================ /** * 指定缓存失效时间 * * @param key * 键 * @param time * 时间(秒) * @return */ public boolean expire(String key, long time) { try { if (time > 0) { redisTemplate.expire(key, time, TimeUnit.SECONDS); } return true; } catch (Exception e) { e.printStackTrace(); return false; } } /** * 根据key 获取过期时间 * * @param key * 键 不能为null * @return 时间(秒) 返回0代表为永久有效 */ public long getExpire(String key) { return redisTemplate.getExpire(key, TimeUnit.SECONDS); } /** * 判断key是否存在 * * @param key * 键 * @return true 存在 false不存在 */ public boolean hasKey(String key) { try { return redisTemplate.hasKey(key); } catch (Exception e) { e.printStackTrace(); return false; } } /** * 删除缓存 * * @param key * 可以传一个值 或多个 */ @SuppressWarnings("unchecked") public void del(String... key) { if (key != null && key.length > 0) { if (key.length == 1) { redisTemplate.delete(key[0]); } else { redisTemplate.delete(CollectionUtils.arrayToList(key)); } } } // ============================String============================= /** * 普通缓存获取 * * @param key * 键 * @return 值 */ public Object get(String key) { return key == null ? null : redisTemplate.opsForValue().get(key); } /** * 普通缓存放入 * * @param key * 键 * @param value * 值 * @return true成功 false失败 */ public boolean set(String key, Object value) { try { redisTemplate.opsForValue().set(key, value); return true; } catch (Exception e) { e.printStackTrace(); return false; } } /** * 普通缓存放入并设置时间 * * @param key * 键 * @param value * 值 * @param time * 时间(秒) time要大于0 如果time小于等于0 将设置无限期 * @return true成功 false 失败 */ public boolean set(String key, Object value, long time) { try { if (time > 0) { redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS); } else { set(key, value); } return true; } catch (Exception e) { e.printStackTrace(); return false; } } /** * 递增 * * @param key * 键 * @param by * 要增加几(大于0) * @return */ public long incr(String key, long delta) { if (delta < 0) { throw new RuntimeException("递增因子必须大于0"); } return redisTemplate.opsForValue().increment(key, delta); } /** * 递减 * * @param key * 键 * @param by * 要减少几(小于0) * @return */ public long decr(String key, long delta) { if (delta < 0) { throw new RuntimeException("递减因子必须大于0"); } return redisTemplate.opsForValue().increment(key, -delta); } // ================================Map================================= /** * HashGet * * @param key * 键 不能为null * @param item * 项 不能为null * @return 值 */ public Object hget(String key, String item) { return redisTemplate.opsForHash().get(key, item); } /** * 获取hashKey对应的所有键值 * * @param key * 键 * @return 对应的多个键值 */ public Map<Object, Object> hmget(String key) { return redisTemplate.opsForHash().entries(key); } /** * HashSet * * @param key * 键 * @param map * 对应多个键值 * @return true 成功 false 失败 */ public boolean hmset(String key, Map<String, Object> map) { try { redisTemplate.opsForHash().putAll(key, map); return true; } catch (Exception e) { e.printStackTrace(); return false; } } /** * HashSet 并设置时间 * * @param key * 键 * @param map * 对应多个键值 * @param time * 时间(秒) * @return true成功 false失败 */ public boolean hmset(String key, Map<String, Object> map, long time) { try { redisTemplate.opsForHash().putAll(key, map); if (time > 0) { expire(key, time); } return true; } catch (Exception e) { e.printStackTrace(); return false; } } /** * 向一张hash表中放入数据,如果不存在将创建 * * @param key * 键 * @param item * 项 * @param value * 值 * @return true 成功 false失败 */ public boolean hset(String key, String item, Object value) { try { redisTemplate.opsForHash().put(key, item, value); return true; } catch (Exception e) { e.printStackTrace(); return false; } } /** * 向一张hash表中放入数据,如果不存在将创建 * * @param key * 键 * @param item * 项 * @param value * 值 * @param time * 时间(秒) 注意:如果已存在的hash表有时间,这里将会替换原有的时间 * @return true 成功 false失败 */ public boolean hset(String key, String item, Object value, long time) { try { redisTemplate.opsForHash().put(key, item, value); if (time > 0) { expire(key, time); } return true; } catch (Exception e) { e.printStackTrace(); return false; } } /** * 删除hash表中的值 * * @param key * 键 不能为null * @param item * 项 可以使多个 不能为null */ public void hdel(String key, Object... item) { redisTemplate.opsForHash().delete(key, item); } /** * 判断hash表中是否有该项的值 * * @param key * 键 不能为null * @param item * 项 不能为null * @return true 存在 false不存在 */ public boolean hHasKey(String key, String item) { return redisTemplate.opsForHash().hasKey(key, item); } /** * hash递增 如果不存在,就会创建一个 并把新增后的值返回 * * @param key * 键 * @param item * 项 * @param by * 要增加几(大于0) * @return */ public double hincr(String key, String item, double by) { return redisTemplate.opsForHash().increment(key, item, by); } /** * hash递减 * * @param key * 键 * @param item * 项 * @param by * 要减少记(小于0) * @return */ public double hdecr(String key, String item, double by) { return redisTemplate.opsForHash().increment(key, item, -by); } // ============================set============================= /** * 根据key获取Set中的所有值 * * @param key * 键 * @return */ public Set<Object> sGet(String key) { try { return redisTemplate.opsForSet().members(key); } catch (Exception e) { e.printStackTrace(); return null; } } /** * 根据value从一个set中查询,是否存在 * * @param key * 键 * @param value * 值 * @return true 存在 false不存在 */ public boolean sHasKey(String key, Object value) { try { return redisTemplate.opsForSet().isMember(key, value); } catch (Exception e) { e.printStackTrace(); return false; } } /** * 将数据放入set缓存 * * @param key * 键 * @param values * 值 可以是多个 * @return 成功个数 */ public long sSet(String key, Object... values) { try { return redisTemplate.opsForSet().add(key, values); } catch (Exception e) { e.printStackTrace(); return 0; } } /** * 将set数据放入缓存 * * @param key * 键 * @param time * 时间(秒) * @param values * 值 可以是多个 * @return 成功个数 */ public long sSetAndTime(String key, long time, Object... values) { try { Long count = redisTemplate.opsForSet().add(key, values); if (time > 0) expire(key, time); return count; } catch (Exception e) { e.printStackTrace(); return 0; } } /** * 获取set缓存的长度 * * @param key * 键 * @return */ public long sGetSetSize(String key) { try { return redisTemplate.opsForSet().size(key); } catch (Exception e) { e.printStackTrace(); return 0; } } /** * 移除值为value的 * * @param key * 键 * @param values * 值 可以是多个 * @return 移除的个数 */ public long setRemove(String key, Object... values) { try { Long count = redisTemplate.opsForSet().remove(key, values); return count; } catch (Exception e) { e.printStackTrace(); return 0; } } // ===============================list================================= /** * 获取list缓存的内容 * * @param key * 键 * @param start * 开始 * @param end * 结束 0 到 -1代表所有值 * @return */ public List<Object> lGet(String key, long start, long end) { try { return redisTemplate.opsForList().range(key, start, end); } catch (Exception e) { e.printStackTrace(); return null; } } /** * 获取list缓存的长度 * * @param key * 键 * @return */ public long lGetListSize(String key) { try { return redisTemplate.opsForList().size(key); } catch (Exception e) { e.printStackTrace(); return 0; } } /** * 通过索引 获取list中的值 * * @param key * 键 * @param index * 索引 index>=0时, 0 表头,1 第二个元素,依次类推;index<0时,-1,表尾,-2倒数第二个元素,依次类推 * @return */ public Object lGetIndex(String key, long index) { try { return redisTemplate.opsForList().index(key, index); } catch (Exception e) { e.printStackTrace(); return null; } } /** * 将list放入缓存 * * @param key * 键 * @param value * 值 * @param time * 时间(秒) * @return */ public boolean lSet(String key, Object value) { try { redisTemplate.opsForList().rightPush(key, value); return true; } catch (Exception e) { e.printStackTrace(); return false; } } /** * 将list放入缓存 * * @param key * 键 * @param value * 值 * @param time * 时间(秒) * @return */ public boolean lSet(String key, Object value, long time) { try { redisTemplate.opsForList().rightPush(key, value); if (time > 0) expire(key, time); return true; } catch (Exception e) { e.printStackTrace(); return false; } } /** * 将list放入缓存 * * @param key * 键 * @param value * 值 * @param time * 时间(秒) * @return */ public boolean lSet(String key, List<Object> value) { try { redisTemplate.opsForList().rightPushAll(key, value); return true; } catch (Exception e) { e.printStackTrace(); return false; } } /** * 将list放入缓存 * * @param key * 键 * @param value * 值 * @param time * 时间(秒) * @return */ public boolean lSet(String key, List<Object> value, long time) { try { redisTemplate.opsForList().rightPushAll(key, value); if (time > 0) expire(key, time); return true; } catch (Exception e) { e.printStackTrace(); return false; } } /** * 根据索引修改list中的某条数据 * * @param key * 键 * @param index * 索引 * @param value * 值 * @return */ public boolean lUpdateIndex(String key, long index, Object value) { try { redisTemplate.opsForList().set(key, index, value); return true; } catch (Exception e) { e.printStackTrace(); return false; } } /** * 移除N个值为value * * @param key * 键 * @param count * 移除多少个 * @param value * 值 * @return 移除的个数 */ public long lRemove(String key, long count, Object value) { try { Long remove = redisTemplate.opsForList().remove(key, count, value); return remove; } catch (Exception e) { e.printStackTrace(); return 0; } } }
使用方法:
@Autowired private RedisTemplateUtils redisTemplateUtils; @RequestMapping("/setUserByUtils2") public JSONResultUtil setUserByUtils2() { redisTemplateUtils.set("mm", "mm"); return JSONResultUtil.ok(); } @RequestMapping("/getUserByUtils2") public JSONResultUtil getUserByUtils2() { String string = (String) redisTemplateUtils.get("mm"); System.out.println(string); return JSONResultUtil.ok(); }
===============使用springboot的自动整合(不推荐)===============
1.Redis单机版:
(1)第一种:springBootStrap自动配置:
- 目录结构
- 引入spring-boot-starter-redis.jar
<!-- 自动配置Redis --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-redis</artifactId> <version>1.4.4.RELEASE</version> </dependency>
- SpringBoot运行类打注解开启redis缓存
package cn.qlq; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cache.annotation.EnableCaching; @SpringBootApplication @EnableCaching//开启redis缓存 public class MySpringBootApplication { public static void main(String[] args) { //入口运行类 SpringApplication.run(MySpringBootApplication.class, args); } }
- application.properties添加redis配置
server.port=80 logging.level.org.springframework=DEBUG #springboot mybatis #jiazai mybatis peizhiwenjian #mybatis.mapper-locations = classpath:mapper/*Mapper.xml #mybatis.config-location = classpath:mapper/config/sqlMapConfig.xml #mybatis.type-aliases-package = cn.qlq.bean #shujuyuan spring.datasource.driver-class-name= com.mysql.jdbc.Driver spring.datasource.url = jdbc:mysql://localhost:3306/test1?useUnicode=true&characterEncoding=utf-8 spring.datasource.username = root spring.datasource.password = 123456 #redis spring.redis.host=localhost spring.redis.port=6379
- Service中打缓存注解:
package cn.qlq.service.impl; import java.sql.SQLException; import java.util.List; import java.util.Map; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cache.annotation.CacheEvict; import org.springframework.cache.annotation.Cacheable; import org.springframework.stereotype.Service; import cn.qlq.bean.User; import cn.qlq.mapper.UserMapper; import cn.qlq.service.UserService; @Service public class UserServiceImpl implements UserService { @Autowired private UserMapper userMapper; @Cacheable(value="findAllUser",key="1")//在redis中开启key为findAllUser开头的存储空间 public List<User> findAllUser(Map condition) { System.out.println("打印语句则没有走缓存"); List<User> list = userMapper.findAll(); return list; } @Override @CacheEvict(value="findAllUser",allEntries=true)//执行此方法的时候删除上面的缓存(以findAllUser为名称的) public int addUser() throws SQLException { // TODO Auto-generated method stub return userMapper.addUser(); } }
- 测试:
(1)访问:http://localhost/list?name=1(第一次不走缓存)
再次访问:
没有打印语句,也就是没有走方法。
(2)http://localhost/list?name=2(第一次不走缓存)
再次访问:
没有打印语句,也就是没有走方法。
(3)访问:http://localhost/list?name=1
没有打印语句,也就是没有走方法。还是走的缓存。
查看Redis缓存的key:
RedisDesktopManager查看:
- 测试清除缓存:
查看Redis缓存的key发现为空:
总结:
注解上的findAllUser是key的前缀,相当于findAllUser:xxxx,后面的是spring自动根据函数的参数生成,如果redis存在则不走方法,直接取出缓存,如果是参数不同,则走方法且加入缓存。
清除缓存的时候只会清除缓存key前缀是findAllUser开头的,如果是自己手动添加的以findAllUser:开头的也会被清除。如下面的在执行add方法的时候也会被清除:
127.0.0.1:6379> set findAllUser:mykey test
OK
补充:StringRedisTemplate 相关API的使用
StringRedisTemplate 实际上相当于RedisTemplate<String, String>
@Autowired private StringRedisTemplate stringRedisTemplate; @PostMapping("testRedisTemplate") public void testRedisTemplate() { System.out.println(stringRedisTemplate.getDefaultSerializer().getClass()); // 针对数据的"序列化与反序列化",提供了多种可以选择的策略(RedisSerializer) /** * JdkSerializationRedisSerializer:当需要存储java对象时使用. * StringRedisSerializer:当需要存储string类型的字符串时使用. * JacksonJsonRedisSerializer:将对象序列化成json的格式存储在redis中,需要jackson-json工具的支持 */ // ValueOperation和BoundValueOperation // redisTemplate有两个方法经常用到,一个是opsForXXX一个是boundXXXOps,XXX是value的类型,前者获取到一个Opercation,但是没有指定操作的key,可以在一个连接(事务)内操作多个key以及对应的value;后者会获取到一个指定了key的operation,在一个连接内只操作这个key对应的value. // 1. 对字符串操作 // 1.1 opsForValue 操作多个key System.out.println("======0======"); ValueOperations<String, String> stringStringValueOperations = stringRedisTemplate.opsForValue(); stringStringValueOperations.set("str1", "str11"); // 默认永久 `set str1 str11` stringStringValueOperations.set("str2", "str22", Duration.ofSeconds(5000)); // `set str2 str22 ex 5000` Boolean aBoolean = stringStringValueOperations.setIfAbsent("str4", "str44"); // `setnx str4 str44` Boolean bBoolean = stringStringValueOperations.setIfAbsent("str4", "str4444"); // `setnx str4 str444` Boolean aBoolean1 = stringStringValueOperations.setIfAbsent("str5", "str55", Duration.ofSeconds(5000)); // `setnx str4 str444` + `expire str4 5000` System.out.println(aBoolean); // true System.out.println(bBoolean); // false System.out.println(aBoolean1); // true stringStringValueOperations.set("str6", "1"); Long str6 = stringStringValueOperations.increment("str6"); // `incr str6` 将字符串值解析成整型,将其加一,最后将结果保存为新的字符串值 System.out.println(str6); // 2 Long str61 = stringStringValueOperations.decrement("str6"); // `decr str6` 将字符串值解析成整型,将其减一,最后将结果保存为新的字符串值 System.out.println(str61); // 1 // 1.1 boundValueOps 操作单个key System.out.println("======1======"); BoundValueOperations<String, String> str11 = stringRedisTemplate.boundValueOps("bound1"); Boolean bound11 = str11.setIfAbsent("bound11"); System.out.println(bound11); System.out.println(str11.getExpire()); System.out.println("======2======"); // 2. 操作List - Redis列表是简单的字符串列表,按照插入顺序排序。你可以添加一个元素到列表的头部(左边)或者尾部(右边), lpush:从左边插入,rpush:从右边插入,lrange,遍历一定范围的值,从左边开始为0,负数代表从尾部象前数 ListOperations<String, String> stringStringListOperations = stringRedisTemplate.opsForList(); String listKey = "list1"; stringStringListOperations.leftPush(listKey, "1"); // `lpush list1 1` 从左边插入元素 stringStringListOperations.leftPush(listKey, "2"); // `lpush list1 2` stringStringListOperations.rightPush(listKey, "3"); // `rpush list1 3` 从右边插入元素 List<String> range = stringStringListOperations.range(listKey, 0, -1); System.out.println(range); // 2 1 3 (可以自己简单理解一下队列插入的顺序) Long size = stringStringListOperations.size(listKey); // `llen list1` 求长度 System.out.println(size); // 3 String index = stringStringListOperations.index(listKey, 1); // `lindex list1 1` 查找list1 list 中下标为1 的元素 System.out.println(index); String s = stringStringListOperations.leftPop(listKey); // `lpop list1` 从左边弹出一个元素 System.out.println(s); // 2 String s2 = stringStringListOperations.rightPop(listKey); // `rpop list1` 从右边弹出一个元素 System.out.println(s2); // 3 Long aLong = stringStringListOperations.leftPushAll(listKey, "1", "2", "3", "4"); // `lpush list1 1 2 3 4` System.out.println(aLong); // 返回的是list1 的集合大小 System.out.println("======3======"); // 3. 操作hash - string类型的field和value的映射表,hash特别适合用于存储对象。 HashOperations<String, Object, Object> stringObjectObjectHashOperations = stringRedisTemplate.opsForHash(); String hashKey = "myhash"; HashMap<String, Object> stringObjectHashMap = new HashMap<>(); stringObjectHashMap.put("key1", "1"); stringObjectHashMap.put("key2", "2"); stringObjectObjectHashOperations.putAll(hashKey, stringObjectHashMap); // `hmset myhash key1 1 key2 2` 一次性存入多个值 stringObjectObjectHashOperations.put(hashKey, "key3", "3"); // `hset myhash key3 3` Long user1 = stringObjectObjectHashOperations.delete(hashKey, "key3"); // `hdel myhash key3` 返回的是删除的元素个数 Boolean key2 = stringObjectObjectHashOperations.hasKey(hashKey, "key2"); // `hexists myhash key1` Object o = stringObjectObjectHashOperations.get(hashKey, "testRedisTemplate"); // `hget myhash key2` List<Object> objects = stringObjectObjectHashOperations.multiGet(hashKey, Lists.newArrayList("key1", "key2")); // `hmget myhash key1 key2` 返回 "1" "2" Set<Object> keys1 = stringObjectObjectHashOperations.keys(hashKey); // `hkeys myhash` List<Object> values = stringObjectObjectHashOperations.values(hashKey); // `hvals myhash` Map<Object, Object> entries = stringObjectObjectHashOperations.entries(hashKey); // `hgetall myhash` 返回key1 val1 key2 value2 组成的map 结构 Long size1 = stringObjectObjectHashOperations.size(hashKey); // `hlen myhash` System.out.println("======4======"); // 4. 操作set - Redis 的 Set 是 String 类型的无序集合。集合成员是唯一的,这就意味着集合中不能出现重复的数据。Redis 中集合是通过哈希表实现的,所以添加,删除,查找的复杂度都是 O(1)。 SetOperations<String, String> stringStringSetOperations = stringRedisTemplate.opsForSet(); String setKey1 = "setkey1"; String setKey2 = "setkey2"; Long add = stringStringSetOperations.add(setKey1, "1", "2", "3", "4", "5"); // `sadd setkey1 1 2 3 4 5` 返回插入的元素的数量 Long add1 = stringStringSetOperations.add(setKey1, "7"); Long add2 = stringStringSetOperations.add(setKey2, "2", "3", "4", "5", "6"); // `sadd setkey1 2 3 4 5 6` 返回插入的元素的数量 Long add3 = stringStringSetOperations.add(setKey2, "7"); Set<String> members = stringStringSetOperations.members(setKey1); // `smembers setkey1` String s1 = stringStringSetOperations.randomMember(setKey1); // `srandmember setkey1` 从set中随机取1个 List<String> strings = stringStringSetOperations.randomMembers(setKey1, 3); // `srandmember setkey1 3` 从set中随机取3个 Boolean member = stringStringSetOperations.isMember(setKey1, "7"); // `sismember setkey1 1` 判断是否是set的元素 Long size2 = stringStringSetOperations.size(setKey1); // `scard setkey1` 求元素个数 Long remove = stringStringSetOperations.remove(setKey1, "1", "2"); // `srem setkey1 1 2` 返回值是删除的元素的个数 String pop0 = stringStringSetOperations.pop(setKey1); // `spop setkey1` 随机出栈1个元素 List<String> pop = stringStringSetOperations.pop(setKey1, 2); // `spop setkey1 2` 随机出栈两个元素 // 求差集、交集、并集 Set<String> intersect = stringStringSetOperations.intersect(setKey1, setKey2); // `sinter setkey1 setkey2` 求两个set 的交集 Set<String> union = stringStringSetOperations.union(setKey1, setKey2); // `sunion setkey1 setkey2` 求两个set 的并集 Set<String> difference = stringStringSetOperations.difference(setKey1, setKey2); // `sdiff setkey1 setkey2` 差集 setKey1有后面的set 中没有的元素(如果后面的set不存在会返回setkey1全部元素) System.out.println("======5======"); // 5. 操作zset有序集合 - Redis 有序集合和集合一样也是string类型元素的集合,且不允许重复的成员。不同的是每个元素都会关联一个double类型的分数。redis正是通过分数来为集合中的成员进行从小到大的排序。有序集合的成员是唯一的,但分数(score)却可以重复。 // zset 主要用于排行旁或者其他需要排名的场景 ZSetOperations<String, String> stringStringZSetOperations = stringRedisTemplate.opsForZSet(); String zsetKey = "zsetkey"; Set<ZSetOperations.TypedTuple<String>> elements = new HashSet<>(); elements.add(new DefaultTypedTuple<String>("val2", 2.1D)); elements.add(new DefaultTypedTuple<String>("val3", 3.1D)); stringStringZSetOperations.add(zsetKey, elements); // `zadd zsetkey 2.1 val2 3.1 val2` 添加元素 stringStringZSetOperations.add(zsetKey, "val4", 4.0D); // `zadd zsetkey 4.0 val4` stringStringZSetOperations.add(zsetKey, "val1", 1.0D); // `zadd zsetkey 1.0 val1` Long size3 = stringStringZSetOperations.size(zsetKey); // `zcard zsetkey` 统计数量 Long val4 = stringStringZSetOperations.remove(zsetKey, "val4"); // `zrem zsetkey val4` 删除zset 元素 Double val1 = stringStringZSetOperations.incrementScore(zsetKey, "val1", 2.1D); // `zincrby zsetkey 2.1 val1` 增加分数 Long val = stringStringZSetOperations.rank(zsetKey, "val1"); // `zrank zsetkey val1` 返回元素的排名 Double val11 = stringStringZSetOperations.score(zsetKey, "val1"); // `zscore zsetkey val1` 查看元素的分数 Long count = stringStringZSetOperations.count(zsetKey, 0D, 5D); // `zcount zsetkey 0 5` 查询分数在0-5之间的个数 Set<String> range2 = stringStringZSetOperations.range(zsetKey, 0, -1); // `zrange zsetkey 0 -1` 获取元素 Set<ZSetOperations.TypedTuple<String>> typedTuples = stringStringZSetOperations.rangeWithScores(zsetKey, 0, -1);// `zrange zsetkey 0 -1 withscores` 根据分数正序排序,并且返回分数以及元素 (小到大) Set<ZSetOperations.TypedTuple<String>> typedTuples2 = stringStringZSetOperations.reverseRangeByScoreWithScores(zsetKey, 0, -1);// `zrevrange zsetkey 0 -1 withscores` 根据分数倒序排序(大到小) Set<String> strings1 = stringStringZSetOperations.rangeByScore(zsetKey, 1D, 6D); // `zrangebyscore zsetkey 1 6` 获取分数在 1 - 6 之间的 Set<ZSetOperations.TypedTuple<String>> typedTuples1 = stringStringZSetOperations.rangeByScoreWithScores(zsetKey, 1D, 6D); // `zrangebyscore zsetkey 1 6 withscores` 获取分数在 1 - 6 之间的并且返回分数 Set<ZSetOperations.TypedTuple<String>> typedTuples3 = stringStringZSetOperations.reverseRangeByScoreWithScores(zsetKey, 1D, 6D, 0, 1); // `zrangebyscore zsetkey 1 6 withscores limit 0 1` 获取分数在 1 - 6 之间的并且返回分数, 并且可以限制返回的数量 System.out.println("======6======"); // 6. 通用的key 操作 Set<String> keys = stringRedisTemplate.keys("*"); // `keys *` System.out.println(keys); Long str1 = stringRedisTemplate.getExpire("str1"); // `ttl str1` System.out.println(str1); Boolean str14 = stringRedisTemplate.hasKey("str1"); // `exists str1` System.out.println(str14); stringRedisTemplate.expire("str1", Duration.ofSeconds(10 * 1000)); // `expire str1 10000 ` Long str2 = stringRedisTemplate.getExpire("str1"); // `ttl str1` System.out.println(str2); DataType str13 = stringRedisTemplate.type("str1"); // `type str1` 查看数据类型。 从DataType 类型也可以看出Redis 的数据类型有: string、 list、 set、 zset、 hash、 stream(用于MQ) System.out.println(str13); Boolean str12 = stringRedisTemplate.delete("str1"); // `del str1` System.out.println(str12); }
1. 用RedisTemplate 测试序列化器不同存放的数据不同的影响:
查看put 方法,我们发给redis 的key 和 value 都是byte[] 数组对象, 接收到的也是byte[] 对象,所以就需要根据存进去采用的转byte[] 方式反序列化为对象。 无论是基于jedis还是基于lettuce 数据的传输都是btye[] 对象。org.springframework.data.redis.core.DefaultHashOperations#put
@Override public void put(K key, HK hashKey, HV value) { byte[] rawKey = rawKey(key); byte[] rawHashKey = rawHashKey(hashKey); byte[] rawHashValue = rawHashValue(value); execute(connection -> { connection.hSet(rawKey, rawHashKey, rawHashValue); return null; }, true); }
查看get 方法源码如下:org.springframework.data.redis.core.DefaultHashOperations#get 拿到byte[] 数组之后进行转换成对应的对象
public HV get(K key, Object hashKey) { byte[] rawKey = rawKey(key); byte[] rawHashKey = rawHashKey(hashKey); byte[] rawHashValue = execute(connection -> connection.hGet(rawKey, rawHashKey), true); return (HV) rawHashValue != null ? deserializeHashValue(rawHashValue) : null; }
所以核心是在org.springframework.data.redis.serializer.RedisSerializer#serialize 方法和 org.springframework.data.redis.serializer.RedisSerializer#deserialize 方法
2. 常见的几种序列化器
org.springframework.data.redis.serializer.JdkSerializationRedisSerializer JDK 序列化,这个key 和 value 都需要实现Serializable 接口。 org.springframework.data.redis.serializer.StringRedisSerializer 这个比较简单,就是简单的调用string.getBytes 方法编码采用UTF-8生成byte[], 然后new String 反向生成字符串对象。 这个一般用于key 的序列化器,不用于value 的序列化器,除非value 是String 类型,否则会报类型转换错误。 org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer 这个实际就是调用jackson工具包, 实际调用的是com.fasterxml.jackson.databind.ObjectMapper 类进行处理 com.alibaba.fastjson.support.spring.GenericFastJsonRedisSerializer fastjson 提供的处理器,类似于jackson。
默认的序列化器是 class org.springframework.data.redis.serializer.JdkSerializationRedisSerializer
3. 测试不同序列化器的数据:
(1). 我们用StringRedisSerializer 做key 的序列化器,用JdkSerializationRedisSerializer 做value 的序列化器:
@Bean public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) { RedisTemplate template = new RedisTemplate(); template.setConnectionFactory(connectionFactory); StringRedisSerializer stringRedisSerializer = new StringRedisSerializer(); // GenericJackson2JsonRedisSerializer genericJackson2JsonRedisSerializer = new GenericJackson2JsonRedisSerializer(); JdkSerializationRedisSerializer jdkSerializationRedisSerializer = new JdkSerializationRedisSerializer(); template.setKeySerializer(stringRedisSerializer); template.setValueSerializer(jdkSerializationRedisSerializer); template.setHashKeySerializer(stringRedisSerializer); template.setHashValueSerializer(jdkSerializationRedisSerializer); template.setEnableTransactionSupport(true); return template; }
测试方法如下:
@Autowired private RedisTemplate redisTemplate; @PostMapping("testRedisTemplate2") public void testRedisTemplate2() { System.out.println(redisTemplate.getHashKeySerializer().getClass()); System.out.println(redisTemplate.getHashValueSerializer().getClass()); User user = new User(); user.setUsername("zhangsan"); user.setFullname("张三"); redisTemplate.opsForValue().set("user1", user); User user1 = (User) redisTemplate.opsForValue().get("user1"); System.out.println(user1); }
结果:
class org.springframework.data.redis.serializer.StringRedisSerializer class org.springframework.data.redis.serializer.JdkSerializationRedisSerializer User(username=zhangsan, fullname=张三, createDate=null)
到redis 查看结果如下:
127.0.0.1:63791> get user1
"\xac\xed\x00\x05sr\x00\x14com.xm.ggn.test.User\xd0\xa2\xe7\xdav\x18\x8c\xb3\x02\x00\x03L\x00\ncreateDatet\x00\x12Ljava/lang/String;L\x00\bfullnameq\x00~\x00\x01L\x00\busernameq\x00~\x00\x01xppt\x00\x06\xe5\xbc\xa0\xe4\xb8\x89t\x00\bzhangsan"
(2) 我们用StringRedisSerializer 做key 的序列化器,用GenericJackson2JsonRedisSerializer 做value 的序列化器:
@Bean public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) { RedisTemplate template = new RedisTemplate(); template.setConnectionFactory(connectionFactory); StringRedisSerializer stringRedisSerializer = new StringRedisSerializer(); GenericJackson2JsonRedisSerializer genericJackson2JsonRedisSerializer = new GenericJackson2JsonRedisSerializer(); // JdkSerializationRedisSerializer jdkSerializationRedisSerializer = new JdkSerializationRedisSerializer(); template.setKeySerializer(stringRedisSerializer); template.setValueSerializer(genericJackson2JsonRedisSerializer); template.setHashKeySerializer(stringRedisSerializer); template.setHashValueSerializer(genericJackson2JsonRedisSerializer); template.setEnableTransactionSupport(true); return template; }
测试方法同上,结果如下:
class org.springframework.data.redis.serializer.StringRedisSerializer class org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer User(username=zhangsan, fullname=张三, createDate=null)
到redis 查看结果如下:
127.0.0.1:63791> get user1
"{\"@class\":\"com.xm.ggn.test.User\",\"username\":\"zhangsan\",\"fullname\":\"\xe5\xbc\xa0\xe4\xb8\x89\",\"createDate\":null}"
补充: java操作redis 的两个驱动包
jedis 和 letttuce。 两个都是Redis Client。 无论是jedis 还是 lettuce, 我们向redis 发送的命令以及数据都是byte[], 所以就需要对key、value 进行序列化以及反序列化,于是便有了上面的序列化器(不同的对象转byte[] 以及 byte[] 转换对象的方式)
Springboot 1.X 中使用的是jedis 客户端,Springboot 2.x 使用的是lettuce 客户端。
Jedis: Jedis在实现上是直接连接的redis server,如果在多线程环境下是非线程安全的,这个时候只有使用连接池,为每个Jedis实例增加物理连接
Lettuce:Lettuce的连接是基于Netty的,连接实例(StatefulRedisConnection)可以在多个线程间并发访问,应为StatefulRedisConnection是线程安全的,所以一个连接实例(StatefulRedisConnection)就可以满足多线程环境下的并发访问,当然这个也是可伸缩的设计,一个连接实例不够的情况也可以按需增加连接实例。lettuce主要利用netty实现与redis的同步和异步通信。
(1)Jedis 是通过Socket 连接到redis-server,然后按照redis 协议发送请求数据和解析响应数据, 测试代码如下:
package com.xm.ggn.test.redis; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.InetSocketAddress; import java.net.Socket; /** * Socket 连接Redis 进行处理 * 所有操作都是通过socket 连接到redis,然后通过socker.getOutputStream().write(byte[]) 发送命令以及参数;通过socket.getInputStream() 读取返回的字节流信息 * 参考: redis.clients.jedis.Protocol 这个里面的sendCommand() 方法处理写入命令和process() 方法读取返回信息 */ public class RedisClient { private static final String HOST = "127.0.0.1"; private static final int PORT = 6379; private static final int CONNECTION_TIMEOUT = 60000; public static void main(String[] args) { Socket socket = null; try { socket = new Socket(); // ->@wjw_add socket.setReuseAddress(true); socket.setKeepAlive(true); // Will monitor the TCP connection is // valid socket.setTcpNoDelay(true); // Socket buffer Whetherclosed, to // ensure timely delivery of data socket.setSoLinger(true, 0); // Control calls close () method, socket.connect(new InetSocketAddress(HOST, PORT), CONNECTION_TIMEOUT); System.out.println("连接成功~~~"); // 如果有密码发送 auth 命令 OutputStream outputStream = socket.getOutputStream(); InputStream inputStream = socket.getInputStream(); // 发送命令参数的格式 // <*><参数数量>\r\n[<$><参数1的字节数量>\r\n<参数1>\r\n][<$><参数2的字节数量>\r\n<参数2>\r\n]... String cmd1 = "*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$3\r\nbar\r\n"; // `set foo bar` String cmd2 = "*2\r\n$3\r\nGET\r\n$3\r\nfoo\r\n"; // `get foo` String cmd3 = "*2\r\n$4\r\nKEYS\r\n$1\r\n*\r\n"; // `keys *` String cmd4 = "*1\r\n$8\r\nflushall\r\n"; // `flushall` String cmd5 = "*1\r\n$9\r\nflushallE\r\n"; // `flushallE` 故意发送一个错误的命令查看其结果 String cmd6 = "*2\r\n$4\r\ntype\r\n$3\r\nfoo\r\n"; // `type foo` String cmd7 = "*2\r\n$4\r\nllen\r\n$6\r\nmylist\r\n"; // `llen mylist` outputStream.write(cmd1.getBytes("UTF-8")); outputStream.flush(); System.out.println("发送命令成功~~~"); byte buffer[] = new byte[1024]; int length = -1; while ((length = inputStream.read(buffer)) != -1) { System.out.println(length); String s = new String(buffer, 0, length, "UTF-8"); System.out.println(s); } // 响应报文格式如下: /** * 状态回复(status reply)的第一个字节是 "+"。 例如: `flushall` 返回的 `+OK\r\n`; `type foo` 返回的 `+string\r\n` * 错误回复(error reply)的第一个字节是 "-"。例如 `flushallE` 返回的 `-ERR unknown command 'flushallE'\r\n` * 整数回复(integer reply)的第一个字节是 ":"。 例如 `llen mylist` 查看list 大小返回的 `:3\r\n` * 批量回复(bulk reply)的第一个字节是 "$", 例如: `get foo` 返回的结果为 `$3\r\nbar\r\n` * 多条批量回复(multi bulk reply)的第一个字节是 "*", 例如: *2\r\n$3\r\nfoo\r\n$4test\r\n */ System.out.println("接收数据完成"); // 具体的发送参数的处理方式参考: redis.clients.jedis.Protocol#sendCommand(redis.clients.util.RedisOutputStream, byte[], byte[]...) // 具体的响应参数处理方式可以参考: redis.clients.jedis.Protocol#process } catch (Exception e) { e.printStackTrace(); } finally { if (socket != null && !socket.isClosed()) { try { socket.close(); } catch (IOException e) { e.printStackTrace(); } } } } }
可以看到发给redis-server 和从redis-server 拿到的都是byte[]。 所以就需要一种序列化技术保证放进去和取出来的数据是一致的。