Spring的缓存

Spring的缓存

Java在使用Cache的时候,为了统一缓存的使用,J2EE发布了JSR107缓存规范。包括了主要的5个核心接口,包括cachingProvidercacheManager等。具体可以查看JSR107缓存规。

Spring的缓存抽象

Spring为了统一缓存的操作,使用org.springframework.cache包下的的cacheManager接口进行开发。并支持上面所说的JSR107缓存规范,用来简化开发。

Spring支持透明的向应用中的方法添加缓存。将查询出的信息放到缓存里,减少应用与实际数据库的查询次数。提高系统相应速度。

SpringBoot开启缓存注解

SpringBoot是默认支持Cache相关基础组件的。需要在启动器类上标注@EnableCaching注解。如:

1
2
3
4
5
6
7
@SpringBootApplication
@EnableCaching //开启SpringBoot的cache支持
public class WebProjectApplication {
public static void main(String[] args) {
SpringApplication.run(WebProjectApplication.class);
}
}

添加缓存,只需要在方法上添加注解,Spring cache 就能够帮助创建缓存。如:

1
2
3
4
5
@Cacheable(value="user-status") //使用Cacheable注解标志这需要对方法进行缓存
@Override
public List<Status> findNotDeletedUserStatus() throws Exception {
return statusRepository.findAllByIsDeletedOrderByOrd(DeletedStatusEnum.IS_NOT_DELETED.isJudge());
}

SpringBoot支持了多种的缓存实现,如EhCache 2.xRedisCaffeine等等。但是接口方法是相同的,就如同使用jdbc一样。虽然实现不同,但是使用上并无差异。

SpringBoot 使用缓存注解

缓存抽象的核心是将缓存应用于Java方法上。这样,如果方法被调用,那么应用于方法上的缓存就开始检查是否按照给定规则进行缓存。

基于注解的缓存

对于缓存声明,Spring的缓存抽象提供了一组Java注解

一、@Cacheable 触发进行缓存

使用@Cacheable注解在方法上,标志着这个方法是可缓存的。使用这个注解,可将方法的返回结构存储在缓存中,再次查询是,就可以从缓存中取结果而不是再次执行查询操作。如下面的例子:

1
2
@Cacheable("books")
public Book findBook(ISBN isbn) {...}

因为缓存是key-value形式的,每次调用缓存时,都需要先找到正确的key。缓存抽象支持简单的KeyGenerator几种生成的策略:作为参考上面的方法findBookCacheable的参数是books

  • 如果没有参数 ,则使用SimpleKey.EMPTY.
  • 如果有一个参数,则使用这个参数作为key。
  • 如果有多个参数,则返回 SimpleKey 中包含这多个参数。

这三种方式包含了大多数的使用场景。如果需要自定义key ,@Cacheable 允许使用key关键字进行key的生成,key关键字的值可以使用SPEL表达式进行表示。如Spring Doc中所举出的例子一样。根据实际的情况进行斟酌。

1
2
3
4
5
6
7
8
@Cacheable(cacheNames="books", key="#isbn")
public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed)

@Cacheable(cacheNames="books", key="#isbn.rawNumber")
public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed)

@Cacheable(cacheNames="books", key="T(someType).hash(#isbn)")
public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed)

自定义的缓存条件 : 可以通过condition关键字在@Cacheable注解中进行条件的定制。如:

1
2
3
4
5
@Cacheable(cacheNames="book", condition="#name.length() < 32") 
public Book findBook(String name)
//如果想排除某些条件 使用unless
@Cacheable(cacheNames="book", condition="#name.length() < 32", unless="#result.hardback")
public Book findBook(String name)

具体的更多的spel注解属性可以查看Spring文档中的内容 地址

二、@CacheEvict 触发清空缓存

Spring cache中不仅允许放置缓存,而且允许移除缓存。使用与Cacheable相对的CacheEvict注解来清空对应 key的缓存数据。

1
2
3
// 使用allEntries用来表示删除所有这个cache中的缓存
@CacheEvict(cacheNames="books", allEntries=true)
public void loadBooks(InputStream batch)

三、@CachePut 触发更新缓存

当需要更新缓存而不干扰方法执行时,就是说每次调用方法时,都会更新缓存。

1
2
@CachePut(cacheNames="book", key="#isbn")
public Book updateBook(ISBN isbn, BookDescriptor descriptor)

四、@Caching 组合使用多个cache注解方法

如果想使用多个cache动作来标注在方法上,就需要使用Caching注解如下面的例子中所表示的一样,想要清空多个key的缓存

1
2
@Caching(evict = { @CacheEvict("primary"), @CacheEvict(cacheNames="secondary", key="#p0") })
public Book importBooks(String deposit, Date date)

五、@CacheConfig 类上注解用来注解公共配置

