目录
缓存
缓存是每一个系统都应该考虑的功能,它用于加速系统的访问,以及提速系统的性能。如:
经常访问的高频热点数据:
- 电商网站的商品信息:每次查询数据库耗时,可以引入缓存
- 微博阅读量、热点话题等
一、Springboot整合缓存
- CacheManager:缓存管理器,用于管理各种Cache缓存组件
- Cache 定义了缓存的各种操作, Spring在Cache接口下提供了各种xxxCache的实现。比如EhCacheCache,RedisCache, ConcurrentMapCache等
Spring 提供了缓存注解: @EnableCaching、@Cacheable、@CachePut
1)整合缓存步骤
1.引入缓存 启动器
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
2.在启动类上,开启基于注解的缓存
@EnableCaching //基于注解的缓存
@MapperScan("com.mxg.springboot.mapper")
@SpringBootApplication
public class SpringBoot12CacheApplication {
public static void main(String[] args) {
SpringApplication.run(SpringBoot12CacheApplication.class, args);
}
}
3.在方法上使用注解@Cacheable
属性:
-
value/cacheNames :缓存的名字
-
key : 作为缓存中的Map的 Key 值,可自已使用 SpEL 表达式指定 ( 不指定就是参数值 ),Map的value是方法的返回值
@Service
public class UserService {
@Autowired
UserMapper userMapper;
/**
* user:自定义缓存名
* key:缓存里的Map的存储的key名,Map的value是方法返回值
*/
@Cacheable(value = "user", key = "#id")//如果id是1,就是获取user缓存中key为1的数据
public User getUserById(Integer id){
return userMapper.getUserById(id);
}
}
4.测试
打印日志,修改日志级别
#修改日志级别为debug
logging.level.com.mxg.springboot.mapper=debug
Controller层
@RestController
public class UserController {
@Autowired
UserService userService;
@RequestMapping("/user/{id}")
public User getUser(@PathVariable("id")Integer id){
return userService.getUserById(id);
}
}
不管访问多少次,控制台可以看到只打印了一条sql语句,说明只访问了一次数据库
2)更新缓存中数据
通过上面获取id为1的user对象后,就会把对象放入缓存中,但是在数据库的数据被更新后,获取缓存的数据还是旧的。
@CachePut:方法被调用后,将更新对应缓存中的数据(先调用方法,调完方法再将结果放到缓存
@CachePut(cacheNames = "user", key = "#result.id")//result表示返回的数据,和#user.id一样
public User update(User user){
userMapper.update(user);
return user; //返回的user会更新到缓存
}
3)删除缓存数据
@CacheEvict:清除缓存
属性:
-
key :指要清除的数据,如 key="#id"
-
allEntries=true : 清除指定的这个缓存中所有数据,默认false
-
beforeInvocation=true : true 在方法之前执行就清除了指定key缓存;默认 false, 在方法之后执行 ,方法体 出现异常则不会清除缓存
/**
* allEntries=true:删除"user"缓存中map的所有数据
*/
@CacheEvict(cacheNames = "user",key = "#id"/**,allEntries=true*/)
public Integer delete(Integer id){
userMapper.delete(id);
return id;
}
4)@CacheConfifig
指定缓存公共属性值,其他方法上就不需要写缓存名
@CacheConfig(cacheNames = "user") //指定公共属性值
@Service
public class UserService {
@Autowired
UserMapper userMapper;
@Cacheable(key = "#id") //省略了cacheNames="user"
public User getUserById(Integer id){
return userMapper.getUserById(id);
}
}
二、底层原理
springboot支持的缓存:
- GenericCacheConfiguration
- JCacheCacheConfiguration
- EhCacheCacheConfiguration
- HazelcastCacheConfiguration
- InfinispanCacheConfiguration
- CouchbaseCacheConfiguration
- RedisCacheConfiguration
- CaffeineCacheConfiguration
- SimpleCacheConfiguration
- NoOpCacheConfiguration
1.默认使用的缓存配置类:SimpleCacheConfiguration
2.SimpleCacheConfiguration使用ConcurrentMapCacheManager缓存管理器
class SimpleCacheConfiguration {
@Bean //注入到spring容器
ConcurrentMapCacheManager cacheManager(CacheProperties cacheProperties,
CacheManagerCustomizers cacheManagerCustomizers) {
ConcurrentMapCacheManager cacheManager = new ConcurrentMapCacheManager();
List<String> cacheNames = cacheProperties.getCacheNames();
if (!cacheNames.isEmpty()) {
cacheManager.setCacheNames(cacheNames);
}
return cacheManagerCustomizers.customize(cacheManager);
}
}
3.当发送第一次请求时,会从ConcurrentMapCacheManager管理器的cacheMap.get(name)中获取有没有ConcurrentMapCache缓存对象,如果没有则创建出来,有则直接返回
public Cache getCache(String name) {
//@Cacheable(cacheNames="user"),name就是"user"缓存的名字
Cache cache = this.cacheMap.get(name);
if (cache == null && this.dynamic) {
synchronized (this.cacheMap) {
cache = this.cacheMap.get(name);
if (cache == null) {
cache = createConcurrentMapCache(name); //创建ConcurrentMapCache
this.cacheMap.put(name, cache);
}
}
}
return cache;
}
4.接着会从ConcurrentMapCache里面调用lookup()获取缓存数据,如果有缓存数据直接响应回去,没有数据则把service方法执行的结果通过put()放入缓存
public class ConcurrentMapCache extends AbstractValueAdaptingCache {
protected Object lookup(Object key) {
//获取缓存数据,key是在@Cacheable(cacheNames="uesr",key="#id")指定的key
return this.store.get(key);
}
//把service方法返回结果放入缓存
public void put(Object key, @Nullable Object value) {
this.store.put(key, toStoreValue(value));
}
}
三、整合Redis
整合redis步骤:
1.引入redis启动器依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
当我们导入了reids的启动器之后 ,springboot会采用redis作为缓存,SimpleCache缓存就不会匹配上了
2.在application全局配置文件配置redis连接地址
#默认就是localhost
spring.redis.host=127.0.0.1
#端口号
spring.redis.port=6379
3.使用RedisTemplate操作redis,参考RedisAutoConfiguration
- StringRedisTemplate:操作字符串的
- RedisTemplate:操作自定义的复杂类型
@SpringBootTest
class SpringBootCacheApplicationTests {
@Autowired
RedisTemplate redisTemplate;
@Autowired
StringRedisTemplate stringRedisTemplate;
@Autowired
UserMapper userMapper;
/**
* 五大数据类型:
* stringRedisTemplate.opsForValue();操作字符串
* stringRedisTemplate.opsForList();操作list
* stringRedisTemplate.opsForHash();操作hash
* stringRedisTemplate.opsForSet();操作set
* stringRedisTemplate.opsForZSet();操作有序set
*/
///操作字符串///
@Test
void stringTest() {
// stringRedisTemplate.opsForValue().set("name","zhangsan");
String name = stringRedisTemplate.opsForValue().get("name");
System.out.println(name);//zhangsan
// stringRedisTemplate.opsForList().leftPushAll("list1","a","b","c");
//获取所有元素
List<String> strings = stringRedisTemplate.opsForList().range("list1",0,-1);
System.out.println(strings);//[c, b, a]
// Map<String,String> map = new HashMap<>();
// map.put("k1","hello");
// stringRedisTemplate.opsForHash().putAll("hash1", map);
String k1 = (String) stringRedisTemplate.opsForHash().get("hash1","k1");
System.out.println(k1);//hello
stringRedisTemplate.opsForSet().add("set1","10","15","13");
System.out.println(stringRedisTemplate.opsForSet().members("set1"));//[10, 13, 15]
}
操作复杂自定义类型//
@Test
void objectTest(){
User user = userMapper.getUserById(1);
//User实体类必须要序列化实现Serializable接口,否则报错
redisTemplate.opsForValue().set("user",user);
System.out.println(redisTemplate.opsForValue().get("user"));
}
}
4.修改redis默认的序列化器为json
redis默认使用jdk序列化器JdkSerializationRedisSerializer不易于我们查看
使用Json序列化器Jackson2JsonRedisSerializer:
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<Object, Object> jsonRedisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
RedisTemplate<Object, Object> template = new RedisTemplate();
template.setDefaultSerializer(new Jackson2JsonRedisSerializer(Object.class));
template.setConnectionFactory(redisConnectionFactory);
return template;
}
}
@SpringBootTest
class SpringBoot12CacheApplicationTests {
@Autowired
RedisTemplate jsonRedisTemplate;
@Test
void objectTest(){
//jsonRedisTemplate.opsForValue().set("user2",userMapper.getUserById(2));
Object o = jsonRedisTemplate.opsForValue().get("user2");
ObjectMapper objectMapper = new ObjectMapper();
//将Object转成User实体类
User user2 = objectMapper.convertValue(o, User.class);
System.out.println(user2);
}
}