十八、SpringBoot之缓存的使用及原理

一、JSR-107

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设置。

十八、SpringBoot之缓存的使用及原理_第1张图片

        
            javax.cache
            cache-api
        

十八、SpringBoot之缓存的使用及原理_第2张图片

二、Spring缓存抽象

Spring从3.1开始定义了org.springframework.cache.Cache 和org.springframework.cache.CacheManager接口来统一不同的缓存技术; 并支持使用JCache(JSR-107)注解简化我们开发;

  • Cache接口为缓存的组件规范定义,包含缓存的各种操作集合;
  • Cache接口下Spring提供了各种xxxCache的实现;如RedisCache,EhCacheCache ,ConcurrentMapCache等;
  • 每次调用需要缓存功能的方法时,Spring会检查指定参数的指定的目标方法是否已经被调用过;如果有就直接从缓存中获取方法调用后的结果,如果没有就调用方法并缓存结果后返回给用户。下次调用直接从缓存中获取。
  • 使用Spring缓存抽象时我们需要关注以下两点:

            1.确定方法需要被缓存以及他们的缓存策略

            2.从缓存中读取之前缓存存储的数据

十八、SpringBoot之缓存的使用及原理_第3张图片

三、几个重要概念&缓存注解

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=”#userNam e.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", #root.caches[0].name "cache2"})),则有两个cache

argument name

evaluation context 方法参数的名字. 可以直接 #参数名 ,也可以使用 #p0或#a0 的 #iban 、 #a0 、 #p0 形式,0代表参数的索引;

result

evaluation context 方法执行后的返回值(仅当方法执行之后的判断有效,如 #result ‘unless’,’cache put’的表达式 ’cache evict’的表达式

beforeInvocation=false)

四、缓存使用

1.缓存使用步骤:

  • 引入spring-boot-starter-cache模块
  • @EnableCaching开启缓存
  • 3、使用缓存注解
  • 4、切换为其他缓存

2.缓存使用代码

  • Application
/**
 * 一、搭建基本环境
 * 1、导入数据库文件 创建出department和employee表
 * 2、创建出JavaBean封装数据
 * 3、整合MyBatis操作数据库
 *    1.配置数据源
 *    2.使用注解版的MyBatis
 *        1)、@MapperScan指定需要扫描的mapper接口所在的包
 * 二、快速体验缓存
 *    步骤:
 *        1、开启基于注解的缓存 @EnableCaching
 *        2、标注缓存注解即可
 *            @Cacheable
 *            @CacheEvict
 *            @CachePut
 * */
@MapperScan(value = "com.atguigu.springboot.mapper")
@SpringBootApplication
@EnableCaching
public class SpringBoot01CacheApplication {
    public static void main(String[] args) {
        SpringApplication.run(SpringBoot01CacheApplication.class, args);
    }
}
  • pom.xml
        
        
            org.springframework.boot
            spring-boot-starter-cache
        
  • Bean

public class Department {
	private Integer id;
	private String departmentName;
	
public class Employee {
	private Integer id;
	private String lastName;
	private String email;
	private Integer gender; //性别 1男  0女
	private Integer dId;
  • Mapper
//@Mapper
public interface DepartmentMapper {
    @Select("select * from department where id=#{id}")
    public Department getDeptById(Integer id);

    @Delete("delete from department where id=#{id}")
    public int deleteDeptById(Integer id);

    @Insert("insert into department(department_name) values(#{departmentName})")
    public int insertDept(Department department);

    @Update("update department set department_name=#{departmentName} where id=#{id}")
    public int updateDept(Department department);
}
//@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 int updateEmp(Employee employee);

    @Delete("delete from employee where id=#{id}")
    public int deleteEmpById(Integer id);

    @Insert("insert into employee(lastName,email,gender,d_id) values(#{lastName},#{email},#{gender},#{dId})")
    public int insertEmp(Employee employee);
}
  • Service
@Service
public class EmployeeService {
    @Autowired
    EmployeeMapper employeeMapper;
    /**
     * @Cacheable 将方法的运行结果进行缓存;以后再要相同的数据,直接从缓存中获取,不用调用方法;
     * @param id
     * @return
     * */
    @Cacheable(value = "emp")
    public Employee getEmp(Integer id){
        System.out.println("查询"+id+"号员工");
        Employee emp = employeeMapper.getEmpById(id);
        return emp;
    }
}
  • Controller
@RestController
public class EmployeeController {
    @Autowired
    EmployeeService employeeService;

