我们知道一个程序的瓶颈在于数据库,我们也知道内存的速度是大大快于硬盘的速度的。当我们需要重复地获取相同的数据的时候,我们一次又一次的请求数据库或者远程服务,导致大量的时间耗费在数据库查询或者远程方法调用上,导致程序性能的恶化,这便是数据缓存要解决的问题。
1.spring的缓存支持
从Spring3.1开始,定义了org.springframework.cache.CacheManager和org.springframework.cache.Cache接口用来统一不同的缓存的技术。其中,CacheManager是Spring提供的各种缓存技术抽象接口,Cache接口包含缓存的各种操作(增加、删除、获得缓存,我们一般不会直接和此接口打交道)。
1.Spring支持的CacheManager
在我们使用任意一个实现的CacheManager的时候,需注册实现的CacheManager的Bean,例如:
@Bean
public EhCacheCacheManager cacheManager(CacheManager ehCacheCacheManager) {
return new EhCacheCacheManager(ehCacheCacheManager);
}
2.声名式缓存注解
Spring提供了4个注解来声明缓存规则
注解 | 解释 |
@Cacheable |
在方法执行前Spring先查看缓存中是否有数据,如果有数据,则直接返回缓存数据;若没有数据,调用方法并将方法值放进缓存 |
@CachePut |
无论怎样,都将方法的返回值放到缓存中。
@CachePut与@Cacheable的属性一致。 |
@CacheEvict |
将一条或多条数据从缓存中删除 |
@Caching | 可以通过@Caching注解组合多个注册策略在一个方法上 |
@Cacheable、@CachePut、@CacheEvit都有value属性,指定的是要使用的缓存名称;key属性指定的是数据在缓存中的存储的键。
3.开启声名式缓存支持
开启声名式缓存支持十分简单,只需在配置类上使用@EnableCaching注解即可,例如:
@Configuration
@EnableCaching
public class AppConfig {
}
2.Spring Boot的支持
在Spring中使用缓存技术的关键是配置CacheManager,而Spring Boot为我们自动配置了多个CacheManager的实现。
Spring Boot的CacheManager的自动配置放置在org.springframework.boot.autoconfigure.cache包中,Spring Boot为我们自动配置了EhCacheCacheConfiguration(使用EhCache)、GenericCacheConfiguration(使用Collection)、GuavaCacheConfiguration(使用Guava)、HazelcastCacheConfiguration(使用Hazelcast)、InfinispanCacheConfiguration(使用Infinispan)、JCacheCacheConfiguration(使用JCache)、NoOpCacheConfiguration(不使用存储)、RedisCacheConfiguration(使用Redis)、SimpleCacheConfiguration(使用ConcurrentMap)。在不做任何额外配置的情况下,默认使用的是SimpleCacheConfiguration,即使用ConcurrentMapCacheManager。Spring Boot支持以“spring.cache”为前缀的属性来配置缓存。
spring.cache.type= # 可选generic, ehcache, hazelcast, infinispan, jcache, redis, # guava, simple, none
spring.cache.cache-names= # 程序启动时创建缓存名称
spring.cache.ehcache.config= # ehcache配置文件地址
spring.cache.infinispan.config= # infinispan 配置文件地址
spring.cache.jcache.config= # jcache 配置文件地址
spring.cache.jcache.provider= #当多个 jcache实现在类路径中的时候,指定jcache实现
在Spring Boot环境下,使用缓存技术只需在项目中导入相关缓存技术的依赖包,并在配置类使用@EnableCaching开启缓存支持即可。
代码示例
1.引入jar包
<!--cache-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
<version>2.6.6</version>
</dependency>
2.service层
package com.demo.service;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.demo.dao.SearchRecordMapper;
import com.demo.dto.SearchRecordRequest;
import com.demo.entity.SearchRecordEntity;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import java.util.*;
/**
* SearchRecordService
*
* @Description
*/
@Service
@Slf4j
public class SearchRecordService extends ServiceImpl<SearchRecordMapper, SearchRecordEntity> {
/**
* 获取搜索记录列表接口
*
* @param recordRequest
* @return
*/
public IPage<SearchRecordEntity> findAll(SearchRecordRequest recordRequest) {
Map<String, Object> result = new HashMap<>();
List<SearchRecordEntity> recordEntities = new ArrayList<>();
IPage<SearchRecordEntity> page = new Page<>();
page.setSize(recordRequest.getPageSize());
page.setCurrent(recordRequest.getPage());
QueryWrapper<SearchRecordEntity> queryWrapper = new QueryWrapper<>();
if (StringUtils.isNotEmpty(recordRequest.getName())) {
queryWrapper.like("name", recordRequest.getName());
}
queryWrapper.orderByDesc("id");
IPage<SearchRecordEntity> searchRecords = this.page(page, queryWrapper);
return searchRecords;
}
/**
* CachePut缓存新增的或更新的数据到缓存,其中缓存名称为searchRecord,数据的key是参数的id。
* @param searchRecordEntity
*/
@CachePut(value = "searchRecord",key = "#p0.id")
public void saveSearchRecord(SearchRecordEntity searchRecordEntity){
this.save(searchRecordEntity);
log.info("搜索记录id:{}添加数据缓存",searchRecordEntity.getId());
}
/**
* @CacheEvict 从缓存searchRecord中删除key为id的数据
* @param id
*/
@CacheEvict(value = "searchRecord",key = "#id")
public void remove(Integer id){
this.removeById(id);
log.info("搜索记录id:{}移除数据缓存",id);
}
/**
* @Cacheable 缓存key为searchRecord的id数据到缓存searchRecord中
* @param id
* @return
*/
@Cacheable(value = "searchRecord",key = "#id")
public SearchRecordEntity findById(Integer id){
SearchRecordEntity searchRecordEntity = this.getById(id);
log.info("搜索记录id:{}查看数据缓存",searchRecordEntity.getId());
return searchRecordEntity;
}
}
3.controller层
package com.demo.controller;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.api.R;
import com.demo.dto.SearchRecordRequest;
import com.demo.entity.SearchRecordEntity;
import com.demo.service.SearchRecordService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
/**
* SearchRecordController
*
* @Description
*/
@Slf4j
@RestController
@RequestMapping(value = "esp/search-record")
public class SearchRecordController {
@Resource
private SearchRecordService searchRecordService;
/**
* 查询搜索记录列表接口
*
* @param recordRequest
* @return
*/
@GetMapping("/findAll")
public R<IPage<SearchRecordEntity>> findAll(SearchRecordRequest recordRequest) {
try {
if (recordRequest.getPage() == null || recordRequest.getPageSize() == null) {
return R.failed("page或pageSize不能为空");
}
return R.ok(searchRecordService.findAll(recordRequest));
} catch (Exception e) {
log.error("查询搜索记录列表接口异常:{}", e);
return R.failed("查询搜索记录列表接口异常:" + e.getMessage());
}
}
@GetMapping("find/{id}")
public R<SearchRecordEntity> findById(@PathVariable Integer id) {
return R.ok(searchRecordService.findById(id));
}
@GetMapping("save")
public R<String> save(SearchRecordEntity searchRecordEntity) {
searchRecordService.saveSearchRecord(searchRecordEntity);
return R.ok("ok");
}
@GetMapping("remove/{id}")
public R<String> removeById(@PathVariable Integer id) {
searchRecordService.remove(id);
return R.ok("ok");
}
}
4.测试
(1)测试@Cacheable
首次访问http://localhost:8090/esp/search-record/find/7
再次访问 http://localhost:8090/esp/search-record/find/7 无日志打印,数据仍然返回,说了直接查询缓存数据
(2)测试 @CacheEvict
访问:http://localhost:8090/esp/search-record/remove/7 删除了缓存
(3)测试 @CachePut
访问:http://localhost:8090/esp/search-record/save?id=13&name=汽车业务&remark=汽车品牌