SpringBoot与缓存使用及原理【各属性的分析】

一 JSR107

Java Caching定义了5个核心接口,分别是CachingProvider, CacheManager, Cache, Entry 和 Expiry。

  1. CachingProvider定义了创建、配置、获取、管理和控制多个CacheManager。一个应用可以在运行期访问多个CachingProvider。
  2. CacheManager定义了创建、配置、获取、管理和控制多个唯一命名的Cache,这些Cache存在于CacheManager的上下文中。一个CacheManager仅被一个CachingProvider所拥有。
  3. Cache是一个类似Map的数据结构并临时存储以Key为索引的值。一个Cache仅被一个CacheManager所拥有。
  4. Entry是一个存储在Cache中的key-value对.
  5. Expiry 每一个存储在Cache中的条目有一个定义的有效期。一旦超过这个时间,条目为过期的状态。一旦过期,条目将不可访问、更新和删除。缓存有效期可以通过ExpiryPolicy设置。

如下图所示:

在这里插入图片描述

二 Spring缓存抽象(下面会有具体Springboot代码演示)

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

  1. Cache接口为缓存的组件规范定义,包含缓存的各种操作集合;
  2. Cache接口下Spring提供了各种xxxCache的实现,如RedisCache,EhCacheCache , ConcurrentMapCache等;每次调用需要缓存功能的方法时,Spring会检查检查指定参数的指定的目标方法是否已经被调用过;如果有就直接从缓存中获取方法调用后的结果,如果没有就调用方法并缓存结果返回给用户。下次直接从缓存中获取
  3. 使用Spring缓存抽象时我们需要关注以下两点;
  • [1] 确定方法需要被缓存以及他们的缓存策略
  • [2] 从缓存中读取之前缓存存储的数据,如下图所示:
    在这里插入图片描述
    了解jdbc的朋友就会很清楚,这就跟面向jdbc编程是一个道理,统一一个规范,统一面向jdbc编程。

三 缓存注解(下面会有具体Springboot代码演示)

在这里插入图片描述
在这里插入图片描述
同样支持spel表达式
在这里插入图片描述

四 缓存使用(下面会有具体Springboot代码演示)

要在Springboot中使用缓存需要以下几步:

第一步: 导入spring-boot-starter-cache模块

第二步: @EnableCaching开启缓存

第三步: 使用缓存注解

1 首先在pom.xml文件中引入坐标地址

	    <dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-cache</artifactId>
		</dependency>

在这里插入图片描述
我们从图中可以看到cache这个模块导入进来了。

2 在主程序中开启缓存注解

@SpringBootApplication
@EnableCaching
public class Springboot07CacheApplication {

   public static void main(String[] args) {
      SpringApplication.run(Springboot07CacheApplication.class, args);
   }
}

3 开始测试代码,首先做好准备

javaBean如下:

package com.lxj.cache.bean;
import java.io.Serializable;
public class Employee  implements Serializable{
   private Integer id;
   private String lastName;
   private String email;
   private Integer gender; //性别 1男  0女
   private Integer dId; 
   public Employee() {}
   public Employee(Integer id, String lastName, String email, Integer gender, Integer dId) {
      super();
      this.id = id;
      this.lastName = lastName;
      this.email = email;
      this.gender = gender;
      this.dId = dId;
   } 
   public Integer getId() {
      return id;
   }
   public void setId(Integer id) {
      this.id = id;
   }
   public String getLastName() {
      return lastName;
   }
   public void setLastName(String lastName) {
      this.lastName = lastName;
   }
   public String getEmail() {
      return email;
   }
   public void setEmail(String email) {
      this.email = email;
   }
   public Integer getGender() {
      return gender;
   }
   public void setGender(Integer gender) {
      this.gender = gender;
   }
   public Integer getdId() {
      return dId;
   }
   public void setdId(Integer dId) {
      this.dId = dId;
   }
   @Override
   public String toString() {
      return "Employee [id=" + id + ", lastName=" + lastName + ", email=" + email + ", gender=" + gender + ", dId="
            + dId + "]";
   }
}

controller层如下:

@RestController
public class EmployeeController {
@Autowired
EmployService employService;
@GetMapping("/emp/{id}")
public Employee getEmpById(@PathVariable("id") Integer id) {
    Employee empById = employService.getEmpById(id);
    return empById;
    }
}

service层如下:

@Service
public class EmployService {

