搭建基本环境
-
导入数据文件。创建department和employee表
-
创建JavaBean封装数据
-
整合Mybatis操作数据库
- 配置数据源信息
- 使用注解版的Mybatis
- @MapperScan指定需要扫描的mapper接口所在的包
-
开启缓存
- 开启基于注解的缓存
- 标注缓存注解即可
@SpringBootApplication @MapperScan("com.whut.mapper") @EnableCaching public class SpringBootCacheApplication { public static void main(String[] args) { SpringApplication.run(SpringBootCacheApplication.class, args); } }
一级缓存
将方法的运行结果进行缓存,以后要相同的数据,直接从缓存中获取,不用调用方法
-
CacheManager管理多个Cache组件的,对缓存的真正CRUD操作在Cache组件中,每一个缓存组件有自己唯一一个名字
-
几个属性
-
cacheNames/value:指定缓存组件的名字
-
key:缓存数据使用的key,可以用它来指定。默认是使用方法参数的值 1-方法的返回值
- 编写SpEL:#id,参数id的值 #a0 #p0 #root.args[0]
@Cacheable(cacheNames = {"emp"},key = "#root.methodName+'['+#id+']'") public Employee getEmpById(Integer id){ Employee empById = employeeMapper.getEmpById(id); System.out.println("查询"+id+"号员工"); return empById; }
- 编写SpEL:#id,参数id的值 #a0 #p0 #root.args[0]
-
keyGenerator:key的生成器。可以自己指定key的生成器的组件id。(key/keyGenerator二选一)
@Configuration public class MyCacheConfig { @Bean("myKeyGenerator") public KeyGenerator keyGenerator(){ return new KeyGenerator() { @Override public Object generate(Object o, Method method, Object... objects) { return method.getName()+"【"+ Arrays.asList(objects).toString()+"】"; } }; } }
@Cacheable(cacheNames = {"emp"},keyGenerator = "myKeyGenerator") public Employee getEmpById(Integer id){ Employee empById = employeeMapper.getEmpById(id); System.out.println("查询"+id+"号员工"); return empById; }
- cacheManager:指定缓存管理器,或者cacheResolver指定获取解析器
- condition:指定符合条件的情况下才缓存
condition = “#a0>1”:第一个参数的值大于1的时候才进行缓存 - unless:否定缓存。当unless指定的条件为true。方法的返回值就不会被缓存,可以获取到结果进行判断
- unless = “#result == null”
- sync:是否使用异步模式,异步模式下unless不支持
@Cacheable(cacheNames = {"emp"}) public Employee getEmpById(Integer id){ Employee empById = employeeMapper.getEmpById(id); System.out.println("查询"+id+"号员工"); return empById; }
-
-
原理
- 自动配置类:CacheAutoConfiguration
- 缓存的配置类
- 0 = “org.springframework.boot.autoconfigure.cache.GenericCacheConfiguration”
- 1 = “org.springframework.boot.autoconfigure.cache.JCacheCacheConfiguration”
- 2 = “org.springframework.boot.autoconfigure.cache.EhCacheCacheConfiguration”
- 3 = “org.springframework.boot.autoconfigure.cache.HazelcastCacheConfiguration”
- 4 = “org.springframework.boot.autoconfigure.cache.InfinispanCacheConfiguration”
- 5 = “org.springframework.boot.autoconfigure.cache.CouchbaseCacheConfiguration”
- 6 = “org.springframework.boot.autoconfigure.cache.RedisCacheConfiguration”
- 7 = “org.springframework.boot.autoconfigure.cache.CaffeineCacheConfiguration”
- 8 = “org.springframework.boot.autoconfigure.cache.SimpleCacheConfiguration”
- 9 = “org.springframework.boot.autoconfigure.cache.NoOpCacheConfiguration”
- 默认生效的配置类
- SimpleCacheConfiguration
- 给容器中注册了一个CacheManager:ConcurrentMapCacheManager
- 可以获取和创建ConcurrentMapCache类型的缓存组件,它的作用将数据保存在ConcurrentMap中
-
运行流程
- 方法运行之前,先去查询cache(缓存组件),按照cacheName指定的名字获取;CacheManager先获取相应的缓存,第一次获取缓存如果没有cache组件会自动创建
public Cache getCache(String name) { Cache cache = (Cache)this.cacheMap.get(name); if (cache == null && this.dynamic) { synchronized(this.cacheMap) { cache = (Cache)this.cacheMap.get(name); if (cache == null) { cache = this.createConcurrentMapCache(name); this.cacheMap.put(name, cache); } } } return cache; }
- 去Cache中查找缓存的内容,使用一个key,默认就是方法的参数;key是按照某种策略生成的,默认是使用keyGenerator生成的,默认使用的是SimpleKeyGenerator生成key
public static Object generateKey(Object... params) { if (params.length == 0) { //如果没有参数,就new SimpleKey() return SimpleKey.EMPTY; } else { if (params.length == 1) { //如果只有一个参数key=参数值 Object param = params[0]; if (param != null && !param.getClass().isArray()) { return param; } } //如果有多个参数,返回new SimpleKey(params); return new SimpleKey(params); } }
- 没有查到缓存就调用目标方法
- 将目标方法返回的结果,放到缓存中
-
@CachePut即调用方法,又更新缓存数据。修改了数据库的数据,同时更新缓存
- 先调用目标方法
- 将目标方法的结果缓存起来
@CachePut(value = "emp",key = "#employee.id") public Employee updateEmployee(Employee employee){ employeeMapper.updateEmployee(employee); return employee; }
- 查询缓存和更新缓存的key需要一致
-
@CacheEvict:缓存清除
- 可删除指定key
- allEntries = true:指定清除这个缓存中的所有数据
- beforeInvocation = false:缓存的清除是否在方法之前执行,默认缓存清除操作在方法之后执行,如果出现异常,缓存就不会执行
-
@Caching,定义复杂的缓存规则
-
@CacheConfig,可以抽取缓存的公共配置
整合redis来进行缓存
- 安装redis:使用docker
- 导入redis-starter
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
- Redis常见的五大数据类型
- String[字符串]:stringRedisTemplate.opsForValue()
- List[列表]:stringRedisTemplate.opsForList()
- Set[集合]:stringRedisTemplate.opsForSet()
- Hash[散列]:stringRedisTemplate.opsForHash()
- ZSet[有序集合]:stringRedisTemplate.opsForZSet()
- 配置redis
spring.redis.host=10.138.227.212
- 保存对象,需实现序列化接口
@Autowired
RedisTemplate redisTemplate;
@Test
public void testRedis(){
Employee empById = employeeMapper.getEmpById(1);
//默认如果保存对象,使用jdk序列化机制,序列化后的数据保存到redis中
redisTemplate.opsForValue().set("emp-01",empById);
}
- 自定义序列化规则
@Configuration
public class MyRedisConfig {
@Bean
public RedisTemplate<Object, Employee> empRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<Object, Employee> template = new RedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
Jackson2JsonRedisSerializer<Employee> jsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Employee.class);
template.setDefaultSerializer(jsonRedisSerializer);
return template;
}
}
测试
@Autowired
RedisTemplate<Object,Employee> empRedisTemplate;
@Test
public void testRedis(){
Employee empById = employeeMapper.getEmpById(1);
empRedisTemplate.opsForValue().set("emp-01",empById);
//1. 将数据以json的方式保存
//(1)自己将对象转为json
//(2)redisTemplate默认的序列化规则
}
-
缓存测试
-
原理:CacheManager==Cache 缓存组件来实际给缓存中存取数据
- 引入redis的starter,容器中保存的是 RedisCacheManager
- RedisCacheManager帮我们创建RedisCache来作为缓存组件,通过redis来缓存数据的
- 默认保存数据K-v都是Object:利用序列化保存,如何保存为json
- 引入redis的starter,CacheManager变为 RedisCacheManager
- 默认创建的RedisCacheManager操作redis的时候使用的是RedisTemplate<Object, Object>
- RedisTemplate<Object, Object>默认使用的是jdk序列化机制
- 自定义CacheManager
@Bean public RedisCacheManager cacheManager(RedisConnectionFactory factory) { RedisCacheConfiguration cacheConfiguration = RedisCacheConfiguration.defaultCacheConfig() .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new Jackson2JsonRedisSerializer<>(Employee.class))); return RedisCacheManager.builder(factory).cacheDefaults(cacheConfiguration).build(); }
- 注:缓存失败,出现500错误,清除之前的jdk序列化缓存即可
-
反序列化问题
- 缓存的数据能存入redis,第二次从缓存中查询就不能反序列化回来。
- 解决:使用多个cacheManager
@Bean public RedisCacheManager employeeCacheManager(RedisConnectionFactory factory) { RedisCacheConfiguration cacheConfiguration = RedisCacheConfiguration.defaultCacheConfig() .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new Jackson2JsonRedisSerializer<>(Employee.class))); return RedisCacheManager.builder(factory).cacheDefaults(cacheConfiguration).build(); } @Bean public RedisCacheManager departmentCacheManager(RedisConnectionFactory factory) { RedisCacheConfiguration cacheConfiguration = RedisCacheConfiguration.defaultCacheConfig() .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new Jackson2JsonRedisSerializer<>(Department.class))); return RedisCacheManager.builder(factory).cacheDefaults(cacheConfiguration).build(); }
@CacheConfig(cacheManager = "departmentCacheManager") @Service public class DepartmentService { }
@CacheConfig(cacheManager = "employeeCacheManager") @Service public class EmployeeService { }
-