Spring框架支持透明地向应用程序添加缓存对缓存进行管理,其管理缓存的核心是将缓存应用于操作数据的方法,从而减少操作数据的执行次数,同时不会对程序本身造成任何干扰。
Spring Boot继承了Spring框架的缓存管理功能,通过使用 @EnableCaching 注解开启基于注解的缓存支持,Spring Boot就可以启动缓存管理的自动化配置。
基础环境搭建:
Spring boot版本:2.7.6
持久层:Spring Data Jpa
数据库:mysql 5.7
redis:latest
maven:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>application.properties
# MySQL数据库连接配置
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/test?characterEncoding=utf8
spring.datasource.username=root
spring.datasource.password=root
#显示使用JPA进行数据库查询的SQL语句
spring.jpa.show-sql=true
#开启驼峰命名匹配映射
mybatis.configuration.map-underscore-to-camel-case=true
#解决乱码
server.servlet.encoding.force-response=truepojo
@Data
@AllArgsConstructor
@NoArgsConstructor
@Entity
@Table(name = "tb_resume")
public class Resume {
/**
* GenerationType.SEQUENCE:依靠序列来产⽣主键 Oracle
* GenerationType.IDENTITY:依赖数据库中主键⾃增功能 Mysql
*/
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
private Long id;
@Column(name = "address")
private String address;
@Column(name = "name")
private String name;
@Column(name = "phone")
private String phone;
}repository
public interface ResumeRepository extends JpaRepository<Resume,Long> {
//根据id修改人物
@Query(value = "update t_resume t set t.name = ?1 where t.id = ?2",nativeQuery = true)
int updateResume(String name,Long id);
}service
@Service
public class ResumeService {
@Autowired
private ResumeRepository repository;
public Resume getResumeById(Long id){
Resume resume = repository.findById(id).get();
System.out.println(resume);
return resume;
}
}controller
@RestController
public class ResumeController {
@Autowired
private ResumeService service;
@RequestMapping("/getResumeById")
public Resume getResumeById(Long id){
Resume resume = service.getResumeById(id);
return resume;
}
}测试结果
Hibernate: select resume0_.id as id1_0_0_, resume0_.address as address2_0_0_, resume0_.name as name3_0_0_, resume0_.phone as phone4_0_0_ from tb_resume resume0_ where resume0_.id=?
Resume(id=1, address=北京, name=刘二, phone=010100000)改造Spring boot默认缓存
在前面搭建的Web应用基础上,开启Spring Boot默认支持的缓存,体验Spring Boot默认缓存的使用效果
使用 @EnableCaching 注解开启基于注解的缓存支持
@EnableCaching //开启spring基于注解缓存管理支持
@SpringBootApplication
public class SpringbootCacheApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootCacheApplication.class, args);
}
}使用 @Cacheable 注解对数据操作方法进行缓存管理。将 @Cacheable 注解标注在Service类的查 询方法上,对查询结果进行缓存
@Service
public class ResumeService {
@Autowired
private ResumeRepository repository;
//@Cacheable 该注解作用就是将查询结果存放到springboot默认缓存中
//cacheNames 起一个缓存命名空间 对应缓存唯一标识
@Cacheable(cacheNames = "resume")
public Resume getResumeById(Long id){
Resume resume = repository.findById(id).get();
System.out.println(resume);
return resume;
}
}测试结果,除了第一次点击后台进行数据库访问外,后面的每一次调用都是直接从Springboot缓存中获取数据。
简单分析一下原理:
- 底层结构:在诸多的缓存自动配置类中, SpringBoot默认装配的是 SimpleCacheConfiguration 他使用的 CacheManager 是 ConcurrentMapCacheManager ,使用 ConcurrentMap 当底层的数据 结构,按照Cache的名字查询出Cache, 每一个Cache中存在多个k-v键值对,缓存值