    @Autowired
    EmploeeMapper emploeeMapper;
 /**
 * 将方法的运行结果进行缓存,以后要相同的数据,直接从缓存中获取,不用调用方法
 * CacheManager管理多个Cache组件的,对缓存的真正CRUD操作在Cache组件中,每一个缓存组件有自己唯一的一个名字
 */
 //cacheNames和value都可以
    @Cacheable(cacheNames = {"emp"})
    public Employee getEmpById(Integer id){
        System.out.println("查询" + id + "号员工");
        Employee employee = emploeeMapper.getEmpById(id);
        return  employee;
    }
}

mapper接口如下:

@Mapper
public interface EmploeeMapper {
    @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 updateEmpById(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 InsertEmp(Employee employee);

    @Select("SELECT * FROM employee WHERE lastName = #{lastName}")
    public Employee getEmpByLastName(String lastName);
}

application.properties如下:

spring.datasource.username=root
spring.datasource.password=xxxxx
spring.datasource.url=jdbc:mysql://localhost:3306/springCache
spring.datasource.driver-class-name=com.mysql.jdbc.Driver

#开启驼峰命名法
mybatis.configuration.map-underscore-to-camel-case=true

#打印sql语句日志
logging.level.com.lxj.cache.mappers=debug

#控制台打印配置信息
debug=true

sql语句如下:

DROP TABLE IF EXISTS `employee`;
CREATE TABLE `employee` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `lastName` varchar(255) DEFAULT NULL,
  `email` varchar(255) DEFAULT NULL,
  `gender` int(2) DEFAULT NULL,
  `d_id` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

在这里插入图片描述
整个工程结构:
在这里插入图片描述

4 测试

运行项目,打开浏览器输入 http://localhost:8080/emp/1 ,可以看到浏览器返回的值
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
可以看到并没有查询数据库,而是从缓存中获取的数据,这说明缓存起作用了.
其工作流程参照下面:

  1. 方法运行之前,先去查询Cache(缓存组件),按照cacheNames指定的名字获取 (CacheManager先获取相应的缓存),第一次获取缓存如果没有Cache组件会自动创建。

  2. 去Cache中查找缓存的内容,使用一个key,默认就是方法的参数
        key是按照某种策略生成的,默认是使用keyGenerator生成的,默认使用SimpleKeyGenerator生成key
        SimpleKeyGenerator生成key的默认策略:
            如果没有参数:key=new SimpleKey()
            如果有一个参数:key=参数值(这里就是id的值作为key)
            如果有多个参数:key = new SimpleKey(params)

  3. 没有查到缓存就调用目标方法

  4. 将目标方法返回的结果,放进缓存中

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

核心:
1)使用CacheManager【ConcurrentMapCacheManager】按照名字获得Cache【ConcurrentMapCache】组件
2)key使用keyGenerator生成,默认是SimpleKeyGenerator

 *          原理
 *              1.自动配置类:CacheAutoConfigration
 *              2.缓存配置类
 *                   0 = "org.springframework.boot.autoconfigure.cache.GenericCacheConfiguration"
 *                   1 = "org.springframework.boot.autoconfigure.cache.JCacheCacheConfiguration"
 *                   2 = "org.springframework.boot.autoconfigure.cache.EhCacheCacheConfiguration"
 *                   3 = "org.springframework.boot.autoconfigure.cache.HazelcastCacheConfiguration"
 *                   4 = "org.springframework.boot.autoconfigure.cache.InfinispanCacheConfiguration"
 *                   5 = "org.springframework.boot.autoconfigure.cache.CouchbaseCacheConfiguration"
 *                   6 = "org.springframework.boot.autoconfigure.cache.RedisCacheConfiguration"
 *                   7 = "org.springframework.boot.autoconfigure.cache.CaffeineCacheConfiguration"
 *                   8 = "org.springframework.boot.autoconfigure.cache.GuavaCacheConfiguration"
 *                   9 = "org.springframework.boot.autoconfigure.cache.SimpleCacheConfiguration"
 *                   10 = "org.springframework.boot.autoconfigure.cache.NoOpCacheConfiguration"
 *              3.哪个配置类默认生效:SimpleCacheConfiguration
 *              4.给容器中注册了一个CacheManager:ConcurrentMapCacheManager
 *              5.可以获取和创建ConcurrentMapCache类型的缓存组件;他的作用是将数据保存在ConcureentMap

几个属性:

    cacheNames/value:指定缓存组件的名字;将方法的返回结果放在哪个缓存中,是数组的方式,可以指定多个缓存.
