一. SpringCache
一.一 SpringCache 的出现
在SpringBoot 整合 Redis 时,无论是使用 Lettuce 还是使用 Jedis 连接池, 在查询单个对象,查询全部对象的时候,都是我们自己手动进行判断缓存的信息。
SpringBoot 使用 Lettuce 连接池时:
@Override public User findById(int id) { log.info("先从缓存中查询用户编号为{} 是否存在",id); User user=redisUtil.get(KEY_PRE+id); if(user!=null){ log.info(">>>>>>>>>>使用的是缓存中的数据"); return user; } log.info(">>>>>>>>>>>从数据库中查询,并放置到缓存中"); user= userMapper.findById(id); redisUtil.set(KEY_PRE+id,user); return user; } @Override public List<User> findAll() { log.info("先从缓存中查询用户列表是否存在"); List<User> userList= (List<User>) redisUtil.range(KEY_PRE+"ALL"); if(!CollectionUtils.isEmpty(userList)){ log.info(">>>>>>>>>>使用的是缓存中的数据"); return userList; } log.info(">>>>>>>>>>>从数据库中查询,并放置到缓存中"); userList= userMapper.findAll(); redisUtil.leftPushAll(KEY_PRE+"ALL",userList); return userList; }
SpringBoot 使用 Jedis 连接池时:
@Override public User findById(int id) { log.info("先从缓存中查询用户编号为{} 是否存在",id); User user=BeanConvertUtil.stringToBean(redisUtil.get(KEY_PRE+id,redisDB),User.class); if(user!=null){ log.info(">>>>>>>>>>使用的是缓存中的数据"); return user; } log.info(">>>>>>>>>>>从数据库中查询,并放置到缓存中"); user= userMapper.findById(id); redisUtil.set(KEY_PRE+id,BeanConvertUtil.beanToString(user),redisDB); return user; } @Override public List<User> findAll() { log.info("先从缓存中查询用户列表是否存在"); List<String> userStringList= (List<String>) redisUtil.lrange(KEY_PRE+"ALL",0,-1,redisDB); List<User> userList=new ArrayList<>(); if(!CollectionUtils.isEmpty(userStringList)){ log.info(">>>>>>>>>>使用的是缓存中的数据"); for(String userString:userStringList){ userList.add(BeanConvertUtil.stringToBean(userString,User.class)); } return userList; } log.info(">>>>>>>>>>>从数据库中查询,并放置到缓存中"); userList= userMapper.findAll(); for(User user:userList){ redisUtil.lpush(redisDB,KEY_PRE+"ALL",BeanConvertUtil.beanToString(user)); } return userList; }
可以发现, 两个都需要开发者自己手动处理缓存的信息。
并且,如果缓存的工具不同,处理的方式也不同。
实际上,这些与业务是没有太大的联系的。
我们希望能够有一种方式,能够通过简单的配置+注解,达到以前原生的写法就完美了。
@Override public User findById(int id) { return userMapper.findById(id); } @Override public List<User> findAll() { return userMapper.findAll(); }
在这两个方法上,添加某个注解, 能够达到 有缓存走缓存,没有缓存走数据库查询,
然后将查询结果放置在缓存中,下一次查询时走缓存的结果,
并且与缓存的实现方式无关 (无论是 Lettuce 还是 Jedis)
有这么一种技术, 叫做SpringCache
一.二 SpringCache 的简单使用
按照 SpringBoot_Redis 项目,创建 SpringBoot_Cache 项目。
Redis服务器打开,使用的仍然是 database 15 数据库。
采用的是 springboot 2.2.13 版本。
目前数据库表 user 里面有三条记录
一.二.一 pom.xml 添加依赖
<!--依赖 data-redis的依赖--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <!--不能忘记这个依赖--> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-pool2</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-cache</artifactId> </dependency> <!--添加cache的依赖信息-->
一.二.二 application.yml 进行配置
与redis整合时一样,没有改变。
# 引入 数据库的相关配置 spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/springboot?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=UTF-8&useSSL=false&allowMultiQueries=true username: root password: abc123 # 配置Redis的使用 redis: database: 15 # 所使用的数据库 默认是0 host: 127.0.0.1 #所使用的redis的主机地址 port: 6379 # 端口号 默认是 6379 password: zk123 # 密码 timeout: 5000 # 超时时间 5000毫秒 # 连接池 lettuce 的配置 lettuce: pool: max-active: 100 min-idle: 10 max-wait: 100000 #整合mybatis时使用的 mybatis: #包别名 type-aliases-package: top.yueshushu.learn.pojo #映射文件路径 mapper-locations: classpath:mybatis/mapper/**/*.xml configuration: #日志信息 log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
一.二.三 启动类上 添加 @EnableCaching 注解
需要在启动类上 添加 @EnableCaching 注解, 开启缓存。
@MapperScan("top.yueshushu.learn.mapper") @SpringBootApplication //开启缓存 @EnableCaching public class RedisApplication { public static void main(String[] args) { SpringApplication.run(RedisApplication.class,args); System.out.println("运行 Redis Cache缓存"); } }
一.二.四 不使用缓存时处理
@Override public User findById(int id) { return userMapper.findById(id); }
一.二.四.二 查询测试
@Test public void findByIdTest(){ User user=userService.findById(40); //id随时更换 log.info(user); }
运行测试方法 findByIdTest()
第一次查询
发现查询了数据库
第二次查询
依然走的是数据库查询.
这是以前的常规的写法。
一.二.五 使用SpringCache 缓存时处理
@Override // 指定了参数为 id: 变成了: value::id 的key值 @Cacheable(value=KEY_PRE,key = "#id") public User findById(int id) { return userMapper.findById(id); }
在方法上 添加了一个注解 @Cacheable ,补充属性信息
value 表示使用的缓存组, key 表示缓存的值。
@Test public void findByIdTest(){ User user=userService.findById(40); //id随时更换 log.info(user); }
运行测试方法 findByIdTest()
第一次查询
发现查询了数据库
第二次查询
发现,并没有查询数据库,走的是缓存里面的数据。
查看 Redis客户端
发现存储的数据乱码了.
一.二.六 处理存储信息乱码问题
除了 RedisConfig.java 配置之外 ,再添加一个 CacheConfig.java 的配置信息
package top.yueshushu.learn.config; import com.fasterxml.jackson.annotation.JsonAutoDetect; import com.fasterxml.jackson.annotation.PropertyAccessor; import com.fasterxml.jackson.databind.ObjectMapper; import lombok.extern.log4j.Log4j2; import lombok.extern.slf4j.Slf4j; import org.springframework.cache.CacheManager; import org.springframework.cache.annotation.CachingConfigurerSupport; import org.springframework.cache.interceptor.*; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.cache.RedisCacheConfiguration; import org.springframework.data.redis.cache.RedisCacheManager; import org.springframework.data.redis.cache.RedisCacheWriter; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer; import org.springframework.data.redis.serializer.RedisSerializationContext; import javax.annotation.Resource; import java.time.Duration; import java.util.HashMap; import java.util.Map; /** * @author :zk_yjl * @description:Cache的缓存配置信息,可以解决乱码问题 * @date :2021/09/23 17:09 */ @Log4j2 @Configuration public class CacheConfig extends CachingConfigurerSupport { @Resource private RedisConnectionFactory factory; /** * 自定义生成redis-key * * @return */ @Override @Bean public KeyGenerator keyGenerator() { return (o, method, objects) -> { StringBuilder sb = new StringBuilder(); sb.append(o.getClass().getName()).append("."); sb.append(method.getName()).append("."); for (Object obj : objects) { sb.append(obj.toString()); } log.info("keyGenerator=" + sb.toString()); return sb.toString(); }; } @Bean @Override public CacheResolver cacheResolver() { return new SimpleCacheResolver(cacheManager()); } @Bean @Override public CacheErrorHandler errorHandler() { // 用于捕获从Cache中进行CRUD时的异常的回调处理器。 return new SimpleCacheErrorHandler(); } @Bean @Override public CacheManager cacheManager() { return new RedisCacheManager( RedisCacheWriter.nonLockingRedisCacheWriter(factory), this.getRedisCacheConfigurationWithTtl(30*60), // 默认策略,未配置的 key 会使用这个 this.getRedisCacheConfigurationMap() // 指定 key 策略 ); } private Map<String, RedisCacheConfiguration> getRedisCacheConfigurationMap() { Map<String, RedisCacheConfiguration> redisCacheConfigurationMap = new HashMap<>(); //DayCache和SecondsCache进行过期时间配置translates缓存丢弃改为了redis redisCacheConfigurationMap.put("translates", this.getRedisCacheConfigurationWithTtl(12*60*60)); redisCacheConfigurationMap.put("strategies", this.getRedisCacheConfigurationWithTtl(60)); return redisCacheConfigurationMap; } private RedisCacheConfiguration getRedisCacheConfigurationWithTtl(Integer seconds) { Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class); ObjectMapper om = new ObjectMapper(); om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL); jackson2JsonRedisSerializer.setObjectMapper(om); RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig(); redisCacheConfiguration = redisCacheConfiguration.serializeValuesWith( RedisSerializationContext .SerializationPair .fromSerializer(jackson2JsonRedisSerializer) ).entryTtl(Duration.ofSeconds(seconds)); return redisCacheConfiguration; } }
重新运行测试 (此时缓存信息并没有清空)
出现了异常.
将缓存信息 key 清空后再执行, 运行是成功的,
从数据库里面查询, 将查询结果放置到Redis缓存里面,并且缓存信息正常展示。