Java Caching定义了5个核心接口,分别是CachingProvider, CacheManager, Cache, Entry 和 Expiry。
JSR-107规范 提取码:uAjL
Cache | 缓存接口,定义缓存操作。实现有:RedisCache、EhCacheCache、ConcurrentMapCache等 |
---|---|
CacheManager | 缓存管理器,管理各种缓存(Cache)组件 |
@Cacheable | 主要针对方法配置,能够根据方法的请求参数对其结果进行缓存 |
@CacheEvict | 清空缓存 |
@CachePut | 保证方法被调用,又希望结果被缓存。 |
@EnableCaching | 开启基于注解的缓存 |
keyGenerator | 缓存数据时key生成策略 |
serialize | 缓存数据时value序列化策略 |
| serialize | 缓存数据时value序列化策略 |
@Service
public class EmployServiceImpl implements EmployService {
@Autowired
EmployeeMapper employeeMapper;
/**
* 将方法的运行结果进行缓存,以后再要相同的数据,直接从缓存中获取,不用调用方法
*
* CacheManager管理多个Cache组件,对缓存真正的CRUD操作在Cache组件中,每一个缓存组件有自己唯一的名字
* cacheName/value:指定缓存组件的名字;将方法的返回结果放在哪个缓存中,是数组的方式,可以指定多个缓存;
* key:缓存数据时使用的key,可以用它来指定,默认使用方法参数的值
* 可以编写SpEL:#id;参数id的值 #a0 #p0 #root.args[0]
* getEmp[2]
* keyGenerator:key的生成器,可以自己指定Key的生成器组件id -- 与key二选一使用
* cacheManager:指定缓存管理器,与cacheResolver(缓存解析器)二选一使用
* condition:指定符合条件的情况下才缓存
* ,condition = "#id>0"
* condition = "#a0>1":第一个参数的值》1的时候才进行缓存
* unless:否定缓存,当unless指定的条件为true,方法的返回值不会被缓存,可以获取到结果进行判断(#result)
* unless = "#result == null"
* unless = "#a0==2":如果第一个参数的值是2,结果不缓存;
* sync:是否使用异步模式
* @param id
* @return
*/
@Override
@Cacheable(cacheNames = "emp", key = "#root.args[0]", condition = "#id > 0")
public Employee getEmp(Integer id) {
System.out.println("查询"+id+"号员工");
Employee emp = employeeMapper.getEmpById(id);
return emp;
}
}
运行流程:
@Cacheable:
(CacheManager先获取相应的缓存),第一次获取缓存如果没有Cache组件会自动创建。
key是按照某种策略生成的;默认是使用keyGenerator生成的,默认使用SimpleKeyGenerator生成key;
SimpleKeyGenerator生成key的默认策略;
@Cacheable标注的方法执行之前先来检查缓存中有没有这个数据,默认按照参数的值作为key去查询缓存,
如果没有就运行方法并将结果放入缓存;以后再来调用就可以直接使用缓存中的数据;
核心:
/**
* @CachePut:既调用方法,又更新缓存数据;同步更新缓存
* 修改了数据库的某个数据,同时更新缓存;
* 运行时机:
* 1、先调用目标方法
* 2、将目标方法的结果缓存起来
*
* 测试步骤:
* 1、查询1号员工;查到的结果会放在缓存中;
* key:1 value:lastName:张三
* 2、以后查询还是之前的结果
* 3、更新1号员工;【lastName:zhangsan;gender:0】
* 将方法的返回值也放进缓存了;
* key:传入的employee对象 值:返回的employee对象;
* 4、查询1号员工?
* 应该是更新后的员工;
* key = "#employee.id":使用传入的参数的员工id;
* key = "#result.id":使用返回后的id
* @Cacheable的key是不能用#result
* 为什么是没更新前的?【1号员工没有在缓存中更新】 -- 要保持key一致才能从缓存中更新
*
*/
@CachePut(/*value = "emp",*/key = "#result.id")
public Employee updateEmp(Employee employee){
System.out.println("updateEmp:"+employee);
employeeMapper.updateEmp(employee);
return employee;
}
/**
* @CacheEvict:缓存清除
* key:指定要清除的数据
* allEntries = true:指定清除这个缓存中所有的数据
* beforeInvocation = false:缓存的清除是否在方法之前执行
* 默认代表缓存清除操作是在方法执行之后执行;如果出现异常缓存就不会清除
*
* beforeInvocation = true:
* 代表清除缓存操作是在方法运行之前执行,无论方法是否出现异常,缓存都清除
*
*
*/
@CacheEvict(value="emp",beforeInvocation = true/*key = "#id",*/)
public void deleteEmp(Integer id){
System.out.println("deleteEmp:"+id);
//employeeMapper.deleteEmpById(id);
int i = 10/0;
}
// @Caching 定义复杂的缓存规则
@Caching(
cacheable = {
@Cacheable(/*value="emp",*/key = "#lastName")
},
put = {
@CachePut(/*value="emp",*/key = "#result.id"),
@CachePut(/*value="emp",*/key = "#result.email")
}
)
public Employee getEmpByLastName(String lastName){
return employeeMapper.getEmpByLastName(lastName);
}
@CacheConfig(cacheNames="emp"/*,cacheManager = "employeeCacheManager"*/) //抽取缓存的公共配置
@Service
public class EmployeeService {}
默认使用的是ConcurrentMapCacheManager==ConcurrentMapCache;将数据保存在 ConcurrentMap<Object, Object>中
开发中使用缓存中间件;redis、memcached、ehcache;
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
RedisTemplate:k-v都是对象
StringRedisTemplate:k-v都是字符串的
(1)方法
stringRedisTemplate.opsForValue() -- 操作字符串的
stringRedisTemplate.opsForList() -- 操作List(列表)的
stringRedisTemplate.opsForSet() -- 操作Set(集合)的
stringRedisTemplate.opsForHash() -- 操作Hash(散列)
stringRedisTemplate.opsForZSet() -- 操作ZSet(有序集合)
@Test
void testRedis() {
Employee emp = employeeMapper.getEmpById(1);
redisTemplate.opsForValue().set("emp-01",emp);
}
保存对象默认使用JDK序列化机制,序列化后的数据保存到redis中
想要将数据以json方式保存,一是自己将对象转化为json,二是改版redisTemplate默认的序列化规则
@Configuration
public class MyRedisConfig {
@Bean
public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory)
throws UnknownHostException {
RedisTemplate<Object, Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory);
Jackson2JsonRedisSerializer<Object> objectJackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<Object>(Object.class);
template.setDefaultSerializer(objectJackson2JsonRedisSerializer);
return template;
}
}
CacheManager===Cache 缓存组件来实际给缓存中存取数据
Springboot 1.X
@Bean
public RedisCacheManager employeeCacheManager(RedisTemplate<Object, Object> redisTemplate){
RedisCacheManager cacheManager = new RedisCacheManager(redisTemplate);
//key多了一个前缀
//使用前缀,默认会将CacheName作为key的前缀
cacheManager.setUsePrefix(true);
return cacheManager;
}
Springboot 2.X
@Bean
public RedisCacheManager redisCacheManager(RedisConnectionFactory redisConnectionFactory) {
RedisCacheConfiguration cacheConfiguration = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofSeconds(10)) //设置过期时间
.disableCachingNullValues() //禁止缓存null对象
// .computePrefixWith(cacheName -> "yourAppName".concat(":").concat(cacheName).concat(":"))//* //此处定义了cache key的前缀,避免公司不同项目之间的key名称冲突
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer())) //定义了key和value的序列化协议,同时hash key和hash value也被定义
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new Jackson2JsonRedisSerializer<>(Object.class))); //序列化器也可以使用GenericJackson2JsonRedisSerializer,可加入@Class属性
return RedisCacheManager.builder(redisConnectionFactory)
.cacheDefaults(cacheConfiguration)
.build();
}
存缓存也可以使用这种方式
@Autowired
CacheManager cacheManager;
public Employee getEmployee(Integer id) {
System.out.println("查询"+id+"号员工");
Employee emp = employeeMapper.getEmpById(id);
//获取某个缓存
Cache dept = cacheManager.getCache("dept");
dept.put("dept::1",dept);
return emp;
}