    @GetMapping("/emp/{id}")
    public Employee getEmployee(@PathVariable("id") Integer id){
        Employee employee = employeeService.getEmp(id);
        return employee;
    }
}
  • application.properties
server.port=8888
spring.datasource.url=jdbc:mysql://172.16.40.147:3306/spring_cache
spring.datasource.username=root
spring.datasource.password=123456
#spring.datasource.driver-class-name=com.mysql.jdbc.Driver

# mybatis驼峰命名法
mybatis.configuration.map-underscore-to-camel-case=true
#cache sql日志
logging.level.com.atguigu.springboot.mapper=debug
#
#debug=true
  • test
    @Autowired
    EmployeeMapper employeeMapper;
    @Test
    public void contextLoads() {
        Employee employee =   employeeMapper.getEmpById(1);
        System.out.println(employee);
    }

五、缓存原理

  • 1.自动配置类:CacheAutoConfiguration.java
@Configuration
@ConditionalOnClass({CacheManager.class})
@ConditionalOnBean({CacheAspectSupport.class})
@ConditionalOnMissingBean(
    value = {CacheManager.class},
    name = {"cacheResolver"}
)
@EnableConfigurationProperties({CacheProperties.class})
@AutoConfigureAfter({CouchbaseAutoConfiguration.class, HazelcastAutoConfiguration.class, HibernateJpaAutoConfiguration.class, RedisAutoConfiguration.class})
//给容器中导入缓存要用的一些组件
@Import({CacheAutoConfiguration.CacheConfigurationImportSelector.class})
public class CacheAutoConfiguration {
    static class CacheConfigurationImportSelector implements ImportSelector {
        CacheConfigurationImportSelector() {
        }

        public String[] selectImports(AnnotationMetadata importingClassMetadata) {
            CacheType[] types = CacheType.values();
            //给容器中导入缓存要用的一些组件
            String[] imports = new String[types.length];

            for(int i = 0; i < types.length; ++i) {
                imports[i] = CacheConfigurations.getConfigurationClass(types[i]);
            }

            return imports;
        }
    }
  • 2.导入缓存自动配置类组件

十八、SpringBoot之缓存的使用及原理_第4张图片

  • 3.默认生效配置类SimpleCacheConfiguration

  • 4.SimpleCacheConfiguration给容器中注册一个CacheManager:ConcurrentMapCacheManager
SimpleCacheConfiguration.java
    @Bean
    public ConcurrentMapCacheManager cacheManager() {
        ConcurrentMapCacheManager cacheManager = new ConcurrentMapCacheManager();
        List cacheNames = this.cacheProperties.getCacheNames();
        if (!cacheNames.isEmpty()) {
            cacheManager.setCacheNames(cacheNames);
        }

        return (ConcurrentMapCacheManager)this.customizerInvoker.customize(cacheManager);
    }
  • 5.ConcurrentMapCacheManager可以获取和创建ConcurrentMapCache类型的缓存组件:它的作用将数据保存在ConcurrentMap中
ConcurrentMapCacheManager.java
    @Nullable
    public Cache getCache(String name) {
        Cache cache = (Cache)this.cacheMap.get(name);
        if (cache == null && this.dynamic) {
            ConcurrentMap var3 = this.cacheMap;
            synchronized(this.cacheMap) {
                cache = (Cache)this.cacheMap.get(name);
                if (cache == null) {
                    cache = this.createConcurrentMapCache(name);
                    this.cacheMap.put(name, cache);
                }
            }
        }

        return cache;
    }
ConcurrentMapCache.java    
    private final ConcurrentMap store;

    @Nullable
    protected Object lookup(Object key) {
        return this.store.get(key);
    }

