声明:小白,学习阶段,主要目的是为了记录学习过程,本文仅供参考,如有不足的地方欢迎指出讨论交流
本文基于Springboot2.1.3版本开发:

准备阶段

首先是pom.xml文件所需的依赖:

 <dependencies><!--redis依赖--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId><version>2.1.3.RELEASE</version></dependency><!--缓存模块所需依赖--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-cache</artifactId></dependency></dependencies>

接着就是application.properties,这里使用的是yum格式的配置文件:

spring:datasource:#配置mysql连接信息url: jdbc:mysql://localhost:3306/enterprisetalentmanagement?serverTimezone=UTCusername: rootpassword: 1234driver-class-name: com.mysql.cj.jdbc.Driverthymeleaf:cache: falseredis:host: 192.168.0.120 #配置redis的IP地址port: 6379 #该属性默认使用的是6379,如果你redis端口映射的不是6379可以通过这个属性来改
logging:level:#打印SQL信息com.demo02.demo.Dao: debug
mybatis:configuration:#开启下划线到驼峰命名法的自动转换,将数据库字段根据驼峰规则自动注入到对象属性map-underscore-to-camel-case: true
server:port: 8080debug: true

这个地方我们开启debug:true,这样我们就可以在程序运行的时候很方便的看到哪些配置类生效,结果会打印到控制台中,接着是springboot的启动类:

@SpringBootApplication //声明启动类
@MapperScan("com.demo02.demo.Dao.Mapper") //mapper文件扫描
@EnableCaching //开启注解
public class DemoApplication {public static void main(String[] args) {SpringApplication.run(DemoApplication.class, args);}
}

接下来在Service中我们来尝试一下缓存最基本的用法:

import com.demo02.demo.Dao.Mapper;
import com.demo02.demo.POJO.department;
import com.demo02.demo.POJO.enterprisetalent;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;import java.util.List;
@Service
public class serviceimpl implements service{@Autowiredprivate Mapper mapper;//将方法的运行结果进行缓存,以后再要相同的数据,可以直接从缓存中获取不需要调用方法@Cacheable(cacheNames = "emp")@Overridepublic enterprisetalent getempByid(Integer id) {System.out.println("查询:"+id+"号员工");return mapper.selectempById(id);}
}

常用的缓存注解

注解名 作用
@Cacheable 主要针对方法配置,能够根据方法的请求参数对其结果进行缓存
@CacheEvict 清空缓存
@CachePut 保证方法被调用,又希望结果被缓存
@EnableCaching 开启基于注解的缓存

