系列文章目录
文章目录
前言
伴随信息量的爆炸式增长以及构建的应用系统越来越多样化、复杂化,特别是企业级应用互联网化的趋势,缓存(Cache)对应用程序性能的优化变的越来越重要。 将所需服务请求的数据放在缓存中,既可以提高应用程序的访问效率,又可以减少数据库服务器的压力,从而让用户获得更好的用户体验。
一、spring cache
1、简介
定义了 org.springframework.cache.Cache 和 org.springframework.cache.CacheManager 接口来统一不同的缓存技术,并支持使用 JCache(JSR-107)注解简化我们开发。SpringCache本质上不是一种缓存的实现,而是一种缓存的抽象。通过在既有代码中添加少量它定义的各种 annotation,即能够达到缓存方法的返回对象的效果
2、注解使用
1、@Cacheable
先查询是否已经有缓存,有会使用缓存,没有则会执行方法并缓存。注意:要缓存的实体类必须实现序列化。一般用于查找
2、@CachePut
和 @Cacheable 不同的是,它每次都会触发真实方法的调用 。简单来说就是用户更新缓存数据。
3、@CacheEvict
作用:主要针对方法配置,能够根据一定的条件对缓存进行清空 。一般用于删除和更新。
4、@Caching
@Caching注解可以让我们在一个方法或者类上同时指定多个Spring Cache相关的注解
@Caching(evict={@CacheEvict("user1"), @CacheEvict("user2",allEntries=true)})
5、@CacheConfig
@CacheConfig是一个类级别的注解,它允许共享缓存名称,自定义KeyGenerator,自定义CacheManager和自定义CacheResolver。
当我们需要缓存的地方越来越多,可以在类上使用 @CacheConfig(cacheNames = {“”}) 注解来统一指定 value 的值,这时方法可省略 value,如果你在你的方法依旧写上了 value,那么依然以方法的value值为准
@CacheConfig(cacheNames = {"myCache"})
public class UserServiceImpl implements UserService {
@Override
@Cacheable(key = "targetClass + methodName + #p0")//此处没写value
public List<User> findAllLimit(int num) {
return userRepository.findAllLimit(num);
}
.....
}
- Spring支持的CacheManager,
- SimpleCache:没有引入其他缓存组件的情况下,SpringBoot 的默认缓存,使用ConcurrentMap实现.
3、注意点
- 配置文件中启用缓存:spring.cache.type=redis
- 缓存的对象必须实现Serializable 注解是基于Spring
- AOP代理类,内部方法调用是不走代理的,注解是不起作用的
4、spring cache 优劣
优势
- 支持开箱即用(Out Of The Box),并提供基本的Cache抽象,方便切换各种底层Cache
- 通过Cache注解即可实现缓存逻辑透明化,让开发者关注业务逻辑
- 当事务回滚时,缓存也会自动回滚
- 支持比较复杂的缓存逻辑
- 提供缓存编程的一致性抽象,方便代码维护。
劣势
- Spring Cache并不针对多进程的应用环境进行专门的处理。
- 另外SpringCache抽象的操作中没有锁的概念,当多线程并发操作(更新或者删除)同一个缓存项时,有可能读取到过期的数据。
- 查询是加锁,其他的都是公用的没有加锁。读模式做了处理,写模式并没有管
常规(读多写少,及时性,一致性不高的数据完全可以用SpringCache)
场景问题处理
二、集成springboot
1、maven依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.70</version>
</dependency>
2、 配置文件
redis:
host: 主机ip地址
port: 6379
password: '密码'
jedis:
pool:
max-active: 8
max-wait: -1
max-idle: 500
min-idle: 0
lettuce:
shutdown-timeout: 30000
3、启动类配置
在启动类上添加**@EnableCaching,开始缓存**
4、编写配置类
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.parser.ParserConfig;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.interceptor.CacheErrorHandler;
import org.springframework.cache.interceptor.KeyGenerator;
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.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.serializer.*;
import java.lang.reflect.Method;
import java.nio.charset.Charset;
import java.time.Duration;
@Slf4j
@Configuration
@EnableCaching
@EnableConfigurationProperties(RedisProperties.class)
public class CacheConfig extends CachingConfigurerSupport {
private static final FastJsonRedisSerializer<Object> fastJsonRedisSerializer = new FastJsonRedisSerializer<>(Object.class);
//缓存管理器。可以管理多个缓存
//只有CacheManger才能扫描到cacheable注解
//spring提供了缓存支持Cache接口,实现了很多个缓存类,其中包括RedisCache。但是我们需要对其进行配置,这里就是配置RedisCache
@Bean
public CacheManager cacheManager(RedisConnectionFactory connectionFactory) {
RedisCacheManager cacheManager = RedisCacheManager.RedisCacheManagerBuilder
//Redis链接工厂
.fromConnectionFactory(connectionFactory)
//缓存配置 通用配置 默认存储一小时
.cacheDefaults(getCacheConfigurationWithTtl(Duration.ofHours(1)))
//配置同步修改或删除 put/evict
.transactionAware()
//对于不同的cacheName我们可以设置不同的过期时间
// .withCacheConfiguration("app:",getCacheConfigurationWithTtl(Duration.ofHours(5)))
.withCacheConfiguration("user:",getCacheConfigurationWithTtl(Duration.ofHours(2)))
.build();
return cacheManager;
}
//缓存的基本配置对象
private RedisCacheConfiguration getCacheConfigurationWithTtl(Duration duration) {
return RedisCacheConfiguration
.defaultCacheConfig()
//设置key value的序列化方式
// 设置key为String
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
// 设置value 为自动转Json的Object
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(fastJsonRedisSerializer))
// 不缓存null
.disableCachingNullValues()
// 设置缓存的过期时间
.entryTtl(duration);
}
//缓存的异常处理
@Bean
@Override
public CacheErrorHandler errorHandler() {
// 异常处理,当Redis发生异常时,打印日志,但是程序正常走
log.info("初始化 -> [{}]", "Redis CacheErrorHandler");
return new CacheErrorHandler() {
@Override
public void handleCacheGetError(RuntimeException e, Cache cache, Object key) {
log.error("Redis occur handleCacheGetError:key -> [{}]", key, e);
}
@Override
public void handleCachePutError(RuntimeException e, Cache cache, Object key, Object value) {
log.error("Redis occur handleCachePutError:key -> [{}];value -> [{}]", key, value, e);
}
@Override
public void handleCacheEvictError(RuntimeException e, Cache cache, Object key) {
log.error("Redis occur handleCacheEvictError:key -> [{}]", key, e);
}
@Override
public void handleCacheClearError(RuntimeException e, Cache cache) {
log.error("Redis occur handleCacheClearError:", e);
}
};
}
@Override
@Bean("myKeyGenerator")
public KeyGenerator keyGenerator() {
return new KeyGenerator() {
@Override
public Object generate(Object target, Method method, Object... params) {
StringBuffer sb = new StringBuffer();
sb.append(target.getClass().getName());
sb.append(method.getName());
for (Object obj : params) {
sb.append(obj.toString());
}
return sb.toString();
}
};
}
}
5、编写代码
@Service
public class UserImpl implements UserService {
@Resource
private UsersMapper usersMapper;
@Override
public User queryNo(Integer userId) {
System.out.println("进入无缓存方法");
User user = usersMapper.queryUser(userId);
return user;
}
/**
* 查找缓存
* @param userId userID
* @return user
*/
@Override
@Cacheable(cacheNames ="user",key = "#userId)
public User query(Integer userId) {
System.out.println("进入有缓存方法");
return usersMapper.queryUser(userId);
}
}