    public void put(Object key, @Nullable Object value) {
        this.store.put(key, this.toStoreValue(value));
    }

六、缓存运行流程

标注的方法执行之前先来检查缓存中有没有这个数据,默认按照参数的值作为key去查询缓存,如果没有就运行方法并将结果放入缓存,以后再来调用就可以直接使用缓存中的数据。

1.核心:

  • 1.使用CacheManager(ConcurrentMapCacheManager)按照名字得到Cache(ConcurrentMapCache)组件
  • 2.key使用KeyGenerator生成的,默认是使用SimpleKeyGenerator生成key

2.流程

  • 1.方法运行之前,先去查询Cache(缓存组件),按照cacheName指定的名字获取

CacheManager先获取相应的缓存,第一次获取缓存如果没有cache组件会自动创建

ConcurrentMapCacheManager.java
    @Nullable
    public Cache getCache(String name) {
        Cache cache = (Cache)this.cacheMap.get(name);
        if (cache == null && this.dynamic) {
            ConcurrentMap var3 = this.cacheMap;
            synchronized(this.cacheMap) {
                cache = (Cache)this.cacheMap.get(name);
                if (cache == null) {
                    cache = this.createConcurrentMapCache(name);
                    this.cacheMap.put(name, cache);
                }
            }
        }

        return cache;
    }
  • 2.使用一个key去cache中查找缓存的内容,key默认就是方法的参数

key是按照某种策略生成的,使用KeyGenerator生成的,默认是使用SimpleKeyGenerator生成key

SimpleKeyGenerator生成key的默认策略:

         如果没有参数 key = new SimpleKey();

         如果有一个参数 key = 参数的值;

         如果有多个参数 key = new SimpleKey(params);

ConcurrentMapCache.java   
    @Nullable
    protected Object lookup(Object key) {
        return this.store.get(key);
    }
CacheAspectSupport
    @Nullable
    private ValueWrapper findCachedItem(Collection contexts) {
        Object result = CacheOperationExpressionEvaluator.NO_RESULT;
        Iterator var3 = contexts.iterator();

        while(var3.hasNext()) {
            CacheAspectSupport.CacheOperationContext context = (CacheAspectSupport.CacheOperationContext)var3.next();
            if (this.isConditionPassing(context, result)) {
                Object key = this.generateKey(context, result);
                ValueWrapper cached = this.findInCaches(context, key);
                if (cached != null) {
                    return cached;
                }

                if (this.logger.isTraceEnabled()) {
                    this.logger.trace("No cache entry for key '" + key + "' in cache(s) " + context.getCacheNames());
                }
            }
        }

        return null;
    }
SimpleKeyGenerator.java
   public static Object generateKey(Object... params) {
        if (params.length == 0) {
            return SimpleKey.EMPTY;
        } else {
            if (params.length == 1) {
                Object param = params[0];
                if (param != null && !param.getClass().isArray()) {
                    return param;
                }
            }

            return new SimpleKey(params);
        }
    }
  • 3.没有查到缓存就调用目标方法
    @Cacheable(cacheNames = "emp")
    public Employee getEmp(Integer id){
        System.out.println("查询"+id+"号员工");
        Employee emp = employeeMapper.getEmpById(id);
        return emp;
    }
  • 4.将目标方法返回到结果放进缓存中
ConcurrentMapCache.java   
    public void put(Object key, @Nullable Object value) {
        this.store.put(key, this.toStoreValue(value));
    }

七、缓存注解的使用

  • 1.@Cacheable

     * @Cacheable 将方法的运行结果进行缓存;以后再要相同的数据,直接从缓存中获取,不用调用方法;
     * CacheManager管理多个Cache组件的,对缓存对真正CRUD操作在Cache组件中,每一个缓存组件有自己唯一一个名字
     * @Cacheable 几个属性:
     *     1.cacheNames/value:指定缓存组件的名字;将方法的返回结果放在哪个缓存中,是数组的方式,可以指定多个缓存;
     *     2.key:可以用它来指定缓存数据使用对key;默认是使用方法参数的值  1-方法的返回值
     *          编写SpEL;  key="#id";参数id的值;  #a0   #p0   #root.args[0]
     *     3.keyGenerator:key的生成器;可以自己指定key的生成器的组件id
     *          keyGenerator/key:二选一使用
     *     4.cacheManager:指定缓存管理器;cacheResolver:指定缓存解析器;
     *     5.condition:指定符合条件的情况下才缓存
     *          condition = "#id>0"
     *          condition = "#a0>1" :第一个参数的值>1的时候才进行缓存
     *          condition = "#a0>1 and #root.methodName eq 'aaa'"
     *     6.unless:否定缓存;当unless指定的条件为true,方法的返回值就不会被缓存;可以获取到结果进行判断
     *          unless = "#result==null"
     *          unless ="#a0==2";如果第一个参数的值是2,结果不缓存
     *     7.sync:是否使用异步模式

