SpringBoot-缓存-redis
JSR107
Java Caching定义了5个核心接口,分别是CachingProvider, CacheManager, Cache, Entry 和 Expiry。
- CachingProvider定义了创建、配置、获取、管理和控制多个CacheManager。一个应用可以在运行期访问多个CachingProvider。
- CacheManager定义了创建、配置、获取、管理和控制多个唯一命名的Cache,这些Cache存在于CacheManager的上下文中。一个CacheManager仅被一个CachingProvider所拥有。
- Cache是一个类似Map的数据结构并临时存储以Key为索引的值。一个Cache仅被一个CacheManager所拥有。
- Entry是一个存储在Cache中的key-value对。
- Expiry 每一个存储在Cache中的条目有一个定义的有效期。一旦超过这个时间,条目为过期的状态。一旦过期,条目将不可访问、更新和删除。缓存有效期可以通过ExpiryPolicy设置。
<dependency> <groupId>javax.cache</groupId> <artifactId>cache-api</artifactId> </dependency>
Spring缓存抽象
- Cache接口为缓存的组件规范定义,包含缓存的各种操作集合;
- Cache接口下Spring提供了各种xxxCache的实现;如RedisCache,EhCacheCache , ConcurrentMapCache等;
每次调用需要缓存功能的方法时,Spring会检查检查指定参数的指定的目标方法是否已经被调用过;如果有就直接从缓存中获取方法调用后的结果,如果没有就调用方法并缓存结果后返回给用户。下次调用直接从缓存中获取。
使用Spring缓存抽象时我们需要关注以下两点;
- 确定方法需要被缓存以及他们的缓存策略
- 从缓存中读取之前缓存存储的数据
搭建环境
步骤
开启基于注解的缓存@EnableCaching@EnableCaching public class SpringbootdemoApplication {...}
标注缓存注解即可
基本架构
sqEL
CacheManager管理多个Cache组件的,对缓存的真正CRUD操作在Cache组件中,每一个缓存组件有自己唯一的一个名字
Cacheable 几个属性
cacheNames/Value:指定缓存组的名字
key:缓存数据使用可以;可以用它来指定。默认是使用方法参数的值, 1-方法的返回值 K-V 可以编写SQEL
key="#root.methodName+'[..'+#id +'..]'"keyGenerator:key的生成器;可以自己指定可以的生成器的组件id 可以通过Config类来生成bean对象
key/Generator二选一使用 【可以进行字符串处理】@Bean public KeyGenerator keyGenerator() { return new KeyGenerator() { @Override public Object generate(Object target, Method method, Object... params) { return method.getName() + "["+Arrays.asList(params)+"]"; } }; }
cacheManager:指定缓存的管理器;或者cacheResovler指定获取解析器 二选一
condition:指定符合条件的情况下才缓存:
condition="#id>0"unless:否定缓存;当unless指定的条件为true,方法的返回值就不会背缓存,可以获取到结果进行判断
unless="#id==2" 排除掉2sync:是否使用异步模式
@Cacheable(cacheNames = "depa",key = "#id") public Department getDepartment(Integer id) { System.out.println("getDepartment....."); return departmentMapper.getDepartment(id); } + Condition condition = "#id>0"
工作原理从自动配置类入手CacheAutoConfiguration || \/ @Import({ CacheConfigurationImportSelector.class, CacheManagerEntityManagerFactoryDependsOnPostProcessor.class }) 在import中一共有十个缓存器 SimpleCacheConfiguration || \/ 放了一个ConcurrentMapCacheManager 缓存管理器 class SimpleCacheConfiguration { @Bean 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); } } || \/ 可以获取缓存中的东西 public Cache getCache(String name) { 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); this.cacheMap.put(name, cache); } } } return cache; } 保存缓存的数据结构是 线程安全 private final ConcurrentMap<String, Cache> cacheMap = new ConcurrentHashMap<>(16);
运行流程:
@Cacheable:
方法运行之前,先去查询Cache(缓存组件),按照cacheNames指定的名字获取;
(CacheManager先获取相应的缓存),第一次获取缓存如果没有Cache组件会自动创建。去Cache中查找缓存的内容,使用一个key,默认就是方法的参数;
key是按照某种策略生成的;默认是使用keyGenerator生成的,默认使用SimpleKeyGenerator生成key;
SimpleKeyGenerator生成key的默认策略;
如果没有参数;key=new SimpleKey();
如果有一个参数:key=参数的值
如果有多个参数:key=new SimpleKey(params);没有查到缓存就调用目标方法;
将目标方法返回的结果,放进缓存中
@Cacheable标注的方法执行之前先来检查缓存中有没有这个数据,默认按照参数的值作为key去查询缓存,
如果没有就运行方法并将结果放入缓存;以后再来调用就可以直接使用缓存中的数据;
@CachePut:即调用方法,更新缓存
- 注意同步更新缓存
/** * 修改了数据库的某个数据,同时更新缓存 * 运行时机: * 1. 先调用用目标方法 * 2. 将目标方法的结果缓存起来 对象需要设置key * key是传入的Department对象 值,返回的Department对象 * department.id * 3. key="#result.id" 使用返回后的id @Cacheable是不能用result【顺序】 * @param department * @return */ @CachePut(value = "depa",key = "#department.id") public Department updateDepartment(Department department) { System.out.println("updateDepartment....."); departmentMapper.insertDepartment(department); return department; }
@CacheEvict缓存清除
- 代码
/** * 清除缓存 * key指定要删除的数据key = "#id" * allEntries = true 删除所有的数据 * beforeInvocation = false:缓存的清楚是否在方法之前执行 * 默认代表是在方法执行之后执行【方法出错了 会影响是否清除】 * @param id */ @CacheEvict(value = "depa") public void deleteDepartment(Integer id) { System.out.println("updateDepartment....."); }
@Caching
- 代码
// @Caching 定义复杂的缓存规则 @Caching( cacheable = { @Cacheable(/*value="emp",*/key = "#lastName") }, put = { @CachePut(/*value="emp",*/key = "#result.id"), @CachePut(/*value="emp",*/key = "#result.email") } ) public Department department(Department department){ return department; }
@ CacheConfig
- 代码
@CacheConfig(cacheNames="depa"/*,cacheManager = "DepartmenCacheManager"*/) //抽取缓存的公共配置 @Service public class MyDepartmentDao {}
在工作用redis
整合redis作为缓存
步骤
导入start
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency>
需要使用两个Bean
public class RedisAutoConfiguration { @Bean @ConditionalOnMissingBean(name = "redisTemplate") public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {//k-v操作的都是对象 RedisTemplate<Object, Object> template = new RedisTemplate<>(); template.setConnectionFactory(redisConnectionFactory); return template; } @Bean @ConditionalOnMissingBean public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {//操作k-v都是字符串 StringRedisTemplate template = new StringRedisTemplate(); template.setConnectionFactory(redisConnectionFactory); return template; }
用docker创建redis
cznczai@cznczaihadoop101:~$ sudo docker run -itd --name redis-test -p 6379:6379 redis :需要指定接口 63f54ff1e789cfc595455d288323245cc5a314905981a79612a5db6867f72621 cznczai@cznczaihadoop101:~$ sudo docker exec -it redis-test /bin/bash
测试代码
@SpringBootTest @RunWith(SpringRunner.class) public class Test01 { @Autowired StringRedisTemplate stringRedisTemplate;//操作字符串的 @Autowired RedisTemplate redisTemplate;//操作对象的 @Test public void test01(){ stringRedisTemplate.opsForValue().append("msg","hello"); String msg = stringRedisTemplate.opsForValue().get("msg"); System.out.println(msg); } }
效果
将数据转化为json 并修改转化器
@SpringBootTest @RunWith(SpringRunner.class) public class Test01 { @Autowired StringRedisTemplate stringRedisTemplate;//操作字符串的 @Autowired RedisTemplate redisTemplate;//操作对象的 @Test public void test01() { redisTemplate.setDefaultSerializer(new Jackson2JsonRedisSerializer<User>(User.class)); User user = new User(); redisTemplate.opsForValue().set("user",user); User user1 = (User) redisTemplate.opsForValue().get("user"); System.out.println(user1.toString());//User{age=1} } } class User implements Serializable { public int age = 1 ; @Override public String toString() { return "User{" + "age=" + age + '}'; } } //@Configuration //class RedisConfig { // @Bean // public RedisTemplate<Object, User> redisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException { // RedisTemplate<Object, User> template = new RedisTemplate<Object, User>(); // template.setConnectionFactory(redisConnectionFactory); // template.setDefaultSerializer(new Jackson2JsonRedisSerializer<User>(User.class)); // return template; // } //}
配置是有顺序的 redis先于simple 所以用redis
通过@ConditionalOnMissingBean(CacheManager.class)自定义CacheManager 可以通过下面的代码进行修改
@Bean RedisCacheManager cacheManager(CacheProperties cacheProperties, CacheManagerCustomizers cacheManagerCustomizers, ObjectProvider<org.springframework.data.redis.cache.RedisCacheConfiguration> redisCacheConfiguration, ObjectProvider<RedisCacheManagerBuilderCustomizer> redisCacheManagerBuilderCustomizers, RedisConnectionFactory redisConnectionFactory, ResourceLoader resourceLoader) { RedisCacheManagerBuilder builder = RedisCacheManager.builder(redisConnectionFactory).cacheDefaults( determineConfiguration(cacheProperties, redisCacheConfiguration, resourceLoader.getClassLoader())); List<String> cacheNames = cacheProperties.getCacheNames(); if (!cacheNames.isEmpty()) { builder.initialCacheNames(new LinkedHashSet<>(cacheNames)); } redisCacheManagerBuilderCustomizers.orderedStream().forEach((customizer) -> customizer.customize(builder)); return cacheManagerCustomizers.customize(builder.build()); }
Cacheable 也对redis生效了 也可以指定对应的Cache管理器
@Primary来指定默认的缓存管理器