幾個重要的概念 & 緩存注解
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"); }