<----------------------------------------------------------分隔符----------------------------------------------------------------->
 

     key:缓存数据使用的key:可以用他来指定。不指定默认是使用方法参数的值    eg:1-方法的返回值 
      编写SpEL; #id;参数id的值   #a0 #p0 #root.args[0](  这种写法参见上面的图SpEL表达式)
        	
			eg:@Cacheable(cacheNames = {"emp"},key = "#root.methodName + '[' + #id +']' ")
      						  此时可以看到该缓存中key的值为:  ey=“getEmp[1]”
<----------------------------------------------------------分隔符----------------------------------------------------------------->    
 
         * keyGenerator:key生成器;可以自己指定key的生成器组件id
         *                  key和keyGenerator二选一使用
 在这里要写一个MyCacheConfig类

 package nju.software.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 {
    @Bean("myKeyGenerator")
    public KeyGenerator keyGenerator(){
       return new KeyGenerator(){
            @Override
            public Object generate(Object o, Method method, Object... objects) {
                return method.getName()+ Arrays.asList() ;
            }
        };
    }
	//在service的方法上指定自定义的key
   @Cacheable(cacheNames = {"emp"},keyGenerator = “myKeyGenerator")
   							此时的key= getEmp[1] (Arrays.asList()自己带了【】)

 <----------------------------------------------------------分隔符----------------------------------------------------------------->    
 
         *          cacheManager:指定缓存管理器;或者cacheResolver指定获取解析器
 
 <----------------------------------------------------------分隔符----------------------------------------------------------------->    
         *          condition:指定符合条件的情况下才缓存;
         * 			condition = "#a0 > 0" 代表打一个参数的值大于0才缓存
           @Cacheable(cacheNames = {"emp"},keyGenerator = “myKeyGenerator",condition = "#id > 0")
           or
             @Cacheable(cacheNames = {"emp"},keyGenerator = “myKeyGenerator", condition = "#a0 > 0")  //SpEL表达式
 <----------------------------------------------------------分隔符----------------------------------------------------------------->    
     
         *          unless:否定缓存:当unless指定的条件为true,方法的返回值就不会被缓存;可以获取到结果进行判断
           @Cacheable(cacheNames = {"emp"},keyGenerator = “myKeyGenerator", condition = "#a0 > 0" unless = ‘#a0==1’)  
         										 如果第一个参数是1就不缓存
           
 <----------------------------------------------------------分隔符----------------------------------------------------------------->    

         *          sync:是否使用异步模式

@CachePut的功能

 @Cacheable(cacheNames = {"emp"})
    public Employee getEmp(Integer id){
        System.out.println("查询" + id + "号员工");
        Employee emp = employeeMapper.getEmpById(id);
        return emp;
    }
     @CachePut(value = "emp",key = "#result.id")
        public Employee updateEmp(Employee employee){
            System.out.println("updateEmp" +employee);
            employeeMapper.updateEmp(employee);
            return employee;
        }

对应的controller代码

@GetMapping("/emp")
    public Employee update(Employee employee){
        Employee emp = employeeService.updateEmp(employee);
        return emp;
    }

测试的步骤:

  1. 查询1号员工,查到的结果会放在缓存中;
    key:1 value:lastName:张三
  2. 以后查询还是之前的结果
  3. 更新1号员工【lastName:zhangsan;gender:2】
  4. 查询1号员工?
    应该是更新后的员工
    key = ”#employee.id":使用传入的参数的员工id
    key = “#result.id”;使用返回后的id

@CacheEvict的功能

      /**
      *     @CacheEvict:缓存清楚
      * allEntries = true 全部删除
      */ 
      @CacheEvict(value = "emp",key = "#id")
        public void deleteEmp(Integer id){
            System.out.println("deleteEmp" + id);
        }

@Caching的功能

@Caching(
            cacheable = {
                    @Cacheable(value = "emp",key = "#lastName")
            },
            put = {
                    @CachePut(value = "emp",key = "#result.id"),
                    @CachePut(value = "emp",key = "#result.email"),
            }
    )
    public Employee getEmployeeByLastName(String lastName){
        return employeeMapper.getEmployeeByLastName(lastName);
    }

此时通过lastName来访问数据库,会把对应的返回结果的id和email更新到这个缓存,所以下次在通过id和email来访问这个对象是不需要经过数据库的。

@Cacheable(cacheNames = “emp”)的功能
在service上直接声明,其下的方法上的注解都可以不写value值了

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值