SpringBoot与缓存
几个重要的概念 & 缓存注解
Cache |
缓存接口,定义缓存操作。实现有:RedisCache、EhCacheCache、ConcurrentMapCache等 |
CacheManager |
缓存管理器,管理各种缓存(Cache)组件 |
@Cacheable |
主要针对方法配置,能够根据方法的请求参数对其结果进行缓存 |
@CacheEvict |
清空缓存 |
@CachePut |
保证方法调用,又能将结果缓存,可以用于刷新缓存 |
@EnableCaching |
开启基于注解的缓存 |
keyGenerator |
缓存数据时key生成策略 |
serialize |
缓存数据时value序列化的策略 |
使用缓存
首先引入cache组件
@Cacheable
将方法的运行结果进行缓存,以后要相同的数据,直接从缓存中获取,不用调用方法
- CacheManager管理多个Cache组件的,每一个缓存组件有自己唯一的名字
- 几个属性:
- cacheNames/value:指定缓存组件的名字 cacheNames = {"emp"}
- key:缓存数据使用的key,默认使用的是方法的参数、还可以使用SpEL来编写
- keyGenerator:key的生成器,生成缓存数据的key,keyGenerator跟key只能二选一
- cacheManager:指定缓存管理器,或者cacheResolver获取解析器
- condition:指定符合条件的情况下才缓存。例:condition="#id>0",id大于0才缓存
- unless:否定缓存,当unless指定的条件为true,方法的返回值就不会被缓存,可以用#result获取方法的返回值。例: unless = "#result == null"
- sync: 是否使用异步模式
key的取值
名字 |
描述 |
示例 |
methodName |
当前被调用的方法名 |
#root.methodName |
method |
当前被调用的方法 |
#root.method.name |
target |
当前被调用的目标对象 |
#root.target |
targetClass |
当前被调用的目标对象类 |
#root.targetClass |
args |
当前被调用的方法的参数列表 |
#root.args[0] |
caches |
当前方法调用使用的缓存列表(如@Cacheable(value={“cache1”, “cache2”})),则有两个cache |
#root.caches[0].name |
argument name |
方法参数的名字,可以直接 #参数名,也可以使用 #p0或#a0 的形式,0代表参数的索引 |
#iban、#a0, #p0 |
result |
方法执行后的返回值(仅当前方法执行之后的判断有效,如’unless’, ‘cache put’的表达式 ‘cache evict’的表达式beforeInvocation=false) |
#result |
例:
①、首先在主程序类上标注上@EnableCaching注解
@EnableCaching @SpringBootApplication public class SpringBootCacheApplication { public static void main(String[] args) { SpringApplication.run(SpringBootCacheApplication.class, args); } }
②、在要缓存的方法上标注即可
@Cacheable(cacheNames = {"emp"}) public Employee getEmp(Integer id){ System.out.println("查询" + id + "员工号"); Employee emp = employeeMapper.getEmployeeById(id); return emp; }
@CachePut
这个注解用于更新缓存
示例:
/** * @CachePut: 既调用方法,又更新缓存 * 主要用于修改了数据库的某个数据,同时更新缓存 * 运行时机:先调用目标方法,再将目标方法的结果缓存起来 * 与@Cacheable不同,@Cacheable是先去缓存里面查看是否有值,再执行方法,因而这里在获取id的时候@CachePut可以通过#result获取而@Cacleable不行 * 注意:这里的key一定要填要更新的缓存的key,不填就会默认使用第一个参数作为key,很可能就达不到更新缓存的目的而是新创建了一个缓存 */ @CachePut(value = "emp", key = "#result.id") public Employee updateEmp(Employee employee){ System.out.println("更新cache"); employeeMapper.updateEmp(employee); return employee; }
@CacheEvict
该注解用于删除一个缓存
示例:
/** * allEntries:删除所有的缓存 allEntries = true * beforeInvocation:缓存的清除是否在方法之前执行,默认是在方法之后执行 * 其他参数与@Cacheable大致一样 * @param id */ @CacheEvict(value = "emp", key = "#id", allEntries = true) public void deleteEmp(Integer id){ System.out.println("删除" + id); }
@Caching
该注解用于存放多个缓存注解,有时候对于一个方法,想放置多个缓存,既想缓存又想更新时使用
示例:
/** * 可以放多个注解 * @param lastName * @return */ @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){ System.out.println("查询"); return employeeMapper.getEmpByLastName(lastName); }
@CacheConfig
有的时候觉得每次在使用缓存注解的时候都要指定缓存的名字,或者指定缓存的cacheManager之类的,觉得很麻烦。那么就可以在类上使用@CacheConfig统一的配置缓存的名字
@CacheConfig(value = "emp") @Service public class EmployeeService {
自定义key的生成策略
对于key,我们可以让它自动生成,生成的策略可以有我们自己制定,之需要在配置类中将我们的定制规则加入到容器中即可
@Bean public KeyGenerator keyGenerator(){ return new KeyGenerator(){ @Override public Object generate(Object o, Method method, Object... objects) { return method.getName() + "[" + Arrays.asList(objects) + "]"; } }; }
使用Redis做为缓存
SpringBoot默认使用的是ConcurrentMapCacheManager==ConcurrentMapCache,将数据保存在ConcurrentMap<String, Cache> cacheMap里面
当然我们还可以使用其他的中间件作为缓存
开发中使用的缓存中间件:redis,memcached,ehcache
引入redis的starter
<!-- 引入redis的starter --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency>
配置redis
来到配置文件application.yaml配置redis的主机地址
spring: redis: host: 172.17.119.176
使用redis
springboot的starter提供了两个Template操作redis
StringRedisTemplate跟RedisTemplate,其中StringRedisTemplate的key跟value都是字符串。redisTemplate的key跟value都是Object
/** * String(字符串)、List(列表)、Set(集合)、Hash(散列)、ZSet(有序集合)、HyperLogLog * stringRedisTemplate.opsForValue() 操作String(字符串) * stringRedisTemplate.opsForList() 操作List(列表) * stringRedisTemplate.opsForSet() 操作Set(集合) * stringRedisTemplate.opsForHash() 操作Hash(散列) * stringRedisTemplate.opsForZSet() 操作ZSet(有序集合) * stringRedisTemplate.opsForHyperLogLog() 操作HyperLogLog */ @Test public void test01(){ stringRedisTemplate.opsForValue().append("redis", "hello"); stringRedisTemplate.opsForList(); stringRedisTemplate.opsForSet(); stringRedisTemplate.opsForHash(); stringRedisTemplate.opsForZSet(); stringRedisTemplate.opsForHyperLogLog(); }
储存对象
有的时候需要将对象存进redis(例如一个JavaBean对象),但是如果对象不是可Serializable的,因此需要让JavaBean对象实现Serializable接口
public class Employee implements Serializable {
序列化机制
如果只是让JavaBean实现Serializable接口也是可以存储的,但是并不好看,那么能不能将JavaBean弄成Json的样式放进redis呢。直接的方式就是自己转换,但是未免有点麻烦,那就只能修改RedisTemplate的序列化机制了,在配置类中配置上序列化的方法即可
@Bean public RedisTemplate<Object, Employee> empredisTemplate( RedisConnectionFactory redisConnectionFactory) throws UnknownHostException { RedisTemplate<Object, Employee> redisTemplate = new RedisTemplate<>(); redisTemplate.setConnectionFactory(redisConnectionFactory); Jackson2JsonRedisSerializer<Employee> ser = new Jackson2JsonRedisSerializer<Employee>(Employee.class); redisTemplate.setDefaultSerializer(ser); return redisTemplate; }
自定义CacheManager
对于自定义CacheManager,SpringBoot 1.x跟SpringBoot 2.x有所不同
SpringBoot 1.x
@Bean public CacheManager cacheManager(RedisTemplate redisTemplate) { RedisCacheManager redisCacheManager = new RedisCacheManager(redisTemplate); //默认超时时间,单位秒 redisCacheManager.setDefaultExpiration(60); //缓存超时时间Map,key为cacheName,value为超时,单位是秒 Map<String, Long> expiresMap = new HashMap<>(); //缓存用户信息的cacheName和超时时间 expiresMap.put("user", 1800L); //缓存产品信息的cacheName和超时时间 expiresMap.put("product", 600L); redisCacheManager.setExpires(expiresMap); return redisCacheManager; }
SpringBoot 2.x
@Bean public RedisCacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) { RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig() .entryTtl(Duration.ofHours(1)); // 设置缓存有效期一小时 return RedisCacheManager .builder(RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory)) .cacheDefaults(redisCacheConfiguration).build(); }
编码的方式使用缓存
前面都是用的注解的方法将注解标注在方法上去缓存方法的结果,但是如果在方法的执行中有一个数据我们想缓存那应该怎么办呢?既然前面提到过CacheManager是管理Cache的,那我们就可以直接使用CacheManager去缓存了
@Autowired RedisCacheManager redisCacheManager; public void test(){ Cache emp = redisCacheManager.getCache("emp"); emp.put("111", "222"); }