上面的注解都是在方法级别上的对方法的返回值进行设置,而CacheConfig是对类级别上的公共设置,如下面的例子中所展示的一样,设置公共的cache name.

1
2
3
4
5
6
@CacheConfig("books") 
public class BookRepositoryImpl implements BookRepository {

@Cacheable
public Book findBook(ISBN isbn) {...}
}

SpringBoot使用redis进行缓存

一、添加Pom依赖

1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

二、配置redis相关属性

在引入spring-boot-starter-data-redis后,启动主程序后会自动的开启redis缓存。如果想配置redis,则可以在org.springframework.boot.autoconfigure.data.redis.RedisProperties这个属性类中查找,然后添加到application.yml中。如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
private int database = 0;
/**
* Connection URL. Overrides host, port, and password. User is ignored. Example:
* redis://user:password@example.com:6379
*/
private String url;

private String host = "localhost";

private String password;

private int port = 6379;

private boolean ssl;

private Duration timeout;

三、自定义redisTemplate和TtlCacheManager

根据SpringBoot自动配置源码,redisTemplate只有在环境中没有以redisTemplate名称命名的bean时候,才进行自动配置。

1
2
3
4
5
6
7
8
@Bean
@ConditionalOnMissingBean(name = "redisTemplate")
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory)
throws UnknownHostException {
RedisTemplate<Object, Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory);
return template;
}

所以,如果想自行配置redisTemplate则可直接装载bean,命名为redisTemplate。如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
@Bean
public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<Object, Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory);
//Jackson2JsonRedisSerializer serializer = new Jackson2JsonRedisSerializer(Object.class);
//使用Fastjson2JsonRedisSerializer来序列化和反序列化redis的value值
FastJson2JsonRedisSerializer serializer = new FastJson2JsonRedisSerializer(Object.class);

ObjectMapper mapper = new ObjectMapper();
mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
serializer.setObjectMapper(mapper);

template.setHashKeySerializer(serializer);
template.setHashValueSerializer(serializer);
template.setDefaultSerializer(serializer);
//使用StringRedisSerializer来序列化和反序列化redis的key值,防止乱码
template.setKeySerializer(new StringRedisSerializer());
template.setValueSerializer(new StringRedisSerializer());
template.afterPropertiesSet();
return template;
}
// 使用 @Cacheable(cacheManager="TtlCacheManager") 激活cacheManager
@Bean(name = "TtlCacheManager")
public CacheManager cacheManager(RedisConnectionFactory connectionFactory) {

//初始化一个RedisCacheWriter
RedisCacheWriter redisCacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(connectionFactory);

RedisCacheConfiguration defaultCacheConfig = RedisCacheConfiguration.defaultCacheConfig();
//设置默认超过期时间是30秒
defaultCacheConfig.entryTtl(Duration.ofSeconds(30));

//初始化RedisCacheManager
RedisCacheManager cacheManager = new RedisCacheManager(redisCacheWriter, defaultCacheConfig);
return cacheManager;
}

这里是用来FastJson2JsonRedisSerializer来代替默认的Jackson2JsonRedisSerializer序列化策略。是在网上查找到的方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
public class FastJson2JsonRedisSerializer<T> implements RedisSerializer<T> {
private ObjectMapper objectMapper = new ObjectMapper();
public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");
private Class<T> clazz;
static {
ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
//如果遇到反序列化autoType is not support错误,请添加并修改一下包名到bean文件路径
// ParserConfig.getGlobalInstance().addAccept("com.xxxxx.xxx");
}
public FastJson2JsonRedisSerializer(Class<T> clazz) {
super();
this.clazz = clazz;
}
// 序列化
@Override
public byte[] serialize(T t) throws SerializationException {
if (t == null) {
return new byte[0];
}
return JSON.toJSONString(t, SerializerFeature.WriteClassName).getBytes(DEFAULT_CHARSET);
}
// 反序列化
@Override
public T deserialize(byte[] bytes) throws SerializationException {
if (bytes == null || bytes.length <= 0) {
return null;
}
String str = new String(bytes, DEFAULT_CHARSET);
return JSON.parseObject(str, clazz);
}
public void setObjectMapper(ObjectMapper objectMapper) {
Assert.notNull(objectMapper, "'objectMapper' must not be null");
this.objectMapper = objectMapper;
}

protected JavaType getJavaType(Class<?> clazz) {
return TypeFactory.defaultInstance().constructType(clazz);
}
}
-------------本文结束感谢您的阅读-------------

本文标题:Spring的缓存

文章作者:NanYin

发布时间:2019年08月15日 - 12:08

最后更新:2019年08月16日 - 09:08

原始链接:https://nanyiniu.github.io/2019/08/15/2019-08-15-Spring%E7%9A%84%E7%BC%93%E5%AD%98/

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。