@EnableCaching注解解析
@EnableCaching 是由spring框架提供的,Springboot 框架对该注解进行了继承,该注解需要配置在类上(通常配置在项目启动类上),用于开启基于注解的缓存支持
@Cacheable注解解析
@Cacheable 注解也是由spring框架提供的,可以作用于类或方法(通常用在数据查询方法上),用于 对方法结果进行缓存存储。注解的执行顺序是,先进行缓存查询,如果为空则进行方法查询,并将结果 进行缓存;如果缓存中有数据,不进行方法查询,而是直接使用缓存数据
@Cacheable 注解提供了多个属性,用于对缓存存储进行相关配置
属性名 | 说明 |
value/cacheNames | 指定缓存空间的名称,必配属性。这两个属性二选一使用 |
key | 指定缓存数据的key,默认使用方法参数值,可以使用SpEL表达式 |
keyGenerator | 指定缓存数据的key的生成器,与key属性二选一使用 |
cacheManager | 指定缓存管理器 |
cacheResolver | 指定缓存解析器,与cacheManager属性二选一使用 |
condition | 指定在符合某条件下,进行数据缓存 |
unless | 指定在符合某条件下,不进行数据缓存 |
sync | 指定是否使用异步缓存。默认false |
执行流程&时机
方法运行之前,先去查询Cache(缓存组件),按照 cacheNames 指定的名字获取,( CacheManager 先获取相应的缓存),第一次获取缓存如果没有Cache组件会自动创建;
去Cache中查找缓存的内容,使用一个key,默认就是方法的参数,如果多个参数或者没有参数,是按 照某种策略生成的,默认是使用 KeyGenerator 生成的,使用 SimpleKeyGenerator 生成 key, SimpleKeyGenerator 生成 key 的默认策略:
参数个数 | key |
没有参数 | new SimpleKey() |
有一个参数 | 参数值 |
多个参数 | new SimpleKey(params) (params:所有参数集合) |
常用的SPEL表达式
描述 | 示例 |
当前被调用的方法名 | #root.mathodName |
当前被调用的方法 | #root.mathod |
当前被调用的目标对象 | #root.target |
当前被调用的目标对象类 | #root.targetClass |
当前被调用的方法的参数列表 | #root.args[0] 第一个参数, #root.args[1] 第二个参数... |
根据参数名字取出值 | #参数名, 也可以使用 #p0 #a0 0是参数的下标索引 |
当前方法的返回值 | #result |
@CachePut注解
目标方法执行完之后生效, @CachePut 被使用于修改操作比较多,哪怕缓存中已经存在目标值了,但是这个 注解保证这个方法依然会执行,执行之后的结果被保存在缓存中。
@CachePut注解也提供了多个属性,这些属性与@Cacheable注解的属性完全相同。
更新操作,前端会把 id+ 实体传递到后端使用,我们就直接指定方法的返回值从新存进缓存时的 key="#id" , 如果前端只是给了实体,我们就使用 key="#实体.id" 获取 key. 同时,他的执行时机是目标 方法结束后执行, 所以也可以使用 key="#result.id" , 拿出返回值的id;
@CacheEvict注解
@CacheEvict 注解是由Spring框架提供的,可以作用于类或方法(通常用在数据删除方法上),该注解 的作用是删除缓存数据。@CacheEvict 注解的默认执行顺序是,先进行方法调用,然后将缓存进行清 除。
Spring Boot整合Redis缓存实现
在Spring Boot默认缓存管理的基础上引入Redis 缓存组件,使用基于注解的方式整合Redis缓存的具体实现;
pom.xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-redis</artifactId>
</dependency>当我们添加进 redis 相关的启动器之后, SpringBoot 会使用 RedisCacheConfigratioin 当做生效的自动配置类进行缓存相关的自动装配,容器中使用的缓存管理器是 RedisCacheManager , 这个缓存管理器创建的 Cache 为 RedisCache , 进而操控 redis 进行数据的缓存
Redis服务连接配置
# Redis服务地址
spring.redis.host=192.168.192.200
# Redis服务器连接端口
spring.redis.port=6379
# Redis服务器连接密码(默认为空)
spring.redis.password=wsy@123Service进行改造,使用@Cacheable、@CachePut、@CacheEvict三个注 解定制缓存管理,分别进行缓存存储、缓存更新和缓存删除
@Service
public class ResumeService {
@Autowired
private ResumeRepository repository;
/**
* select
* @param id
* @return
*/
//@Cacheable 该注解作用就是将查询结果存放到springboot默认缓存中
//cacheNames 起一个缓存命名空间 对应缓存唯一标识
//value 缓存结果,key:默认在只有一个参数的情况下,key值默认就是方法参数值,如果没有参数或者多个参数的情况下,由SimpleKeyGenerator生成key
//unless 指定在符合某条件下,不进行数据缓存
@Cacheable(cacheNames = "resume",unless = "#result==null")
public Resume getResumeById(Long id){
Resume resume = repository.findById(id).get();
System.out.println(resume);
return resume;
}
/**
* update
* @param resume
* @return
*/
@CachePut(cacheNames = "resume",key = "#result.id")
public Resume updataResume(Resume resume){
repository.updateResume(resume.getName(), resume.getId());
return resume;
}
/**
* delete
* @param id
*/
@CacheEvict(cacheNames = "resume")
public void deleteResume(Long id){
repository.deleteById(id);
}
}controller
@RestController
public class ResumeController {
@Autowired
private ResumeService service;
@RequestMapping("/getResumeById")
public Resume getResumeById(Long id){
Resume resume = service.getResumeById(id);
return resume;
}
@RequestMapping("/updateResume")
public Resume updateResume(Resume resume){
Resume resume1 = service.getResumeById(resume.getId());
resume1.setName(resume.getName());
Resume resume2 = service.updataResume(resume1);
return resume2;
}
@RequestMapping("/deleteResume")
public void deleteResume(Long id){
service.deleteResume(id);
}
}运行getResumeById 测试结果:

