SpringBoot与缓存:
什么时候用缓存:电商网站的高频数据,比如商品信息,不需要直接查找数据库了。
临时数据比如验证码就不放在数据库放在缓存里面。
上面是JSR107缓存规范。
得到缓存管理器,获得缓存组件,将key-value记录进去。
缓存机制。
首先是应用程序调用缓存的提供者,缓存的提供者管理多个缓存的管理器,管理器里面有多个缓存是Entry数组。
CacheManager和Cache就像是连接池和连接一样。
使用JSR107:
导入这个包。
Cache组件里面提供了创建和获得缓存组件的方法。同时也提供了crud的方法。都是接口面向接口编程。
简化开发提供了自己的缓存的抽象。
1--------------------------------------------------------------------------------------------------------20190717
Spring的缓存抽象.
Cache是接口,学习下springboot提供的几个重要的接口和注解:
一般在删除的方法上标注CacheEvict,用户删除缓存删除。
@CachePut更新缓存:updataUser方法,更新用户,传入用户信息,缓存里面的数据也缓存。总是会被调用。
@EnbleCaching开启缓存机制。
其他的注解是序列化的策略。
2-------------------------------------------------------------------------------------------------------------------------------------------------20190717
基本的环境的搭建:
代码github地址:https://github.com/FandyWw/springboot-01-cache已失效看这个去电脑找代码
、
程序:
1.配置数据源
spring.datasource.url=jdbc:mysql://192.168.224.130:3306/spring_cache
spring.datasource.username=root
spring.datasource.password=123456
#spring.datasource.driver-class-name=com.mysql.jdbc.Driver
#上面这一行可以省略springboot会自动判断的
#10.211.55.9
2.配置扫描的mapper包,写mapper,写在主配置类上的。
@MapperScan("com.atguigu.cache.mapper")
3.在路径下写mapper
package com.atguigu.cache.mapper;
import com.atguigu.cache.bean.Department;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
@Mapper
public interface DepartmentMapper {
@Select("SELECT * FROM department WHERE id = #{id}")
Department getDeptById(Integer id);
}
package com.atguigu.cache.mapper;
import com.atguigu.cache.bean.Employee;
import org.apache.ibatis.annotations.*;
@Mapper
//显示声明十mybatis的mapper类
public interface EmployeeMapper {
@Select("SELECT * FROM employee WHERE id = #{id}")
public Employee getEmpById(Integer id);
@Update("UPDATE employee SET lastName=#{lastName},email=#{email},gender=#{gender},d_id=#{dId} WHERE id=#{id}")
public void updateEmp(Employee employee);
@Delete("DELETE FROM employee WHERE id=#{id}")
public void deleteEmpById(Integer id);
@Insert("INSERT INTO employee(lastName,email,gender,d_id) VALUES(#{lastName},#{email},#{gender},#{dId})")
public void insertEmployee(Employee employee);
@Select("SELECT * FROM employee WHERE lastName = #{lastName}")
Employee getEmpByLastName(String lastName);
}
单元测试:
@Autowired
EmployeeMapper employeeMapper;
@Test
public void contextLoads() {
Employee empById = employeeMapper.getEmpById(1);
System.out.println(empById);
}
补全controller和service
@Autowired
EmployeeMapper employeeMapper;
public Employee getEmp(Integer id){
System.out.println("查询"+id+"号员工");
Employee emp = employeeMapper.getEmpById(id);
return emp;
}
package com.atguigu.cache.controller;
import com.atguigu.cache.bean.Employee;
import com.atguigu.cache.service.EmployeeService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class EmployeeController {
@Autowired
EmployeeService employeeService;
@GetMapping("/emp/{id}")
public Employee getEmployee(@PathVariable("id") Integer id){
Employee employee = employeeService.getEmp(id);
return employee;
}
启动测试:
注意一个问题:程序里面是dId到那时数据库是d_id要开启驼峰命名法。
# 开启驼峰命名匹配规则
mybatis.configuration.map-underscore-to-camel-case=true
网址:localhost:8080/emp/1
3-------------------------------------------------------------------------------------------------------------------------------------------------20190707---
快速体验缓存:
/**
* 一、搭建基本环境
* 1、导入数据库文件 创建出department和employee表
* 2、创建javaBean封装数据
* 3、整合MyBatis操作数据库
* 1.配置数据源信息
* 2.使用注解版的MyBatis;
* 1)@MapperScan指定需要扫描的mapper接口所在的包
* 二、快速体验缓存
* 步骤:
* 1、开启基于注解的缓存 @EnableCaching
* 2、标注缓存注解即可
* @Cacheable 将方法的运行结果缓存 查
* @CacheEvict 删除
* @CachePut 修改
* 默认使用的是ConcurrentMapCacheManager==ConcurrentMapCache;将数据保存在 ConcurrentMap<Object, Object>中
* 开发中使用缓存中间件;redis、memcached、ehcache;
* 三、整合redis作为缓存
* Redis 是一个开源(BSD许可)的,内存中的数据结构存储系统,它可以用作数据库、缓存和消息中间件。
* 1、安装redis:使用docker;
* 2、引入redis的starter
* 3、配置redis
* 4、测试缓存
* 原理:CacheManager===Cache 缓存组件来实际给缓存中存取数据
* 1)、引入redis的starter,容器中保存的是 RedisCacheManager;
* 2)、RedisCacheManager 帮我们创建 RedisCache 来作为缓存组件;RedisCache通过操作redis缓存数据的
* 3)、默认保存数据 k-v 都是Object;利用序列化保存;如何保存为json
* 1、引入了redis的starter,cacheManager变为 RedisCacheManager;
* 2、默认创建的 RedisCacheManager 操作redis的时候使用的是 RedisTemplate<Object, Object>
* 3、RedisTemplate<Object, Object> 是 默认使用jdk的序列化机制
* 4)、自定义CacheManager;
*
*/
开启基于注解的缓存,用的话必须先开启缓存:
package com.atguigu.cache;
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")
@EnableCaching
@SpringBootApplication
public class CacheApplication {
public static void main(String[] args) {
SpringApplication.run(CacheApplication.class, args);
}
}
课堂讲解:
//打印这个包下面mooer的所有的日志
logging.level.com.atguigu.cache.mapper=debug
打印日志要在配置文件配置这个。
2019-04-09 16:41:39.849 INFO 9628 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Initializing Servlet 'dispatcherServlet'
2019-04-09 16:41:39.857 INFO 9628 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Completed initialization in 8 ms
查询1号员工
2019-04-09 16:41:39.906 INFO 9628 --- [nio-8080-exec-1] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Starting...
2019-04-09 16:41:40.554 INFO 9628 --- [nio-8080-exec-1] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Start completed.
2019-04-09 16:41:40.563 DEBUG 9628 --- [nio-8080-exec-1] c.a.c.mapper.EmployeeMapper.getEmpById : ==> Preparing: SELECT * FROM employee WHERE id = ?
2019-04-09 16:41:40.596 DEBUG 9628 --- [nio-8080-exec-1] c.a.c.mapper.EmployeeMapper.getEmpById : ==> Parameters: 1(Integer)
2019-04-09 16:41:40.653 DEBUG 9628 --- [nio-8080-exec-1] c.a.c.mapper.EmployeeMapper.getEmpById : <== Total: 1
在方法上加入缓存:可以指定多个缓存的名字{"emp","temp"} a0,p0,ai,pi也是代表的参数的索引。
@Cacheable(value = {"emp"}/*,keyGenerator = "myKeyGenerator",condition = "#a0>1",unless = "#a0==2"*/)
public Employee getEmp(Integer id){
System.out.println("查询"+id+"号员工");
Employee emp = employeeMapper.getEmpById(id);
return emp;
}
SPEL表达式:
缓存的解释这个注释好好看下:
/**
* 将方法的运行结果进行缓存;以后再要相同的数据,直接从缓存中获取,不用调用方法;
* CacheManager管理多个Cache组件的,对缓存的真正CRUD操作在Cache组件中,每一个缓存组件有自己唯一一个名字;
*
*
* 原理:
* 1、自动配置类;CacheAutoConfiguration
* 2、缓存的配置类
* org.springframework.boot.autoconfigure.cache.GenericCacheConfiguration
* org.springframework.boot.autoconfigure.cache.JCacheCacheConfiguration
* org.springframework.boot.autoconfigure.cache.EhCacheCacheConfiguration
* org.springframework.boot.autoconfigure.cache.HazelcastCacheConfiguration
* org.springframework.boot.autoconfigure.cache.InfinispanCacheConfiguration
* org.springframework.boot.autoconfigure.cache.CouchbaseCacheConfiguration
* org.springframework.boot.autoconfigure.cache.RedisCacheConfiguration
* org.springframework.boot.autoconfigure.cache.CaffeineCacheConfiguration
* org.springframework.boot.autoconfigure.cache.GuavaCacheConfiguration
* org.springframework.boot.autoconfigure.cache.SimpleCacheConfiguration【默认】
* org.springframework.boot.autoconfigure.cache.NoOpCacheConfiguration
* 3、哪个配置类默认生效:SimpleCacheConfiguration;
*
* 4、给容器中注册了一个CacheManager:ConcurrentMapCacheManager
* 5、可以获取和创建ConcurrentMapCache类型的缓存组件;他的作用将数据保存在ConcurrentMap中;
*
* 运行流程:
* @Cacheable:
* 1、方法运行之前,先去查询Cache(缓存组件),按照cacheNames指定的名字获取;
* (CacheManager先获取相应的缓存),第一次获取缓存如果没有Cache组件会自动创建。
* 2、去Cache中查找缓存的内容,使用一个key,默认就是方法的参数;
* key是按照某种策略生成的;默认是使用keyGenerator生成的,默认使用SimpleKeyGenerator生成key;
* SimpleKeyGenerator生成key的默认策略;
* 如果没有参数;key=new SimpleKey();
* 如果有一个参数:key=参数的值
* 如果有多个参数:key=new SimpleKey(params);
* 3、没有查到缓存就调用目标方法;
* 4、将目标方法返回的结果,放进缓存中
*
* @Cacheable标注的方法执行之前先来检查缓存中有没有这个数据,默认按照参数的值作为key去查询缓存,
* 如果没有就运行方法并将结果放入缓存;以后再来调用就可以直接使用缓存中的数据;
*
* 核心:
* 1)、使用CacheManager【ConcurrentMapCacheManager】按照名字得到Cache【ConcurrentMapCache】组件
* 2)、key使用keyGenerator生成的,默认是SimpleKeyGenerator
*
*
* 几个属性:
* cacheNames/value:指定缓存组件的名字;将方法的返回结果放在哪个缓存中,是数组的方式,可以指定多个缓存;
*
* key:缓存数据使用的key;可以用它来指定。key默认是使用方法参数的值就是传的值-value是方法的返回值就是存入后的返回值
* 编写SpEL; #id;取出参数id的值 #a0取出第一个参数 #p0取出第一个参数 #root.args[0]取出第一个参数
*
*
* keyGenerator:key的生成器;可以自己指定key的生成器的组件id
* key/keyGenerator:二选一使用;
*
*
* cacheManager:指定缓存管理器,就是两个缓存管理器都有一样的缓存;或者cacheResolver指定获取解析器也是二选一
*
* condition:指定符合条件的情况下才缓存;
* ,condition = "#id>0"
* condition = "#a0>1":第一个参数的值》1的时候才进行缓存
*
* unless:否定缓存;当unless指定的条件为true,方法的返回值就不会被缓存;可以获取到结果进行判断
* unless = "#result == null"
* unless = "#a0==2":如果第一个参数的值是2,结果不缓存;
* sync:是否使用异步模式
* @param id
* @return
*
*/
指定缓存管理器,就是两个缓存管理器都有一样的缓存
@Cacheable源码: 注解类的属性可以用spel表达式进行赋值。
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
package org.springframework.cache.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.core.annotation.AliasFor;
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Cacheable {
@AliasFor("cacheNames")
String[] value() default {};
@AliasFor("value")
String[] cacheNames() default {};//缓存的唯一的名字
String key() default "";//缓存数据使用的key 默认是方法参数的值 值是方法的返回值 可以使用spel表达式编写
String keyGenerator() default "";//自己指定key的生成器的组件id key与这个二选一
String cacheManager() default "";//指定缓存管理器redis还是普通的
String cacheResolver() default "";//缓存解析器 和上面的二选一
String condition() default "";//判断条件
String unless() default "";//判断条件 可以对获取的结果判断
boolean sync() default false;//异步?
}
原理图解:
总结下重要的知识点:
cache是ConcurrentMap。
cache:https://blog.csdn.net/qq_38023748/article/details/89188337
cachemanager是ConcurrentHashMap,因为有许多的cache,每个cache有自己的名字,不同的cachemanager的cache名字可以是一样的。
细节:
指定key或者key="#id的值“
其他spel表达式:
#id为参数id的值#a0 #p0 #root.args[0]
指定什么条件下用缓存:
@Cacheable(value = {"emp"},condition = "#a0>1",unless = "#a0==2")
public Employee getEmp(Integer id){
System.out.println("查询"+id+"号员工");
Employee emp = employeeMapper.getEmpById(id);
return emp;
}
测试:
@Cacheable(cacheNames = {"emp"})
public Employee getEmp(Integer id){
System.out.println("查询"+id+"号员工");
Employee emp = employeeMapper.getEmpById(id);
return emp;
}
unless是可以用返回值判断的。
4---------------------------------------------------------------------------------------------------------------------------------------------------20190717
重点:缓存的工作原理和工作步骤:
1.自动配置类入手---
代码:
@Import({CacheAutoConfiguration.CacheConfigurationImportSelector.class})
点进去看源码调用这个方法会给容器导入缓存用到的组件:
打断点调试下:
查看到返回的imports的所有的缓存的配置:
org.springframework.boot.autoconfigure.cache.GenericCacheConfiguration
org.springframework.boot.autoconfigure.cache.JCacheCacheConfiguration
org.springframework.boot.autoconfigure.cache.EhCacheCacheConfiguration
org.springframework.boot.autoconfigure.cache.HazelcastCacheConfiguration
org.springframework.boot.autoconfigure.cache.InfinispanCacheConfiguration
org.springframework.boot.autoconfigure.cache.CouchbaseCacheConfiguration
org.springframework.boot.autoconfigure.cache.RedisCacheConfiguration
org.springframework.boot.autoconfigure.cache.CaffeineCacheConfiguration
org.springframework.boot.autoconfigure.cache.GuavaCacheConfiguration
org.springframework.boot.autoconfigure.cache.SimpleCacheConfiguration
org.springframework.boot.autoconfigure.cache.NoOpCacheConfiguration
这些都是配置类,是有顺序的。点进去上面的任意一个配置类看源码是有条件运行的。
在配置文件中加入
debug=true
打印自动配置报告:
SimpleCacheConfiguration matched:
- Cache org.springframework.boot.autoconfigure.cache.SimpleCacheConfiguration automatic cache type (CacheCondition)
- @ConditionalOnMissingBean (types: org.springframework.cache.CacheManager; SearchStrategy: all) did not find any beans (OnBeanCondition)
只有这个匹配了,默认生效。
进入这个类这个方法,给容器中注册了一个cacheManager---ConcurrentMapCacheManager:
@Bean
public ConcurrentMapCacheManager cacheManager() {
ConcurrentMapCacheManager cacheManager = new ConcurrentMapCacheManager();
List<String> cacheNames = this.cacheProperties.getCacheNames();
if (!cacheNames.isEmpty()) {
cacheManager.setCacheNames(cacheNames);
}
return this.customizerInvoker.customize(cacheManager);
}
发现其给容器中注册了一个ConcurrentMapCacheManager
内部是ConcurrentHashMap 里面存的是n个<name,Cache>。
进入ConcurrentMapCacheManager
@Override
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;
}
点进去createConcurrentMapCache:
protected Cache createConcurrentMapCache(String name) {
SerializationDelegate actualSerialization = (isStoreByValue() ? this.serialization : null);
return new ConcurrentMapCache(name, new ConcurrentHashMap<Object, Object>(256),
isAllowNullValues(), actualSerialization);
}
这个是ConcurrentMap就是Cache里面是ConcurrentMap
可以获取和创建ConcurrentMapCache类型缓存组件,作用是将数据保存在ConcurrentMap(看这个容器的源码)。
点进去ConcurrentMapCache
cache将数据保存在ConcurrentMap(cache)中。
------------------------------------缓存的运行流程----原理:
以Cacheable为例:
@Cacheable:第一次是null
第一次为null的时候,注意这个emp是cache的名字。
注意没有的话会创建的。
1、目标方法运行之前,先去查询Cache(缓存组件),按照cacheNames指定的名字获取;
CacheManager先获取相应的缓存),第一次获取缓存如果没有Cache组件会自动创建。
在cache组件中:
看以前的步骤key是按照某种策略生成的。
点进去看生成原理
执行目标方法,第一次走生成key了,put的值就是目标方法的了。
值就是方法的返回值。
默认是SimpleKeyGenerator
2、去Cache中查找缓存的内容,使用一个key,默认就是方法的参数;
key是按照某种策略生成的;默认是使用keyGenerator生成的,默认使用SimpleKeyGenerator生成key;
SimpleKeyGenerator生成key的默认策略;
如果没有参数;key=new SimpleKey();
如果有一个参数:key=参数的值
如果有多个参数:key=new SimpleKey(params);
3、没有查到缓存就调用目标方法那个value就已经是目标方法返回的了;
4、将目标方法返回的结果,放进缓存中
第一次运行。
已经有缓存了就要查了查之前也要生成key:
5-----------------------------------------------------------------------------------------------------------------------------------------------------------会了
/**
* 将方法的运行结果进行缓存;以后再要相同的数据,直接从缓存中获取,不用调用方法;
* CacheManager管理多个Cache组件的,对缓存的真正CRUD操作在Cache组件中,每一个缓存组件有自己唯一一个名字;
*
*
* 原理:
* 1、自动配置类;CacheAutoConfiguration
* 2、缓存的配置类
* org.springframework.boot.autoconfigure.cache.GenericCacheConfiguration
* org.springframework.boot.autoconfigure.cache.JCacheCacheConfiguration
* org.springframework.boot.autoconfigure.cache.EhCacheCacheConfiguration
* org.springframework.boot.autoconfigure.cache.HazelcastCacheConfiguration
* org.springframework.boot.autoconfigure.cache.InfinispanCacheConfiguration
* org.springframework.boot.autoconfigure.cache.CouchbaseCacheConfiguration
* org.springframework.boot.autoconfigure.cache.RedisCacheConfiguration
* org.springframework.boot.autoconfigure.cache.CaffeineCacheConfiguration
* org.springframework.boot.autoconfigure.cache.GuavaCacheConfiguration
* org.springframework.boot.autoconfigure.cache.SimpleCacheConfiguration【默认】
* org.springframework.boot.autoconfigure.cache.NoOpCacheConfiguration
* 3、哪个配置类默认生效:SimpleCacheConfiguration;
*
* 4、给容器中注册了一个CacheManager:ConcurrentMapCacheManager
* 5、可以获取和创建ConcurrentMapCache类型的缓存组件;他的作用将数据保存在ConcurrentMap中;
*
* 运行流程:
* @Cacheable:
* 1、方法运行之前,先去查询Cache(缓存组件),按照cacheNames指定的名字获取;
* (CacheManager先获取相应的缓存),第一次获取缓存如果没有Cache组件会自动创建。
* 2、去Cache中查找缓存的内容,使用一个key,默认就是方法的参数;
* key是按照某种策略生成的;默认是使用keyGenerator生成的,默认使用SimpleKeyGenerator生成key;
* SimpleKeyGenerator生成key的默认策略;
* 如果没有参数;key=new SimpleKey();
* 如果有一个参数:key=参数的值
* 如果有多个参数:key=new SimpleKey(params);
* 3、没有查到缓存就调用目标方法;
* 4、将目标方法返回的结果,放进缓存中
*
* @Cacheable标注的方法执行之前先来检查缓存中有没有这个数据,默认按照参数的值作为key去查询缓存,
* 如果没有就运行方法并将结果放入缓存;以后再来调用就可以直接使用缓存中的数据;
*
* 核心:
* 1)、使用CacheManager【ConcurrentMapCacheManager】按照名字得到Cache【ConcurrentMapCache】组件
* 2)、key使用keyGenerator生成的,默认是SimpleKeyGenerator
*
*
* 几个属性:
* cacheNames/value:指定缓存组件的名字;将方法的返回结果放在哪个缓存中,是数组的方式,可以指定多个缓存;
*
* key:缓存数据使用的key;可以用它来指定。key默认是使用方法参数的值就是传的值-value是方法的返回值就是存入后的返回值
* 编写SpEL; #id;取出参数id的值 #a0取出第一个参数 #p0取出第一个参数 #root.args[0]取出第一个参数
*
*
* keyGenerator:key的生成器;可以自己指定key的生成器的组件id
* key/keyGenerator:二选一使用;
*
*
* cacheManager:指定缓存管理器,就是两个缓存管理器都有一样的缓存;或者cacheResolver指定获取解析器也是二选一
*
* condition:指定符合条件的情况下才缓存;
* ,condition = "#id>0"
* condition = "#a0>1":第一个参数的值》1的时候才进行缓存
*
* unless:否定缓存;当unless指定的条件为true,方法的返回值就不会被缓存;可以获取到结果进行判断
* unless = "#result == null"
* unless = "#a0==2":如果第一个参数的值是2,结果不缓存;
* sync:是否使用异步模式
* @param id
* @return
*
*/
分析了缓存的配置原理:
属性:key
过程:
拿到缓存 在缓存里面找。
没有缓存就去运行目标方法。
运行打了注解的目标方法之后将数据存入:
----------
指定的话自己编写spel表达式:
上面的红框的方法是对应的。
root.methodName:获得方法名getEmp
//网址:http://localhost:8080/emp/1
@Cacheable(cacheNames = {"emp"},key="#root.methodName+']'+#id+']'")
public Employee getEmp(Integer id){
System.out.println("查询"+id+"号员工");
Employee emp = employeeMapper.getEmpById(id);
return emp;
}
在这个方法上打断点
@Override
@Nullable
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;
}
在进入这里:
@Nullable
private Cache.ValueWrapper findCachedItem(Collection<CacheOperationContext> contexts) {
Object result = CacheOperationExpressionEvaluator.NO_RESULT;
for (CacheOperationContext context : contexts) {
if (isConditionPassing(context, result)) {
Object key = generateKey(context, result);
Cache.ValueWrapper cached = findInCaches(context, key);
if (cached != null) {
return cached;
}
else {
if (logger.isTraceEnabled()) {
logger.trace("No cache entry for key '" + key + "' in cache(s) " + context.getCacheNames());
}
}
}
}
return null;
}
---------------
另一种主要的方法:
指定自己的key的生成器,新建一个包加一个类。注意下面的代码是自己写的。
package com.atguigu.cache.config;
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 {
//不指定的话这个组件就是叫做keyGenerator就是方法名字
@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()+"]";
}
};
}
}
注意加@configuration必须在方法上加@bean返回给容器方法名字的bean
//网址:http://localhost:8080/emp/1
/* @Cacheable(cacheNames = {"emp"},key="#root.methodName+']'+#id+']'")*/
@Cacheable(cacheNames = {"emp"},keyGenerator = "myKeyGenerator")
public Employee getEmp(Integer id){
System.out.println("查询"+id+"号员工");
Employee emp = employeeMapper.getEmpById(id);
return emp;
}
放行的话就走我们自己的key生成策略。
-----------------------
下一个属性:condition
//网址:http://localhost:8080/emp/1
/* @Cacheable(cacheNames = {"emp"},key="#root.methodName+']'+#id+']'")*/
@Cacheable(cacheNames = {"emp"},keyGenerator = "myKeyGenerator",condition="#a0>1")
public Employee getEmp(Integer id){
System.out.println("查询"+id+"号员工");
Employee emp = employeeMapper.getEmpById(id);
return emp;
}
注意这里的a0就是第一个参数。
unless属性:a0==2不缓存。
@Cacheable(cacheNames = {"emp"},keyGenerator = "myKeyGenerator",condition="#a0>1",unless="#a0==2")
public Employee getEmp(Integer id){
System.out.println("查询"+id+"号员工");
Employee emp = employeeMapper.getEmpById(id);
return emp;
}
sync属性:是否异步,默认是false,就是方法执行完毕在加入缓存。true的话不支持unless。
6-------------------------------------------------------------------------------------------------------------------------------------------会了
@CachePut注解:
/**
* @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号员工没有在缓存中更新】
*
*/
@CachePut(value = "emp")
public Employee updateEmp(Employee employee){
System.out.println("updateEmp:"+employee);
employeeMapper.updateEmp(employee);
return employee;
}
注意这个注解的执行时机。
这个是在调方法之前的。
更新的网址:
http://localhost:8080/emp?id=1&lastName=zahngsan&gender=0
注意这样的话查完了还是有问题的,就是查询的时候传入的是key是方法的参数(id)的值,但是更新的时候传入的是方法的参数(对象)的值个查询的是不一样的。
解决办法:
@CachePut(value = "emp",key = "#result.id")
public Employee updateEmp(Employee employee){
System.out.println("updateEmp:"+employee);
employeeMapper.updateEmp(employee);
return employee;
}
result是返回值。
之前的写法如下图:
是因为运行时机不一样。
总结:注意的一点就是查询和更新的时候缓存要是同一个key
小结:cachePut先调用目标方法,一定会执行目标方法放入。
7------------------------------------------------------------------------------------------------------------会了
@CacheEvit注解,缓存清除,就是删除数据,将数据之前的缓存也删除掉。
/**
* @CacheEvict:缓存清除
* key:指定要清除的数据
* allEntries = true:指定清除 emp这个cache缓存中所有的数据
* beforeInvocation = false:缓存的清除是否在方法之前执行
* 默认代表缓存清除操作是在方法执行之后执行;如果出现异常缓存就不会清除
*
* beforeInvocation = true:
* 代表清除缓存操作是在方法运行之前执行,无论方法是否出现异常,缓存都清除
*
*
*/
/* @CacheEvict(value="emp",beforeInvocation = true*//*key = "#id",*//*)*/
@CacheEvict(value="emp",key = "#id")
public void deleteEmp(Integer id){
employeeMapper.deleteEmpById(id);
System.out.println("deleteEmp:"+id);
}
删除的是缓存的。是按照指定的key删除的。
添加属性:
/* @CacheEvict(value="emp",beforeInvocation = true*//*key = "#id",*//*)*/
/*@CacheEvict(value="emp",key = "#id")*/
@CacheEvict(value="emp",allEntries = true)
public void deleteEmp(Integer id){
System.out.println("deleteEmp:"+id);
//employeeMapper.deleteEmpById(id);
int i = 10/0;
}
allEntries = true就是不用指定key了删除所有的数据。就是把1 2号员工的缓存都删除了。
属性beforeInvocation是否在方法之前执行,默认是false,true的话就是方法报错也执行。
@CacheEvict(value="emp",beforeInvocation = true)
public void deleteEmp(Integer id){
System.out.println("deleteEmp:"+id);
//employeeMapper.deleteEmpById(id);
int i = 10/0;
}
fasle时候方法错了就不清除了,因为默认是再方法之后执行的。
8-------------------------------------------------------------------------------------------------------------会了
@caching注解就是指定的多个条件:
//@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);
}
注意put规则是在方法运行之后调用的。
网址:
第一次在数据库查的
再次用lastName查还是会执行数据库。因为cachePut注解的意义在于方法一定执行。目的是为了更新的,不是查询的。
http://localhost:8080/emp/lastname/zahngsan
公共属性的抽取:
在类上加注解。
@CacheConfig(cacheNames="emp"/*,cacheManager = "employeeCacheManager"*/) //抽取缓存的公共配置
@Service
public class EmployeeService {
9---------------------------------------------------------------------------------------------------------------