SpringCache的使用
在目前的项目中, 缓存的使用越来越多,适用的场景也越来越广.对于缓存,Spring提供的Spring Cache框架,能够非常方便地使用缓存.
1 SpringCache的简介
缓存,就是将数据从数据库等数据来源获取数据,将数据缓存在内存或其他设备如Redis中,为了二次查询能够快速高效的响应结果.
Spring Cache是3.1开始提供, 通过注解的形式,对于整合业务代码友好.
Spring Cache特点:
- 提供Cache通用入口 ,方便多种实现切换缓存源,如Redis,Guava Cache等
- 支持事务, 即食物回滚时,缓存同时自动回滚
Cache源码说明:
/**
* Interface that defines common cache operations.
*
* <b>Note:</b> Due to the generic use of caching, it is recommended that
* implementations allow storage of <tt>null</tt> values (for example to
* cache methods that return {@code null}).
*
* @author Costin Leau
* @author Juergen Hoeller
* @author Stephane Nicoll
* @since 3.1
*/
// 定义公共缓存操作的接口
public interface Cache {
/**
* Return the cache name.
*/
// 缓存的名称
String getName();
/**
* Return the underlying native cache provider.
*/
// 得到底层的缓存 如Ehcache
Object getNativeCache();
// 根据可以得到ValueWrapper对象,再给句get方法获取值
@Nullable
ValueWrapper get(Object key);
// 根据key,和value的类型获取value
@Nullable
<T> T get(Object key, @Nullable Class<T> type);
// 添加缓存
void put(Object key, @Nullable Object value);
// 存在key则获取,不存在则添加
@Nullable
default ValueWrapper putIfAbsent(Object key, @Nullable Object value) {
ValueWrapper existingValue = get(key);
if (existingValue == null) {
put(key, value);
}
return existingValue;
}
// 根据key清除对应缓存
void evict(Object key);
// 如key存在,则清除对应缓存
default boolean evictIfPresent(Object key) {
evict(key);
return false;
}
// 清空缓存
void clear();
// 缓存值的ValueWrapper对象
@FunctionalInterface
interface ValueWrapper {
@Nullable
Object get();
}
}
Cache接口提供的默认实现类有等:
- AbstractValueAdaptingCache 抽象类
- ConcurrentMapCache 基于java.util.concurrent.ConcurrentHashMap的缓存
- JCacheCache 对javax.cache.Cache的实例
- CaffeineCache 对com.github.benmanes.caffeine.cache.Cache的实例
- RedisCache 基于Redis的缓存实现类
Spring中提供的缓存管理的类
CacheManager接口:
/**
* Spring's central cache manager SPI.
*
* <p>Allows for retrieving named {@link Cache} regions.
*
* @author Costin Leau
* @author Sam Brannen
* @since 3.1
*/
public interface CacheManager {
/**
* Get the cache associated with the given name.
* <p>Note that the cache may be lazily created at runtime if the
* native provider supports it.
* @param name the cache identifier (must not be {@code null})
* @return the associated cache, or {@code null} if such a cache
* does not exist or could be not created
*/
@Nullable
// 根据缓存名字获取缓存
Cache getCache(String name);
/**
* Get a collection of the cache names known by this manager.
* @return the names of all caches known by the cache manager
*/
// 得到所有缓存的名字
Collection<String> getCacheNames();
}
CacheManager接口提供的默认实现类有等:
- AbstractCacheManager 抽象类
- ConcurrentMapCacheManager 对应 ConcurrentMapCacheFactoryBean
- EhCacheCacheManager 对应 EhCacheManagerFactoryBean
- JCacheCacheManager 对应 EhCacheManagerFactoryBean
- RedisCacheManager 基于Redis的缓存管理
- CompositeCacheManager 组合管理CacheManager的实现类
KeyGenerator接口
接口提供了一套生成缓存默认key的策略.
/**
* Cache key generator. Used for creating a key based on the given method
* (used as context) and its parameters.
*
* @author Costin Leau
* @author Chris Beams
* @author Phillip Webb
* @since 3.1
*/
@FunctionalInterface
public interface KeyGenerator {
/**
* Generate a key for the given method and its parameters.
* @param target the target instance
* @param method the method being called
* @param params the method parameters (with any var-args expanded)
* @return a generated key
*/
Object generate(Object target, Method method, Object... params);
}
KeyGenerator接口提供的默认实现类有等:
-
DefaultKeyGenerator Spring3.1版本默认key生成实现类, 已过期,从Spring4.0开始, 使用SimpleKeyGenerator来代替
-
SimpleKeyGenerator Spring4.0开始,默认的实现类
-
KeyGeneratorAdapter key生成相关参数适配
2 SpringCache的使用
1 注解说明
@Cacheable
使用场景: 查询方法接口
该注解会把方法的返回值缓存下来, 下一次调用方法时, 先查询缓存中是否存在,存在则直接返回,不存在,则查询数据返回,并将数据缓存.
@Target({
ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Cacheable {
/**
* Alias for {@link #cacheNames}.
*/
@AliasFor("cacheNames")
// 缓存的名称,数据可以写入多个缓存
String[] value() default {
};
/**
* Names of the caches in which method invocation results are stored.
* <p>Names may be used to determine the target cache (or caches), matching
* the qualifier value or bean name of a specific bean definition.
* @since 4.2
* @see #value
* @see CacheConfig#cacheNames
*/
@AliasFor("value")
// 缓存的名称,数据可以写入多个缓存
String[] cacheNames() default {
};
/**
* Spring Expression Language (SpEL) expression for computing the key dynamically.
*/
// 缓存key, 默认使用KeyGenerator生成
String key() default "";
/**
* The bean name of the custom {@link org.springframework.cache.interceptor.KeyGenerator}
* to use.
* <p>Mutually exclusive with the {@link #key} attribute.
* @see CacheConfig#keyGenerator
*/
// key生成器
String keyGenerator() default "";
/**
* The bean name of the custom {@link org.springframework.cache.CacheManager} to use to
*/
// 缓存管理
String cacheManager() default "";
/**
* The bean name of the custom {@link org.springframework.cache.interceptor.CacheResolver}
* to use.
* @see CacheConfig#cacheResolver
*/
// 缓存解析
String cacheResolver() default "";
/**
* Spring Expression Language (SpEL) expression used for making the method
* caching conditional.
* </ul>
*/
// 满足condition条件,才缓存数据 (在方法执行前后都判断)
String condition() default "";
/**
* Spring Expression Language (SpEL) expression used to veto method caching.
* <p>Unlike {@link #condition}, this expression is evaluated after the method
* has been called and can therefore refer to the {@code result}.
* @since 3.2
*/
// 否决缓存更新 (方法执行后判断)
String unless() default "";
/**
* Synchronize the invocation of the underlying method if several threads are
* attempting to load a value for the same key. The synchronization leads to
*/
// 如果多个线程试图为同一个键加载一个值,则同步底层方法的调用。
boolean sync() default false;
}
@CachePut
使用场景: 新增或修改方法
该注解会把方法的返回值Put到缓存中, 供其他查询使用.
@Target({
ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface CachePut {
/**
* Alias for {@link #cacheNames}.
*/
@AliasFor("cacheNames")
String[] value() default {
};
/**
* Names of the caches to use for the cache put operation.
* <p>Names may be used to determine the target cache (or caches), matching
* the qualifier value or bean name of a specific bean definition.
* @since 4.2
* @see #value
* @see CacheConfig#cacheNames
*/
@AliasFor("value")
String[] cacheNames() default {
};
/**
* Spring Expression Language (SpEL) expression for computing the key dynamically.
* <p>Default is {@code ""}, meaning all method parameters are considered as a key,
*/
String key() default "";
/**
* The bean name of the custom {@link org.springframework.cache.interceptor.KeyGenerator}
* to use.
* <p>Mutually exclusive with the {@link #key} attribute.
* @see CacheConfig#keyGenerator
*/
String keyGenerator() default "";
/**
* The bean name of the custom {@link org.springframework.cache.CacheManager} to use to
* create a default {@link org.springframework.cache.interceptor.CacheResolver} if none
* is set already.
* <p>Mutually exclusive with the {@link #cacheResolver} attribute.
* @see org.springframework.cache.interceptor.SimpleCacheResolver
* @see CacheConfig#cacheManager
*/
String cacheManager() default "";
/**
* The bean name of the custom {@link org.springframework.cache.interceptor.CacheResolver}
* to use.
* @see CacheConfig#cacheResolver
*/
String cacheResolver() default "";
/**
* Spring Expression Language (SpEL) expression used for making the cache
* put operation conditional.
*/
String condition() default "";
/**
* Spring Expression Language (SpEL) expression used to veto the cache put operation.
*/
String unless() default "";
}
@CacheEvict
使用场景: 删除或修改方法
该注解会清空指定缓存.
@Target({
ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface CacheEvict {
/**
* Alias for {@link #cacheNames}.
*/
@AliasFor("cacheNames")
String[] value() default {
};
/**
* Names of the caches to use for the cache eviction operation.
* <p>Names may be used to determine the target cache (or caches), matching
* the qualifier value or bean name of a specific bean definition.
* @since 4.2
* @see #value
* @see CacheConfig#cacheNames
*/
@AliasFor("value")
String[] cacheNames() default {
};
/**
* Spring Expression Language (SpEL) expression for computing the key dynamically.
*/
String key() default "";
/**
* The bean name of the custom {@link org.springframework.cache.interceptor.KeyGenerator}
* to use.
* <p>Mutually exclusive with the {@link #key} attribute.
* @see CacheConfig#keyGenerator
*/
String keyGenerator() default "";
/**
* The bean name of the custom {@link org.springframework.cache.CacheManager} to use to
*/
String cacheManager() default "";
/**
* The bean name of the custom {@link org.springframework.cache.interceptor.CacheResolver}
* to use.
* @see CacheConfig#cacheResolver
*/
String cacheResolver() default "";
/**
* Spring Expression Language (SpEL) expression used for making the cache
* eviction operation conditional.
*/
String condition() default "";
/**
* Whether all the entries inside the cache(s) are removed.
* <p>By default, only the value under the associated key is removed.
* <p>Note that setting this parameter to {@code true} and specifying a
* {@link #key} is not allowed.
*/
// 是否移除所有数据
boolean allEntries() default false;
/**
* Whether the eviction should occur before the method is invoked.
* <p>Setting this attribute to {@code true}, causes the eviction to
* occur irrespective of the method outcome (i.e., whether it threw an
* exception or not).
*/
boolean beforeInvocation() default false;
}
@Caching
使用场景: 同时操作多个缓存
该注解是一个组合缓存,可以同时添加几个不同的注解,供不同的应用场景使用.如一个接口,可能同时操作多个缓存,且缓存的处理状态都不一样.
@Target({
ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Caching {
Cacheable[] cacheable() default {
};
CachePut[] put() default {
};
CacheEvict[] evict() default {
};
}
@CacheConfig
该注解用于配置该类中用的一些共用的缓存配置
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CacheConfig {
/**
* Names of the default caches to consider for caching operations defined
* in the annotated class.
*/
String[] cacheNames() default {
};
/**
* The bean name of the default {@link org.springframework.cache.interceptor.KeyGenerator} to
*/
String keyGenerator() default "";
/**
* The bean name of the custom {@link org.springframework.cache.CacheManager} to use to
*/
String cacheManager() default "";
/**
* The bean name of the custom {@link org.springframework.cache.interceptor.CacheResolver} to use.
*/
String cacheResolver() default "";
}
关于自定义key值的定义策略: 可以使用Spring的EL表达式来指定key
1 直接使用 “#参数名”或者“#p参数index”
@Cacheable(value = "user", key = "#id")
public Object select(String id) {
return null;
}
@Cacheable(value="user", key="#p0")
public Object select(String id) {
return null;
}
@Cacheable(value="user", key="#user.id")
public Object select(User user) {
return null;
}
@Cacheable(value="user", key="#p0.id")
public Object select(User user) {
return null;
}
2 使用root对象来生成key
属性名称 | 描述 | 示例 |
---|---|---|
methodName | 当前方法名 | #root.methodName |
method | 当前方法 | #root.method.name |
target | 当前被调用的对象 | #root.target |
targetClass | 当前被调用的对象的class | #root.targetClass |
args | 当前方法参数组成的数组 | #root.args[0] |
caches | 当前被调用的方法使用的Cache | #root.caches[0].name |
2 案例
1 准备一个SpringBoot环境
2 添加maven依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
3 添加一个Service类
@Slf4j
@Service
public class UserService {
@Autowired
private UserMapper userMapper;
// 先从缓存中读取,如果没有再调用方法获取数据,然后把数据添加到缓存中
@Cacheable(value = "user", key = "#id")
public User select(String id) {
log.info("=====select查询数据库啦=====");
List<User> users = userMapper.selectAll();
for (User user : users) {
if (user.getId().equals(id)) {
return user;
}
}
return null;
}
// 移除对应的缓存
@CacheEvict(value = "user", key = "#user.id")
public User update(User user) {
log.info("=====update查询数据库啦=====");
userMapper.updateByPrimaryKey(user);
return user;
}
// 查询数据库,将查询结果放到缓存
@CachePut(value = "user", key = "#user.id")
public User save(User user) {
log.info("=====save查询数据库啦=====");
userMapper.insert(user);
return user;
}
}
4 添加一个测试类
@RunWith(SpringRunner.class)
// 指定启动类
@SpringBootTest(classes = Application.class)
@Slf4j
public class DemoApplicationTests {
@Autowired
private UserService userService;
@Test
public void testCache() {
String id = "8";
User user = userService.save(new User(id, "李白"));
log.info("[ save方法 - {} ]",user);
User user1 = userService.select(id);
log.info("[ select 方法 - {} ]",user1);
userService.update(user);
User user2 = userService.select(id);
log.info("[ select 方法 - {} ]",user2);
User user3 = userService.select(id);
log.info("[ select 方法 - {} ]",user3);
}
}
/* 运行结果:
=====save查询数据库啦=====
[ save方法 - User(id=8, username=李白, queryTime=Fri Nov 12 15:41:33 CST 2021) ]
[ select 方法 - User(id=8, username=李白, queryTime=Fri Nov 12 15:41:33 CST 2021) ]
=====update查询数据库啦=====
=====select查询数据库啦=====
[ select 方法 - User(id=8, username=李白, queryTime=Fri Nov 12 15:41:34 CST 2021) ]
[ select 方法 - User(id=8, username=李白, queryTime=Fri Nov 12 15:41:34 CST 2021) ]
*/
从上面运行结果可知, 当调用save方法时, 将id为8的用户数据给缓存到了user缓存中, 第一次查询select方法时, 因缓存中存在,所以直接从缓存中获取, 当执行了update方法, 缓存中数据被删除了, 第二次查询select方法时, 因为缓存中没有,所以select方法中查询数据库,并将数据缓存起来了. 第三次调用select方法时,因为缓存中又存在数据了,所以直接返回.
3 SpringCache整合Redis
1 添加Redis的maven依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
2 添加Redis的配置信息
#缓存类型
spring.cache.type=redis
#毫秒为单位 1h = 1*60*60*1000ms
spring.cache.redis.time-to-live=3600000
#如果指定了前缀就用我们指定的前缀CACHE_,没有默认就用缓存的名字作为前缀
spring.cache.redis.key-prefix=CACHE_
#允许使用前缀
spring.cache.redis.use-key-prefix=true
#是否缓存空值,防止缓存穿透
spring.cache.redis.cache-null-values=true
3 添加配置类
@EnableConfigurationProperties(CacheProperties.class)
@Configuration
//@EnableCaching // 开启缓存功能 启动类上添加, 此处可省略
public class MyCacheConfig {
@Bean
public RedisCacheConfiguration redisCacheConfiguration(CacheProperties cacheProperties) {
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig();
config = config.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()));
config = config.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()));
CacheProperties.Redis redisProperties = cacheProperties.getRedis();
//将配置文件中的所有配置都生效
if (redisProperties.getTimeToLive() != null) {
config = config.entryTtl((redisProperties.getTimeToLive()));
}
if (redisProperties.getKeyPrefix() != null) {
config = config.prefixCacheNameWith((redisProperties.getKeyPrefix()));
}
if (!redisProperties.isCacheNullValues()) {
config = config.disableCachingNullValues();
}
if (!redisProperties.isUseKeyPrefix()) {
config = config.disableKeyPrefix();
}
return config;
}
}
4 使用上面测试类
@RunWith(SpringRunner.class)
// 指定启动类
@SpringBootTest(classes = Application.class)
@Slf4j
public class DemoApplicationTests {
@Autowired
private UserService userService;
@Test
public void testCache() {
String id = "9";
User user = userService.save(new User(id, "李白"));
log.info("[ save方法 - {} ]",user);
User user1 = userService.select(id);
log.info("[ select 方法 - {} ]",user1);
userService.update(user);
User user2 = userService.select(id);
log.info("[ select 方法 - {} ]",user2);
User user3 = userService.select(id);
log.info("[ select 方法 - {} ]",user3);
}
}
使用Redis连接工具查看Redis数据库:
/*
CACHE_user::9
{
"@class": "com.cf.demo.pojo.User",
"id": "9",
"username": "李白",
"queryTime": [
"java.util.Date",
1636704213634
]
}
*/
参考资料:
https://www.jianshu.com/p/33c019de9115
https://www.cnblogs.com/fashflying/p/6908028.html
https://blog.csdn.net/yiyihuazi/article/details/109065327