一、首先了解spring自带的注解
- 首先在启动类需要开启该功能
package com.frame.util;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;
@SpringBootApplication
@EnableCaching
@MapperScan("com.frame.util.dao")
public class UtilApplication {
public static void main(String[] args) {
SpringApplication.run(UtilApplication.class, args);
}
}
springboot自带注解主要有@Cacheable、@CacheEvict和@CachePut,主要是用AOP代理实现
1.@Cacheable
- @Cacheable可以标记在一个类上或者方法上,当标记在一个方法上时表示该方法是支持缓存的,当标记在一个类上时则表示该类所有的方法都是支持缓存的。对于加上该注解的方法,spring会首先判断该方法相同的参数之前是否有缓存,有的话直接返回,不会走该方法的逻辑;如果没有则需要重新执行该方法,并将方法执行的结果放入缓存。看下面这个例子:
package com.frame.util.service.redis;
import com.frame.util.dao.TbPhoneDao;
import com.frame.util.entity.dataObject.TbPhone;
import com.frame.util.entity.vo.ResultVo;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ObjectUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
/**
* @Author: towards
* @CreateTime: 2022-07-16 11:33
* @Description: 缓存注解
* @Version: 1.0
*/
@Slf4j
@Service
public class CacheAnnotionServiceimpl {
private final String phoneKey = "PHONE_";
@Resource
private TbPhoneDao tbPhoneDao;
@Autowired
private RedisService redisService;
/**
* @description: 通过手机名获取手机
* @author: towards
* @date: 2022/7/16 11:37
* @param: name
* @return: com.frame.util.vo.ResultVo
**/
@Cacheable(cacheNames = "phoneCache",key = "#id")
public <T> ResultVo getPhone(Integer id) throws Exception {
T value = redisService.getKey(keyGenorate(id));
if (ObjectUtils.isNotEmpty(value)){
// 如果缓存中有,直接返回
log.info("query phone from redis");
return ResultVo.success(value);
}
// 缓存中没有,查询数据不为空时,存入缓存
TbPhone tbPhone = tbPhoneDao.queryById(id);
if (!ObjectUtils.isEmpty(tbPhone)){
log.info("query from mysql ,and save to redis");
redisService.setKey(keyGenorate(id), tbPhone);
return ResultVo.success(tbPhone);
}
// 缓存和数据库都没有
throw new Exception("query phone in redis and mysql is not exist");
}
/**
* @description: 根据id生成redis的key
* @author: towards
* @date: 2022/7/16 12:12
* @param: id
* @return: java.lang.String
**/
public String keyGenorate(Integer id){
String redisKey = phoneKey+id;
return redisKey;
}
}
- 当第一次id=1访问getPhone方法,则getPhone方法会执行内部逻辑,并将结果放入名叫phoneCache的缓存中,下次id=1访问该方法,并不会执行方法内部逻辑。
- 有一些属性,需要解释一下,方便不同场景使用:
- cacheNames/value :用来指定缓存组件的名字
- key :缓存数据时使用的 key,可以用它来指定。默认是使用方法参数的值。(这个 key 你可以使用 spEL 表达式来编写)
- keyGenerator :key 的生成器。 key 和 keyGenerator 二选一使用
- cacheManager :可以用来指定缓存管理器。从哪个缓存管理器里面获取缓存。
- condition :可以用来指定符合条件的情况下才缓存
- unless :否定缓存。当 unless 指定的条件为 true ,方法的返回值就不会被缓存。当然你也可以获取到结果进行判断。(通过 #result 获取方法结果)
- sync :是否使用异步模式。
- 缓存key的获取有多样,支持spel表达式,以下介绍几种,引用博客:https://blog.csdn.net/zl1zl2zl3/article/details/110987968
- key可以通过keyGenerator生成
- 自定义KeyGenerator
package com.frame.util.config;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.lang.reflect.Method;
import java.util.Arrays;
/**
* @Author: towards
* @CreateTime: 2022-07-16 16:45
* @Description: TODO
* @Version: 1.0
*/
@Configuration
public class KeyGenerateConfig {
@Bean("myKeyGenerate")
public KeyGenerator keyGenerator(){
return new KeyGenerator() {
@Override
public Object generate(Object target, Method method, Object... params) {
return method.getName()+"["+ Arrays.asList(params).toString()+"]";
}
};
}
}
- 使用keyGenerate作为缓存键
@Cacheable(cacheNames = "phoneCache",keyGenerator = "myKeyGenerate")
public <T> ResultVo getPhone(Integer id) throws Exception {
T value = redisService.getKey(keyGenorate(id));
if (ObjectUtils.isNotEmpty(value)){
// 如果缓存中有,直接返回
log.info("query phone from redis");
return ResultVo.success(value);
}
// 缓存中没有,查询数据不为空时,存入缓存
TbPhone tbPhone = tbPhoneDao.queryById(id);
if (!ObjectUtils.isEmpty(tbPhone)){
log.info("query from mysql ,and save to redis");
redisService.setKey(keyGenorate(id), tbPhone);
return ResultVo.success(tbPhone);
}
// 缓存和数据库都没有
throw new Exception("query phone in redis and mysql is not exist");
}
2.@CachePut
- @CachePut 也可以声明一个方法支持缓存功能。与 @Cacheable 不同的是使用 @CachePut 标注的方法在执行前不会去检查缓存中是否存在之前执行过的结果,而是每次都会执行该方法,并将执行结果以键值对的形式存入指定的缓存中。
- key、value等属性值和@Cacheable相同,key的获取方法也相同
- 一般使用在更新的场景中,需要更新缓存。以下介绍代码实现:
@CachePut(cacheNames = "phoneCache" , keyGenerator = "myKeyGenerate")
public <T> ResultVo getPhone(Integer id) throws Exception {
T value = redisService.getKey(keyGenorate(id));
if (ObjectUtils.isNotEmpty(value)){
// 如果缓存中有,直接返回
log.info("query phone from redis");
return ResultVo.success(value);
}
// 缓存中没有,查询数据不为空时,存入缓存
TbPhone tbPhone = tbPhoneDao.queryById(id);
if (!ObjectUtils.isEmpty(tbPhone)){
log.info("query from mysql ,and save to redis");
redisService.setKey(keyGenorate(id), tbPhone);
return ResultVo.success(tbPhone);
}
// 缓存和数据库都没有
throw new Exception("query phone in redis and mysql is not exist");
}
- 会发现每次都会执行方法逻辑,无论缓存是否存在。
3.@CacheEvict
- allEntries是boolean类型,表示是否需要清除缓存中的所有元素。默认为false,表示不需要。当指定了allEntries为true时,Spring Cache将忽略指定的key。有的时候我们需要Cache一下清除所有的元素,这比一个一个清除元素更有效率。
- 清除操作默认是在对应方法成功执行之后触发的,即方法如果因为抛出异常而未能成功返回时也不会触发清除操作。使用beforeInvocation可以改变触发清除操作的时间,当我们指定该属性值为true时,Spring会在调用该方法之前清除缓存中的指定元素。
4.@Caching
- @Caching注解可以让我们在一个方法或者类上同时指定多个Spring Cache相关的注解。其拥有三个属性:cacheable、put和evict,分别用于指定@Cacheable、@CachePut和@CacheEvict。
@Caching(cacheable = @Cacheable("users"), evict = { @CacheEvict("cache2"),
@CacheEvict(value = "cache3", allEntries = true) })
public User find(Integer id) {
returnnull;
}
5.@CacheConfig
- 用于抽取该类下面的共同配置,以下为源码
package org.springframework.cache.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CacheConfig {
String[] cacheNames() default {};
String keyGenerator() default "";
String cacheManager() default "";
String cacheResolver() default "";
}
- 可以在类上注明,like this
二、以上使用的是Spring自带缓存,可以对cache配置进行修改,改为redis,以下介绍过程
- 配置
package com.frame.util.config;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
import org.springframework.beans.factory.annotation.Autowired;
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.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.*;
import java.time.Duration;
/**
* @Description:
* @date: 2022/5/25 15:21
* @author: towards
* @since JDK 1.8
*/
/**
* 自定义 RedisTemplate
*/
@Configuration
public class RedisConfig {
@Autowired
private LettuceConnectionFactory lettuceConnectionFactory;
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<String, Object>();
template.setConnectionFactory(factory);
Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.activateDefaultTyping(
LaissezFaireSubTypeValidator.instance,
ObjectMapper.DefaultTyping.NON_FINAL,
JsonTypeInfo.As.WRAPPER_ARRAY);
jackson2JsonRedisSerializer.setObjectMapper(om);
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
// key 采用 String 的序列化方式
template.setKeySerializer(stringRedisSerializer);
// hash 的 key 也采用 String 的序列化方式
template.setHashKeySerializer(stringRedisSerializer);
// value 序列化方式采用 jackson
template.setValueSerializer(jackson2JsonRedisSerializer);
// hash 的 value 序列化方式采用 jackson
template.setHashValueSerializer(jackson2JsonRedisSerializer);
template.afterPropertiesSet();
return template;
}
@Bean
public RedisCacheManager redisCacheManager(RedisTemplate redisTemplate){
RedisCacheConfiguration configuration = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofMillis(30000))//设置缓存过期时间
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()));
RedisCacheManager build = RedisCacheManager.builder(lettuceConnectionFactory).cacheDefaults(configuration)
.build();
return build;
}
}
- 使用注解
@Cacheable(cacheNames = "phoneCache",cacheManager = "redisCacheManager",keyGenerator = "myKeyGenerate")
public <T> ResultVo getPhone(Integer id) throws Exception {
T value = redisService.getKey(keyGenorate(id));
if (ObjectUtils.isNotEmpty(value)){
// 如果缓存中有,直接返回
log.info("query phone from redis");
return ResultVo.success(value);
}
// 缓存中没有,查询数据不为空时,存入缓存
TbPhone tbPhone = tbPhoneDao.queryById(id);
if (!ObjectUtils.isEmpty(tbPhone)){
log.info("query from mysql ,and save to redis");
// redisService.setKey(keyGenorate(id), tbPhone);
return ResultVo.success(tbPhone);
}
// 缓存和数据库都没有
throw new Exception("query phone in redis and mysql is not exist");
}
- 查看缓存