Spring Boot与缓存
一、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设置。
二、Spring缓存抽象
Spring从3.1开始定义了org.springframework.cache.Cacheorg.springframework.cache.CacheManager接口来统一不同的缓存技术;并支持使用JCache(JSR-107)注解简化我们开发
- Cache接口为缓存的组件规范定义,包含缓存的各种操作集合;
- Cache接口下Spring提供了各种xxxCache的实现;如RedisCache,EhCacheCache , ConcurrentMapCache等;
- 每次调用需要缓存功能的方法时,Spring会检查检查指定参数的指定的目标方法是否已经被调用过;如果有就直接从缓存中获取方法调用后的结果,如果没有就调用方法并缓存结果后返回给用户。下次调用直接从缓存中获取。
- 使用Spring缓存抽象时我们需要关注以下两点;
- 1、确定方法需要被缓存以及他们的缓存策略
- 2、从缓存中读取之前缓存存储的数据
三、几个重要概念&缓存注解
Cache |
缓存接口,定义缓存操作。实现有:**RedisCache、EhCacheCache、ConcurrentMapCache**等 |
CacheManager |
缓存管理器,管理各种缓存(**Cache**)组件 |
@Cacheable |
主要针对方法配置,能够根据方法的请求参数对其结果进行缓存 |
@CacheEvict |
清空缓存 |
@CachePut |
保证方法被调用,又希望结果被缓存。 |
@EnableCaching |
开启基于注解的缓存 |
keyGenerator |
缓存数据时**key**生成策略 |
serialize |
缓存数据时**value**序列化策略 |
@Cacheable、@CachePut、@CacheEvict主要的参数
|
|
|
value |
缓存的名称,在 spring 配置文件中定义,必须指定至少一个 |
例如: @Cacheable(value=”mycache”) 或者 @Cacheable(value={”cache1”,”cache2”} |
key |
缓存的 key,可以为空,如果指定要按照 SpEL 表达式编写,如果不指定,则缺省按照方法的所有参数进行组合 |
例如: @Cacheable(value=”testcache”,key=”#userName”) |
condition |
缓存的条件,可以为空,使用 SpEL 编写,返回 true 或者 false,只有为 true 才进行缓存/清除缓存,在调用方法之前之后都能判断 |
例如: @Cacheable(value=”testcache”,condition=”#userName.length()>2”) |
allEntries (@CacheEvict ) |
是否清空所有缓存内容,缺省为 false,如果指定为 true,则方法调用后将立即清空所有缓存 |
例如: @CachEvict(value=”testcache”,allEntries=true) |
beforeInvocation (@CacheEvict) |
是否在方法执行前就清空,缺省为 false,如果指定为 true,则在方法还没有执行的时候就清空缓存,缺省情况下,如果方法执行抛出异常,则不会清空缓存 |
例如: @CachEvict(value=”testcache”,beforeInvocation=true) |
unless (@CachePut) (@Cacheable) |
用于否决缓存的,不像condition,该表达式只在方法执行之后判断,此时可以拿到返回值result进行判断。条件为true不会缓存,fasle才缓存 |
例如: @Cacheable(value=”testcache”,unless=”#result == null”) |
Cache SpEL available metadata
名字 |
位置 |
描述 |
示例 |
methodName |
root object |
当前被调用的方法名 |
#root.methodName |
method |
root object |
当前被调用的方法 |
#root.method.name |
target |
root object |
当前被调用的目标对象 |
#root.target |
targetClass |
root object |
当前被调用的目标对象类 |
#root.targetClass |
args |
root object |
当前被调用的方法的参数列表 |
#root.args[0] |
caches |
root object |
当前方法调用使用的缓存列表(如@Cacheable(value={“cache1”, “cache2”})),则有两个cache |
#root.caches[0].name |
argument name |
evaluation context |
方法参数的名字. 可以直接 #参数名 ,也可以使用 #p0或#a0 的形式,0代表参数的索引; |
#iban 、 #a0 、 #p0 |
result |
evaluation context |
方法执行后的返回值(仅当方法执行之后的判断有效,如‘unless’,’cache put’的表达式 ’cache evict’的表达式beforeInvocation=false) |
#result |
四、讲解实战例子
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| import org.mybatis.spring.annotation.MapperScan; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cache.annotation.EnableCaching;
@MapperScan("com.atguigu.cache.mapper") @SpringBootApplication @EnableCaching public class Springboot01CacheApplication {
public static void main(String[] args) { SpringApplication.run(Springboot01CacheApplication.class, args); } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152
| import com.atguigu.cache.bean.Employee; import com.atguigu.cache.mapper.EmployeeMapper; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cache.annotation.*; import org.springframework.stereotype.Service;
@CacheConfig(cacheNames="emp") @Service public class EmployeeService {
@Autowired EmployeeMapper employeeMapper;
@Cacheable(value = {"emp"}) public Employee getEmp(Integer id){ System.out.println("查询"+id+"号员工"); Employee emp = employeeMapper.getEmpById(id); return emp; }
@CachePut(key = "#result.id") public Employee updateEmp(Employee employee){ System.out.println("updateEmp:"+employee); employeeMapper.updateEmp(employee); return employee; }
@CacheEvict(value="emp",beforeInvocation = true) public void deleteEmp(Integer id){ System.out.println("deleteEmp:"+id); int i = 10/0; }
@Caching( cacheable = { @Cacheable(key = "#lastName") }, put = { @CachePut(key = "#result.id"), @CachePut(key = "#result.email") } ) public Employee getEmpByLastName(String lastName){ return employeeMapper.getEmpByLastName(lastName); } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60
| import com.atguigu.cache.bean.Employee; import com.atguigu.cache.mapper.EmployeeMapper; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.test.context.junit4.SpringRunner;
@RunWith(SpringRunner.class) @SpringBootTest public class Springboot01CacheApplicationTests { @Autowired EmployeeMapper employeeMapper;
@Autowired StringRedisTemplate stringRedisTemplate;
@Autowired RedisTemplate redisTemplate;
@Autowired RedisTemplate<Object, Employee> empRedisTemplate;
@Test public void test01(){
} @Test public void test02(){ Employee empById = employeeMapper.getEmpById(1); empRedisTemplate.opsForValue().set("emp-01",empById); } @Test public void contextLoads() { Employee empById = employeeMapper.getEmpById(1); System.out.println(empById); } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| import org.springframework.cache.interceptor.KeyGenerator; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration;
import java.lang.reflect.Method; import java.util.Arrays;
@Configuration public class MyCacheConfig {
@Bean("myKeyGenerator") public KeyGenerator keyGenerator(){ return new KeyGenerator(){
@Override public Object generate(Object target, Method method, Object... params) { return method.getName()+"["+ Arrays.asList(params).toString()+"]"; } }; } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
| import com.atguigu.cache.bean.Department; import com.atguigu.cache.bean.Employee; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; import org.springframework.data.redis.cache.RedisCacheManager; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer; import java.net.UnknownHostException;
@Configuration public class MyRedisConfig { @Bean public RedisTemplate<Object, Employee> empRedisTemplate( RedisConnectionFactory redisConnectionFactory) throws UnknownHostException { RedisTemplate<Object, Employee> template = new RedisTemplate<Object, Employee>(); template.setConnectionFactory(redisConnectionFactory); Jackson2JsonRedisSerializer<Employee> ser = new Jackson2JsonRedisSerializer<Employee>(Employee.class); template.setDefaultSerializer(ser); return template; } @Bean public RedisTemplate<Object, Department> deptRedisTemplate( RedisConnectionFactory redisConnectionFactory) throws UnknownHostException { RedisTemplate<Object, Department> template = new RedisTemplate<Object, Department>(); template.setConnectionFactory(redisConnectionFactory); Jackson2JsonRedisSerializer<Department> ser = new Jackson2JsonRedisSerializer<Department>(Department.class); template.setDefaultSerializer(ser); return template; } @Primary @Bean public RedisCacheManager employeeCacheManager(RedisTemplate<Object, Employee> empRedisTemplate){ RedisCacheManager cacheManager = new RedisCacheManager(empRedisTemplate);
cacheManager.setUsePrefix(true);
return cacheManager; } @Bean public RedisCacheManager deptCacheManager(RedisTemplate<Object, Department> deptRedisTemplate){ RedisCacheManager cacheManager = new RedisCacheManager(deptRedisTemplate);
cacheManager.setUsePrefix(true);
return cacheManager; } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
| import com.atguigu.cache.bean.Department; import com.atguigu.cache.mapper.DepartmentMapper; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.cache.Cache; import org.springframework.cache.annotation.CacheConfig; import org.springframework.cache.annotation.Cacheable; import org.springframework.data.redis.cache.RedisCacheManager; import org.springframework.stereotype.Service;
@Service public class DeptService {
@Autowired DepartmentMapper departmentMapper; @Qualifier("deptCacheManager") @Autowired RedisCacheManager deptCacheManager;
public Department getDeptById(Integer id){ System.out.println("查询部门"+id); Department department = departmentMapper.getDeptById(id); Cache dept = deptCacheManager.getCache("dept"); dept.put("dept:1",department); return department; } }
|