我们可以点击@Cacheable注解进入它的源码,来看看它有些什么属性:

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Cacheable {/*** Alias for {@link #cacheNames}.*/@AliasFor("cacheNames")String[] value() default {};@AliasFor("value")String[] cacheNames() default {};String key() default "";String keyGenerator() default "";String cacheManager() default "";String cacheResolver() default "";String unless() default "";boolean sync() default false;

可以看到有一个叫做value的属性和它的作用其实是和cacheNames一样的,用于指定缓存组件的名字,是数组的方式,可以指定多个缓存。

cacheNames 用于指定缓存存储的集合名,value作用与 cacheNames相同
key 缓存数据使用的key;可以用它来指定。默认是使用方法参数的值
keyGenerator key的生成器;可以自己指定key的生成器的组件id 。key/keyGenerator:二选一使用
cacheManager 指定缓存管理器;或者cacheResolver指定获取解析器
unless 否定缓存;当unless指定的条件为true,方法的返回值就不会被缓存;可以获取到结果进行判断

这里先只介绍这五个常用属性

其他类和文件就是普通的三层架构dao,service,controller大家应该都会我先就不写着占位置了,
我们可以先运行代码一遍,执行getempByid()方法:

可以看到,我们第一次执行查询是调用了方法去连接数据库的获取数据的,查询的是id为2的员工信息,打开RedisDesktopManager我们可以看见redis中多了一个名为emp::2,但它的value是序列化后的结果,因为我们要将对象数据存储到redis中实体类必须序列化,这个问题我们会放到后面说。
  

缓存运行原理浅析

说完了基本的文件配置接下来说我们应该怎么去实现springboot整合redis作为缓存,
首先我们先屡一下缓存的运行原理,既然我们引入了缓存模块那我们就应该可以想到肯定会有一个类叫做xxxAutoConfiguration的自动配置类,不然我们还用什么springboot。全局搜索可以找到一个叫做CacheAutoConfiguration的类,点进去源码可以看到它给我们注入了一个叫做CacheConfigurationImportSelector的class。

我们点击定位到该类所在位置

可以看到里面有一个方法,这个方法有什么作用呢,我们可以直接在return上打个断点,然后以debug模式运行程序:

我们可以看到,这个方法返回的都是一些xxxCacheConfiguration之类的组件,是一些缓存的配置类bean,所以这个方法的作用就是为我们的容器中添加一些缓存组件,接着我们把它放行,因为之前我们在配置文件中配置了debug=true,我们可以在控制台中看到这些bean的输出:

可以发现,在刚才selectImports方法为我们添加的组件中只有SimpleCacheConfiguration是matched匹配上的,通过打印日志可以看出SimpleCacheConfiguration配置类默认生效,我们可以全局搜索SimpleCacheConfiguration类进入源码看看它做了什么:

首先可以看到该类的上边有个注解叫做@ConditionalOnMissingBean(CacheManager.class),这个注解的意思是只有在容器中没有CacheManager也就是缓存组件的时候才会给该类装配,如果有的话该类就不会生效。
接下来看到下面的cacheManager方法,该方法先是直接创建了一个ConcurrentMapCacheManager的对象,点击进入ConcurrentMapCacheManager可以看到里面声明了一个叫做ConcurrentMap<String, Cache>的HashMap,这个map的key是一个字符串,value是对应的cache缓存对象,可以看出来该对象是用来存储缓存对象的:

往下拉找到getCache(String name)的方法:

 @Override@Nullablepublic Cache getCache(String name) {Cache cache = this.cacheMap.get(name);if (cache == null && this.dynamic) {synchronized (this.cacheMap) {cache = this.cacheMap.get(name);if (cache == null) {cache = createConcurrentMapCache(name);this.cacheMap.put(name, cache);}}}return cache;}

它会通过传进来的name也就是我们在Service方法的注解中设置的cacheNames去cacheMap中获取对应的cache,如果获取到的cache为null,则会给cacheMap上锁后再去获取,如果还是为空就会调用createConcurrentMapCache方法,我们可以进入createConcurrentMapCache看看它又做了什么:

可以看到它会去帮去我们创建一个缓存对象,该方法返回了一个叫做ConcurrentMapCache对象,该对象将cacheNames给传入了进去,我们进入ConcurrentMapCache,可以看到其中有两个方法,一个叫做put,还有一个叫做lookup,我们为这两个方法打上断点来看看他们的运行时机:

可以看到我们传入了一个不存在的cacheNames,它会通过之前的getCache方法进入到createConcurrentMapCache方法,然后createConcurrentMapCache方法会去new一个ConcurrentMapCache方法,进入到ConcurrentMapCache后首先会调用lookup方法,lookup方法会以某种策略生成一个key,当不指定缓存的key时,SpringBoot会默认使用keyGenerator()方法生成key,然后会调用目标方法也就是getempByid(),方法得到返回值后ConcurrentMapCache会调用put()方法拿到目标方法返回值,再将目标方法返回值放入新创建的缓存中。

整合RedisTemplate

在我们引入了redis模块的依赖后,redis的starter场景后自动配置就会生效,这时候我们再来启动一次程序,观察控制台输出,可以看到RedisCacheConfiguration这个组件就被matched了:


而先前SpringBoot给我们默认的SimpleCacheConfiguration组件就不会生效,因为我们引入redis的场景就开启了RedisCacheConfiguration的自动配置,redis的缓存组件会被注册,而SimpleCacheConfiguration中标注了@ConditionalOnMissingBean(CacheManager.class),现在CacheManager已经有了redis的缓存组件了所以SimpleCacheConfiguration就不会生效。
然后我们来说一下为什么前面我们存入redis的数据会是那样的字节对象呢?原因是因为CacheManager中value的序列化方式默认使用的是JdkSerializationRedisSerializer,也就是jdk自带的序列化器,所以我们之前才会说需要被序列化的实体类必须要实现Serializable接口,那我们一般喜欢用json的方式存储数据,我们可以去先去看一下它源码中是怎么实现的,我们找到redis的缓存组件RedisCacheConfiguration进入源码:
** **
先说明一下,SpringBoot1.x和2.x版本的cacheManager略有不同,2.x版本之前RedisCacheManager会自带一个单参构造函数,而2.x后换成了builder构造,我们可以参照源码中的方法自定义一些规则:

@Bean
public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory){RedisSerializer<String> redisSerializer = new StringRedisSerializer();Jackson2JsonRedisSerializer 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 config = RedisCacheConfiguration.defaultCacheConfig().entryTtl(Duration.ofHours(1)) //我们这里设置缓存失效时间为一个小时.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer)).serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer)).disableCachingNullValues();RedisCacheManager cacheManager = RedisCacheManager.builder(redisConnectionFactory).cacheDefaults(config).build();return cacheManager;}

我们这里给CacheManager设置的序列化规则用的是Jackson2JsonRedisSerializer,它实现了RedisSerializer接口,找到RedisSerializer接口ctrl+h可以查看有哪些类实现了它:

可以看到我们之前默认的JdkSerializationRedisSerializer,还有一些其他的类,我们点进我们现在所使用的Jackson2JsonRedisSerializer:

可以看到类中注释了这个该类可以用来读取和写入JSON,“ {@link RedisSerializer} that can read and write JSON using”,注意,定制了CacheManager 后方法上一定要记得加上@Bean注解,这样我们写的CacheManager 就会注册进Spring容器中取代原先的CacheManager,我们再启动一遍程序调用一遍方法:

可以看到现在数据就变成了json的形式;

StringRedisTemplate与RedisTemplate

两者的关系是StringRedisTemplate继承RedisTemplate。

两者的数据是不共通的;也就是说StringRedisTemplate只能管理StringRedisTemplate里面的数据,RedisTemplate只能管理RedisTemplate中的数据。

SDR默认采用的序列化策略有两种,一种是String的序列化策略,一种是JDK的序列化策略。

StringRedisTemplate默认采用的是String的序列化策略,保存的key和value都是采用此策略序列化保存的。

RedisTemplate默认采用的是JDK的序列化策略,保存的key和value都是采用此策略序列化保存的。

StringRedisTemplate常用方法:
  这里给大家一个链接,StringRedisTemplate常用方法都在里面我就不一一列出来了  https://blog.csdn.net/awhip9/article/details/71425041

RedisTemplate常用方法
     RedisTemplate中定义了对5种数据结构操作
     redisTemplate.opsForValue();//操作字符串
     redisTemplate.opsForHash();//操作hash
     redisTemplate.opsForList();//操作list
     redisTemplate.opsForSet();//操作set
     redisTemplate.opsForZSet();//操作有序set

这里我们来简单演示一个demo:

 @Autowiredprivate RedisTemplate<Object, Object> redisTemplate;@Autowiredprivate StringRedisTemplate stringRedisTemplate;@Overridepublic void SRTemplateTest(){stringRedisTemplate.opsForValue().set("hhh","hhh");}@Overridepublic department getdeptByid(Integer id) {System.out.println("查询"+id+"号部门");department department = mapper.selectdeptByid(id);redisTemplate.opsForValue().set("dept",department);return department;}

如果想要存储String类型的数据推荐使用StringRedisTemplate ,因为StringRedisTemplate 默认采用的是String的序列化策略,保存的key和value都是采用此策略序列化保存的。而RedisTemplate默认采用的JDK的序列化策略,会出现一种情况,我们下面会介绍及该如何处理这种情况,StringRedisTemplate 主要作用是用来操作字符串,我们重点介绍RedisTemplate的使用及如何去定制它的序列化机制,现在调用getdeptByid()方法:

会发现我们的dept已经存入redis中了,但显示方式却是序列化后的字节对象,我们前面说过RedisTemplate默认使用的JDK的序列化策略,我们点击进入RedisTemplate源码:

这边可以看到RedisTemplate中RedisSerializer<?> defaultSerializer也就是redis的序列化机制如果是空的话,他会默认采用JdkSerializationRedisSerializer来进行序列化,我们找到redis的自动配置类RedisAutoConfiguration:

进入源码可以看到,里面有两个方法,这两个方法正是我们前面所使用的RedisTemplate和StringRedisTemplate ,我们可以参照源码的代码来自己重写一个新方法,起名就叫做deptRedisTemplate():

@Bean
public RedisTemplate<Object, department> deptRedisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {RedisTemplate<Object, department> template = new RedisTemplate<Object, department>();template.setConnectionFactory(redisConnectionFactory);Jackson2JsonRedisSerializer<department> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<department>(department.class);template.setDefaultSerializer(jackson2JsonRedisSerializer);return template;}

和前面一样我们想要把数据序列化成JSON形式,声明Jackson2JsonRedisSerializer对象:



接下来我们和源码中一样实例一个RedisTemplate对象,在它的源码中可以看到里面声明了一个RedisSerializer<?> defaultSerializer,这个正是前面做判断的条件,它是RedisTemplate
的序列化机制,如果我们没有主动赋值它就会默认使用JdkSerializationRedisSerializer,回到代码我们给RedisTemplate对象setDefaultSerializer()我们自己的序列化机制,把上面声明的jackson2JsonRedisSerializer入参,然后返回RedisTemplate对象,和之前CacheManager 方法一样我们一定要在方法上加上@Bean注解,这样我们定义的RedisTemplate就会替换容器中的bean。
最后在Service中声明我们自己的deptRedisTemplate()方法再次调用就解决了序列化的问题了。

第一次写博客,希望能够帮到大家。

Springboot整合redis实现缓存及其缓存运行原理浅析相关推荐

  1. SpringBoot整合Redis配置MyBatis二级缓存

    目录 写在前面 源码获取 一.MyBatis缓存机制 1.1.一级缓存 1.2.二级缓存 二.集成Redis 2.1.安装Redis 2.2.项目引入Redis 2.2.1.Maven依赖 2.2.2 ...

  2. springboot整合redis分别实现手动缓存和注解缓存

    一.前期准备 一个构建好的springboot系统 下载redis安装包,去redis官网下载 启动redis服务,windows下双击bin目录下的redis-service.exe 二.环境构建 ...

  3. SpringBoot整合Redis缓存

    SpringBoot整合Redis缓存 一.缓存概念知识 1.是什么缓存 2.缓存的优缺点 3.为什么使用缓存 二.Redis概念知识 1.Redis简介 2.为什么用Redis作为缓存 3.Redi ...

  4. 8分钟带你学会SpringBoot整合Redis来实现缓存技术

    1.概述 随着互联网技术的发展,对技术要求也越来越高,所以在当期情况下项目的开发中对数据访问的效率也有了很高的要求,所以在项目开发中缓存技术使用的也越来越多,因为它可以极大的提高系统的访问速度,关于缓 ...

  5. Springboot:整合redis对接口数据进行缓存

    文章目录 Springboot:整合redis对接口数据进行缓存 一.注解及其属性介绍 @Cacheable @CacheEvict @CachePut @CacheConfig 二.代码实现 1.创 ...

  6. SpringBoot整合Redis+Redis缓存应用+Redis实现Session共享+...

    一.SpringBoot整合Redis 1.导入依赖 <!--存在Redis依赖--> <dependency><groupId>org.springframewo ...

  7. springboot整合redis做缓存

    之前的项目中,用到过redis,主要是使用redis做缓存,redis在web开发中使用的场景很多,其中缓存是其中一个很重要的使用场景,之所以用作缓存,得益于redis的读写数据,尤其是在读取数据的时 ...

  8. 【SpringBoot】Redis配置CacheManager实现缓存

    1.唠嗑 最近学了网上的springboot1.X的springboot整合redis实现缓存,自己在练习的时候才发现springboot2.x已经不支持教程里面的配置cacheManager了,网上 ...

  9. SpringBoot(六):SpringBoot整合Redis

    From: https://blog.csdn.net/plei_yue/article/details/79362372 前言 在本篇文章中将SpringBoot整合Redis,使用的是RedisT ...

最新文章

  1. 在Visual Studio中使用Git [关闭]
  2. android fragment概念,android Fragment相关概念简介
  3. 对简单单元格的增删改
  4. 参数匹配顺序——Python学习之参数(三)
  5. Percona Server for MySQL 5.5.30-30.2
  6. 如何删除 Mac 上的 Office 许可证文件?
  7. ArcGIS 起伏度、坡度、交通便利度数据生成
  8. 2022年最值得学习的5款开源Java框架
  9. 疯狂java讲义第七章课后习题答案
  10. 小米笔记本PRO黑苹果使用第三方蓝牙设备
  11. c语言中windows头文件,windows与linux 标准c语言头文件
  12. 三极管的输入输出的特性曲线
  13. PS制作搞笑印章 - 仿真印章 - 水印滤色
  14. 笔记本电脑的计算机名称在哪里看,如何查看笔记本电脑的IP地址
  15. 小技巧 CSR蓝牙连接问题
  16. jQuery实现购物车功能(小计、总计)
  17. php和python学不明白_现在自学php和python那个合适?
  18. UEFI中的界面设计(一)
  19. Eclipse---Refreshing /.org.eclipse.jdt.core.external.folders/.link0
  20. PS学习之小猪佩奇身上纹,掌声送给社会人

热门文章

  1. 前端遇到的问题及解决办法
  2. WordCloud词云库快速入门(一)
  3. 互联网金融将会成为真正的“穷人银行”
  4. CAD标注设置精度的方法
  5. linux底层驱动内核,Linux底层驱动开发需要学习哪些内容
  6. Stata:elabel命令-强大的标签管理工具
  7. CSDN为什么没有单独购买积分的方式?
  8. Python Cartopy地图投影【1】
  9. 香港夜景[Canon IXUS75拍摄]
  10. EXCEL:隐藏的模块中的编译错误:mSetMenu