   /** 
     * @param id
     * @return
     * */
    @Cacheable(value = "emp",keyGenerator="myKeyGenerator",unless ="#a0==2")
    public Employee getEmp(Integer id){
        System.out.println("查询"+id+"号员工");
        Employee emp = employeeMapper.getEmpById(id);
        return emp;
    }
//自定义一个KeyGenerator
@Configuration
public class MyCacheConfig {
    @Bean("myKeyGenerator")
    public KeyGenerator keyGenerator(){
        return new KeyGenerator(){
            @Override
            public Object generate(Object o, Method method, Object... objects) {
                return method.getName()+"["+Arrays.asList(objects).toString() +"]";
            }
        };
    }
}
  • 2.@CachePut

     * @CachePut:既调用方法,又更新缓存数据,同步更新缓存
     * 修改来数据库的某个数据,同时更新缓存
     * 运行时机:
     *      1、先调用目标方法
     *      2、将目标方法缓存起来
     *
     * 测试步骤:
     *      1、查询1号员工:查到的结果会放在缓存中
     *            key:1  value:lastName:张三
     *      2、以后查询1号员工还是之前的结果
     *      3、更新1号员工:(lastName:王五;gender:0)
     *            将方法的返回值也放进缓存了
     *            key:传入的employee对象 值:返回的employee对象
     *      4、查询1号员工:是更新之前的结果,为什么?(1号员工没有在缓存中更新)
     *            key = "#employee.id":使用传入的参数的员工id
     *            key = "#result.id":使用返回后的id
     *                  @Cacheable的key是不能用#result

    @CachePut(value = "emp",key = "#employee.id")
    public  Employee updateEmp(Employee employee){
        System.out.println("update:"+employee);
        employeeMapper.updateEmp(employee);
        return employee;
    }
    @GetMapping("/emp")
    public Employee updateEmployee(Employee employee){
        Employee emp = employeeService.updateEmp(employee);
        return emp;
    }
  • 3.@CacheEvict

     * @CacheEvict:缓存清除
     *      key:指定要清除的数据
     *          key = "#id"
     *      allEntries = true:指定清除这个缓存中所有的数据
     *      beforeInvocation = false:缓存的清除是否在方法之前清除
     *          false:默认代表缓存清除操作是在方法执行之后执行;如果出现异常缓存就不会清除
     *          true:代表清除缓存操作是在方法运行之前执行,无论方法是否出现异常,缓存都清除

    @CacheEvict(value = "emp",allEntries = true)
    public void deleteEmp(Integer id){
        System.out.println("deleteEmp:"+id);
        //employeeMapper.deleteEmpById(id);
    }
    @GetMapping("/delemp")
    public String deleteEmployee(Integer id){
        employeeService.deleteEmp(id);
        return "success";
    }
  • 4.@Caching
定义复杂的缓存规则
    @Caching(
        cacheable = {
            @Cacheable(value = "emp",key="#lastName")
        },
        put = {
            @CachePut(value = "emp",key="#result.id")
        }
    )
    public Employee getEmpByLastName(String lastName){
        return employeeMapper.getEmpByLastName(lastName);
    }
    @GetMapping("/emp/lastname/{lastName}")
    public Employee getEmpByLastName(@PathVariable("lastName") String lastName){
        Employee employee = employeeService.getEmpByLastName(lastName);
        return employee;
    }
  • 5.@CacheConfig
抽取缓存的公共配置
@CacheConfig(cacheNames="emp") 
@Service 
public class EmployeeService {

 

你可能感兴趣的:(SpringBoot,SpringBoot内部原理)