运动deleteResume 测试结果:

基于API的Redis缓存实现
ApiResumeService
@Service
public class ApiResumeService {
@Autowired
private ResumeRepository repository;
@Autowired
private RedisTemplate redisTemplate;
/**
* select
* @param id
* @return
*/
public Resume getResumeById(Long id){
Object o = redisTemplate.opsForValue().get("resume_" + id);
if(o != null) {
return (Resume) o;
} else {
Resume resume = repository.findById(id).get();
//将查询结果存到缓存中,同时还可以设置有效期为1天
redisTemplate.opsForValue().set("resume_" + id,resume,1, TimeUnit.DAYS);
System.out.println(resume);
return resume;
}
}
/**
* update
* @param resume
* @return
*/
public Resume updataResume(Resume resume){
repository.updateResume(resume.getName(), resume.getId());
redisTemplate.opsForValue().set("resume_" + resume.getId(),resume);
return resume;
}
/**
* delete
* @param id
*/
public void deleteResume(Long id){
repository.deleteById(id);
redisTemplate.delete("resume_"+id);
}
}ApiResumeController
@RestController
@RequestMapping("/api")
public class ApiResumeController {
@Autowired
private ApiResumeService service;
@RequestMapping("/getResumeById")
public Resume getResumeById(Long id){
Resume resume = service.getResumeById(id);
return resume;
}
@RequestMapping("/updateResume")
public Resume updateResume(Resume resume){
Resume resume1 = service.getResumeById(resume.getId());
resume1.setName(resume.getName());
Resume resume2 = service.updataResume(resume1);
return resume2;
}
@RequestMapping("/deleteResume")
public void deleteResume(Long id){
service.deleteResume(id);
}
}测试结果与上面无意;
自定义Redis缓存序列化机制
- Redis API默认序列化机制
基于API的Redis缓存实现是使用RedisTemplate模板进行数据缓存操作的,这里打开 RedisTemplate类,查看该类的源码信息
public class RedisTemplate<K, V> extends RedisAccessor implements RedisOperations<K, V>, BeanClassLoaderAware {
// 声明了key、value的各种序列化方式,初始值为空
private boolean enableTransactionSupport = false;
private boolean exposeConnection = false;
private boolean initialized = false;
private boolean enableDefaultSerializer = true;
@Nullable
private RedisSerializer<?> defaultSerializer;
@Nullable
private ClassLoader classLoader;
@Nullable
private RedisSerializer keySerializer = null;
@Nullable
private RedisSerializer valueSerializer = null;
@Nullable
private RedisSerializer hashKeySerializer = null;
@Nullable
private RedisSerializer hashValueSerializer = null;
private RedisSerializer<String> stringSerializer = RedisSerializer.string();
@Nullable
private ScriptExecutor<K> scriptExecutor;
private final ValueOperations<K, V> valueOps = new DefaultValueOperations(this);
private final ListOperations<K, V> listOps = new DefaultListOperations(this);
private final SetOperations<K, V> setOps = new DefaultSetOperations(this);
private final StreamOperations<K, ?, ?> streamOps = new DefaultStreamOperations(this, ObjectHashMapper.getSharedInstance());
private final ZSetOperations<K, V> zSetOps = new DefaultZSetOperations(this);
private final GeoOperations<K, V> geoOps = new DefaultGeoOperations(this);
private final HyperLogLogOperations<K, V> hllOps = new DefaultHyperLogLogOperations(this);
private final ClusterOperations<K, V> clusterOps = new DefaultClusterOperations(this);
public RedisTemplate() {
}
// 进行默认序列化方式设置,设置为JDK序列化方式
public void afterPropertiesSet() {
super.afterPropertiesSet();
boolean defaultUsed = false;
if (this.defaultSerializer == null) {
this.defaultSerializer = new JdkSerializationRedisSerializer(this.classLoader != null ? this.classLoader : this.getClass().getClassLoader());
}
。。。。。。从上述 RedisTemplate 核心源码可以看出,在 RedisTemplate 内部声明了缓存数据key、value的各 种序列化方式,且初始值都为空;在afterPropertiesSet() 方法中,判断如果默认序列化参数 defaultSerializer 为空,将数据的默认序列化方式设置为 JdkSerializationRedisSerializer
根据上述源码信息的分析,可以得到以下两个重要的结论:
1, 使用RedisTemplate进行Redis数据缓存操作时,内部默认使用的是 JdkSerializationRedisSerializer序列化方式,所以进行数据缓存的实体类必须实现JDK自带的序列化接 口(例如Serializable);
2, 使用RedisTemplate进行Redis数据缓存操作时,如果自定义了缓存序列化方式 defaultSerializer,那么将使用自定义的序列化方式。
- 自定义RedisTemplate序列化机制
在项目中引入Redis依赖后,Spring Boot提供的RedisAutoConfiguration自动配置会生效。打开 RedisAutoConfiguration类,查看内部源码中关于RedisTemplate的定义方式
@AutoConfiguration
@ConditionalOnClass({RedisOperations.class})
@EnableConfigurationProperties({RedisProperties.class})
@Import({LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class})
public class RedisAutoConfiguration {
public RedisAutoConfiguration() {
}
@Bean
@ConditionalOnMissingBean(
name = {"redisTemplate"}
)
@ConditionalOnSingleCandidate(RedisConnectionFactory.class)
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<Object, Object> template = new RedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
@Bean
@ConditionalOnMissingBean
@ConditionalOnSingleCandidate(RedisConnectionFactory.class)
public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
return new StringRedisTemplate(redisConnectionFactory);
}
......从上述RedisAutoConfiguration核心源码中可以看出,在Redis自动配置类中,通过Redis连接工厂 RedisConnectionFactory初始化了一个RedisTemplate;该类上方添加了 @ConditionalOnMissingBean注解(顾名思义,当某个Bean不存在时生效),用来表明如果开发者自 定义了一个名为redisTemplate的Bean,则该默认初始化的RedisTemplate不会生效。
如果想要使用自定义序列化方式的RedisTemplate进行数据缓存操作,可以参考上述核心代码创建 一个名为redisTemplate的Bean组件,并在该组件中设置对应的序列化方式即可,如下:
@Configuration
public class RedisConfig {
//自定义 RedisTemplate
@Bean
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<Object, Object> template = new RedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
//创建Json格式序列化对象,对缓存数据的key和value进行转换
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);
//设置 RedisTemplate 模板Api的序列化方式为Json
template.setDefaultSerializer(jackson2JsonRedisSerializer);
return template;
}
}- 测试结果:

















