前序:Spring Cache对Cache进行了抽象,提供了@Cacheable、@CachePut、@CacheEvict等注解。可用于大型系统或者分布式系统。应用系统需要通过Cache来缓存不经常改变的数据已提高系统性能,减少跟数据库的交互来增加系统的吞吐量。
一、Spring Boot Cache
Spring Boot本身提供了一个基于ConcurrentHashMap的缓存机制,也集成了ExCache2.x、JCache(JSR-107、EhCache3.x、Hazelcast、Infunispan),还有Couchbase、Redis等。Spring Boot应用通过注解的方式使用同一缓存,只需再方法上使用缓存注解即可,其缓存的具体实现依赖于选择的目标缓存管理器,使用@Cacheble
@Service("cacheServiceImpl")
public class CacheServiceImpl implements CacheService {
@Override
@Cacheable("user")
public UserDO getUser(Integer id) throws Exception {
return null;
}
}
CacheService 作为一个容器管理Bean,Spring将生成代理类,在实际调用CacheService.getUser()方法前,会调用缓存管理器,取名为“user”的缓存,此时,缓存项的Key就是方法参数Id,如果缓存命中,则返回此值,如果没有找到,则进入实际的CacheService.getUser()方法。在返回调用结果给调用之前,还会将查询结果缓存以备下次使用。
集成Spring Cache
<!-- Spring Cache缓存 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
要是使用Spring自带的内存缓存管理器,需要再application.properties中配置属性
spring.cache.type=Simple
Simple只适合单体应用或者开发环境,再或者是一个小系统。
需要使用注解@EnableCaching打开缓存功能。
@SpringBootApplication
@EnableCaching
public class ElectricityApplication {
public static void main(String[] args) {
SpringApplication.run(ElectricityApplication.class, args);
}
/**
* 跨域
*/
@Bean
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurerAdapter() {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**").allowedOrigins("*");
}
};
}
}
一旦配置好Spring Boot缓存,就可以在Spring管理的Bean中使用缓存注解,通常可以直接放在Service类上
- @Cacheable,作用在方法上,触发缓存读取操作。
- @CacheEvivt,作用在方法上,触发缓存失效操作。
- @CachePut,作用在方法上,触发缓存更新操作。
- @Cache,作用在方法上,综合上面的各种操作,在有些场景下,调用业务会触发多种缓存操作。
- @CacheConfig,在类上设置当前缓存的一些公共设置。
1.@Cacheable
注解Cacheable声明了方法的结果是缓存的,如果缓存存在,则目标方法不被调用,直接取出缓存,可以为方法声明多个缓存,如果至少有一个缓存项,则其缓存项将被返回
在这里我们先打印一下数据库com.xiaofeixia.lcc.dao这里是自己写的Mysql路径
logging.level.com.xiaofeixia.lcc.dao=debug
@RestController
public class CacheController {
@Autowired
private CacheService cacheService;
@GetMapping("/cache/{id}")
public UserDO cache(
@PathVariable Integer id
) throws Exception{
UserDO userDO = cacheService.getUser(id);
return userDO;
}
}
注意:对于不同的缓存实现,要将对象序列化,这样无缝切换到分布式缓存系统
public class UserDO implements Serializable {...........}
访问接口http://localhost:8080/cache/2
第一次请求,这里显示访问了数据库,和前台得到的数据。
第二次访问 我们看到日志没有任何打印东西,页面还是显示这样数据,是直接读取的缓存,没有操作数据库
2.Key生成器
缓存的Key非常重要,Spring使用了KeyGenerator类来根据方法参数生成Key,有以下规则。
如果只有一个参数,这个参数就是Key
@Override
@Cacheable("user")
public UserDO getUser(Integer id) throws Exception {
return userDAO.getUserById(id);
}
要是没有参数,则返回SimpleKey.EMPTY,比如构造系统的菜单树,因为系统只有唯一的一个菜单树,所以不用指定参数,Key值是SimpleKey.EMPTY。
@Override
@Cacheable("user")
public UserDO getUser( ) throws Exception {
. . . . . . . . . . . . . . . . . . . .
}
要是有多个参数,则返回包含多个参数的SimpleKey。
@Override
@Cacheable("user")
public UserDO getUser(Integer Id, Integer shopId,Integer goodId) throws Exception {
. . . . . . . . . . . . . . . . . . . .
}
Spring使用SimpleKeyGenerator来实现上述Key的生成:
public class SimpleKeyGenerator implements KeyGenerator {
@Override
public Object generate(Object target, Method method, Object... params) {
return generateKey(params);
}
/**
* Generate a key based on the specified parameters.
*/
public static Object generateKey(Object... params) {
if (params.length == 0) {
return SimpleKey.EMPTY;
}
if (params.length == 1) {
Object param = params[0];
if (param != null && !param.getClass().isArray()) {
return param;
}
}
return new SimpleKey(params);
}
}
也可以自定义KeyGenerator方法。
@Component("myKeyGenerator")
public class MyKeyGenerator implements KeyGenerator {
@Override
public Object generate(Object target, Method method, Object... params) {
//返回后缀名
//return method.getName();
//注意,这里不能返回null,否则会报错
return "";
}
}
注解使用
@Cacheable(value = "listAll", keyGenerator = "myKeyGenerator")
也可以通过条件缓存,比如id大于100的都需要缓存
@Override
@Cacheable(cacheNames = "user",condition = "#id>100")
public UserDO getUser(Integer id) throws Exception {
return userDAO.getUserById(id);
}
返回值status为0,不缓存
@Override
@Cacheable(cacheNames = "user",condition = "#result.status==0")
public UserDO getUser(Integer id) throws Exception {
return userDAO.getUserById(id);
}
3.@CachePut
注解CachePut总是会执行的方法体,并且使用返回的结果来更新缓存,同Cacheable一样,支持condition、unless、key选项,也支持KeyGenerator。
@Override
@CachePut(cacheNames="user")
public Integer updateUser(Integer id) throws Exception {
return null;
}
更新完之后,再次请求查询接口,就会再次请求数据库
4.@CacheEvict
注解CacheEvict用于删除缓存项或者清空缓存,CacheEvict可以指定多个缓存名字来清空多个缓存
@Override
@CachePut(cacheNames="user",key=“#id”)
public Integer updateUser(Integer id) throws Exception {
return null;
}
清空“user”缓存中键值为id的缓存项。比如一个缓存用不到了,就删掉吧。
5.@Caching
注解Caching可以混合以上的各种注解,可以在Caching标签中混合@Cacheable、@CachePut、@CacheEvict。比如一个修改同时需要失效对应的用户缓存和用户扩展信息缓存。
@Override
@Caching(evict = {@CacheEvict(cacheNames = "user"),@CacheEvict(cacheNames = "test")})
@CachePut(cacheNames="user")
public Integer updateUser(Integer id) throws Exception {
return null;
}
6.@CacheConfig
到目前为止,所有的Cache注解都需要提供Cache名称,如果每个Service方法上都包含Cache名称,可能写起来重复。注解CacheConfig作用于类上,可以为此类的方法的缓存注解提供默认值
- 缓存的默认名称
- 缓存的KeyGenerator
@Service("cacheServiceImpl")
@CacheConfig(cacheNames = "user")
public class CacheServiceImpl implements CacheService {
@Autowired
private UserDAO userDAO;
@Override
@Cacheable
public UserDO getUser(Integer id) throws Exception {
return userDAO.getUserById(id);
}
@Override
@CachePut
public Integer updateUser(Integer id) throws Exception {
// TODO 自动生成的方法存根
return null;
}
}