SpringBoot缓存

SpringBoot缓存

SpringBoot默认使用的是ConcurrentMapCache作缓存(由ConcurrentMapCacheManager创建),ConcurrentMapCache是将数据存储在ConcurrentMap< Object, Object>中

一、使用IDEA创建一个SpringBoot项目

创建的Springboot版本是1.5.15.RELEASE

二、项目开发
  1. 使用Mybatis操作数据库,在pom.xml中引入mybatis、Mysql和Cache依赖

    <!-- cache依赖 -->
    <dependency>
       <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-starter-cache</artifactId>
    </dependency>
    
    <!-- mybatis依赖 -->
    <dependency>
       <groupId>org.mybatis.spring.boot</groupId>
       <artifactId>mybatis-spring-boot-starter</artifactId>
       <version>1.3.2</version>
    </dependency>
    
    <!-- mysql连接依赖 -->
    <dependency>
       <groupId>mysql</groupId>
       <artifactId>mysql-connector-java</artifactId>
       <scope>runtime</scope>
    </dependency>
  2. 在数据库中创建两张表,employee和department,数据库的名字是springboot_cache

 SET FOREIGN_KEY_CHECKS=0;

   -- ----------------------------
   -- Table structure for department
   -- ----------------------------
   DROP TABLE IF EXISTS `department`;
   CREATE TABLE `department` (
     `id` int(11) NOT NULL AUTO_INCREMENT,
     `departmentName` varchar(255) DEFAULT NULL,
     PRIMARY KEY (`id`)
   ) ENGINE=InnoDB DEFAULT CHARSET=utf8;

   -- ----------------------------
   -- Table structure for employee
   -- ----------------------------
   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;
  1. 在项目中创建名字为bean的package,编写employee表和department表对应的实体类,最好重写一下toString()方法

  2. 在项目中创建名字为mapper的package,用于存放EmployeeMapper和DepartmentMapper

    public interface EmployeeMapper {
    
       @Select("select * from employee")
       public List<Employee> getAll();
    
       @Select("select * from employee where id=#{id}")
       public Employee getById(Integer id);
    
       @Options(useGeneratedKeys = true, keyProperty = "id")
       @Insert("insert into employee(lastName, email, gender, d_id) values(#{lastName}, #{email}, #{gender}, #{dId})")
       public Integer save(Employee employee);
    
       @Update("update employee set lastName=#{lastName}, email=#{email}, gender=#{gender}, d_id=#{dId} where id=#{id}")
       public Integer update(Employee employee);
    
       @Delete("delete from employee where id=#{id}")
       public Integer delete(Integer id);
    }
  3. 在启动类上添加@MapperScan注解和@EnableCache注解

    // @MapperScan的作用是将指定包中的mapper扫描进spring容器中,也可以在每个mapper上添加@Mapper注解,作用相同
    @MapperScan("com.example.cache.mapper")
    // @EnableCaching的作用是开启缓存
    @EnableCaching
    @SpringBootApplication
    public class SpringBoot09CacheApplication {
    
       public static void main(String[] args) {
           SpringApplication.run(SpringBoot09CacheApplication.class, args);
       }
    }
  4. 编写EmployeeService

    @Service
    public class EmployeeService {
    
       @Autowired
       private EmployeeMapper employeeMapper;
    
       private final static Logger logger = LoggerFactory.getLogger(EmployeeService.class);
    
       @Cacheable(cacheNames="emp")
       public List<Employee> getAll() {
    
           logger.info("【获取所有员工信息】");
           return employeeMapper.getAll();
       }
    
       @Cacheable(cacheNames="emp")
       public Employee getById(Integer id) {
    
           logger.info("【获取id={}的员工信息】");
           return employeeMapper.getById(id);
       }
    
       @CachePut(cacheNames="emp")
       public Integer save(Employee employee) {
    
           logger.info("【新增员工,{}】", employee);
           return employeeMapper.save(employee);
       }
    
       @CachePut(cacheNames="emp")
       public Integer update(Employee employee) {
    
           logger.info("更新员工信息,{}", employee);
           return employeeMapper.update(employee);
       }
    
       @CacheEvict(cacheNames="emp")
       public Integer delete(Integer id) {
    
           logger.info("删除id={}的员工信息", id);
           return employeeMapper.delete(id);
       }
    }
  5. 编写EmployeeController

    @RestController
    public class EmployeeController {
    
       @Autowired
       private EmployeeService employeeService;
    
       @GetMapping("/emps")
       public List<Employee> getAll() {
    
           return employeeService.getAll();
       }
    
       @GetMapping("/emp/{id}")
       public Employee getById(@PathVariable("id") Integer id) {
    
           return employeeService.getById(id);
       }
    
       @PostMapping("/emp")
       public Integer save(Employee employee) {
    
           return employeeService.save(employee);
       }
    
       @PutMapping("/emp")
       public Integer update(Employee employee) {
    
           return employeeService.update(employee);
       }
    
       @DeleteMapping("/emp/{id}")
       public Integer delete(@PathVariable("id") Integer id) {
    
           return employeeService.delete(id);
       }
    }
  6. 测试:

    ​ 当我们第一次发出http://localhost:8080/emps 请求时会执行EmployeeService中的getAll()方法,但是之后再请求就不会执行,而是直接从缓存中获取数据,而不是从数据库中获取

三、Springboot中cache的使用详解

几个重要概念&缓存注解
这里写图片描述

概念图:

这里写图片描述

在Springboot中要使用cache,首先要在启动类上添加@EnableCaching注解,启用缓存

springboot中与cache有关的注解有五个,分别是@Cacheable@CachePut@CacheEvict@Caching@CacheConfig,下面我们解释一下这五个注解的作用

  • @Cacheable注解

    @Cacheable执行的时机是在目标方法执行之前

    作用是将方法的返回值存储在缓存中,以后再要相同的数据就直接从缓存中获取,不用执行方法操作数据库;

    进入Cacheable源码中查看,可以发现Cacheable中有几个核心的属性,分别是:

    • cacheNames/value:指定缓存组件的名字(必须有),cacheNames与value的作用一样,二选一使用;

    ​ 每个CacheManager可以管理多个Cache组件(缓存组件),真正对缓存进行CRUD操作的是Cache组件;每一个Cache组件有一个唯一的名字,可以通过cacheNames/value指定;每个Cache组件中存储多个键值对(key/value),我们可以通过key获取value,而value中存放的是我们要存储的数据

    // cache组件的名字为emp
    @Cacheable(cacheNames = "emp")
    public Employee getById(Integer id) {
        // ...
    }
    • key:缓存数据使用的key,可以通过该属性指定;我们可以通过key获取存储在Cache中的数据;key默认是方法的参数的值,value默认是方法的返回值

    我们也可以使用SpEL表达式自己指定key的名字:

    // 使用方法的参数列表中名字为id的参数的值作为要存储数据的key,value为方法的返回值
    @Cacheable(cacheNames = "emp", key = "#id")
    public Employee getById(Integer id) {

    方法名[id]格式的key,例如:getById[2]

    @Cacheable(cacheNames = "emp", key = "#root.methodName+'['+#id+']'")
    public Employee getById(Integer id) {

    可以使用的SpEL表达式:

    名字位置描述示例
    methodNameroot object当前被调用的方法名root.methodName
    methodroot object当前被调用的方法root.method.name
    targetroot object当前被调用的目标对象root.target
    targetClassroot object当前被调用的目标对象类root.targetClass
    argsroot object当前被调用的方法的参数列表root.args[0]
    cachesroot object当前方法调用使用的缓存列表(如@Cacheable(value={“cache1”, “cache2”})),则有两个cacheroot.caches[0].name
    argument nameevaluation context方法参数的名字. 可以直接#参数名,也可以使用#p0或#a0 的形式,0代表参数的索引;id、#username 、#a0、#p0
    resultevaluation context方法执行后的返回值(仅当方法执行之后的判断有效,如‘unless’,’cache put’的表达式’cache evict’的表达式beforeInvocation=false)result
    • keyGenerator:key的生成策略,可以自己写一个生成器放入到spring容器中,然后使用keyGenerator指定使用我们自己的生成器,key和keyGenerator两个属性二选一使用

    自定义KeyGenerator:

    import org.springframework.cache.interceptor.KeyGenerator;
    @Configuration
    public class CacheConfiguration {
    
        /**
         * 自己定义Cache中存储的数据的key的生成策略
         * @return
         */
        @Bean("myKeyGenerator")
        public KeyGenerator myKeyGenerator() {
    
            KeyGenerator keyGenerator = new KeyGenerator() {
                @Override
                public Object generate(Object o, Method method, Object... objects) {
    //                return method.getName() + "[" + Arrays.asList(objects).get(0) + "]";
                    return method.getName() + "[" + SimpleKeyGenerator.generateKey(objects) + "]";
                }
            };
    
            return keyGenerator;
        }
    }

    使用自定义的KeyGenerator:

    @Cacheable(cacheNames = "emp",keyGenerator = "myKeyGenerator")
    public Employee getById(Integer id) {
    • cacheManager/cacheResolver:缓存管理器/缓存解析器,可以指定从哪个缓存管理器/缓存解析器中获取缓存组件,cacheManager和cacheResolver两个属性二选一使用

    • condition:满足指定条件的情况下才缓存,可以使用SpEl表达式,但是不能使用#result(使用condition=”#result!=null”不会生效)

    // 参数id>0的情况下才缓存返回的数据
    @Cacheable(cacheNames = "emp", key = "#id", condition = "#id>0")
    public Employee getById(Integer id) {

    condition中也可以使用and、or、not、eq等逻辑表达式,例如:

    condition = “#id>0 and #id<100 and #root.methodName eq ‘getById’”

    • unless:否定缓存,与condition相反,当unless指定的条件为true时,方法的返回值不会被缓存,可以使用SpEl表达式,并且可以使用#result获取返回的结果
    // 返回结果为null的情况下不缓存返回的数据
    @Cacheable(cacheNames = "emp", key = "#id", unless = "#result==null")
    public Employee getById(Integer id) {
    • sync:是否支持异步,异步模式下不支持unless
  • @CachePut注解

    @CachePut执行的时机是在目标方法执行之后

    作用是将既调用方法,又更新缓存数据,实现缓存和数据库的同步更新;在目标方法执行结束之后,将返回值存储在cache中

    进入CachePut源码中查看,可以发现CachePut的属性与Cacheable中的属性是一样的,作用也都相同;但是CachePut与Cacheable中key属性是有区别的,CachePut中的key属性中可以使用#result,而Cacheable中的key属性中不能使用#result(#result不生效),原因是CachePut的执行时机是在目标方法执行完之后,所以可以得到result,而Cacheable的执行时机是在目标方法执行之前,此时是获取不到result的

    还需要注意的是@CachePut中的key要与@Cacheable中的key一致,才能实现数据库与缓存的同步更新

    错误的使用方式:

    @Cacheable(cacheNames = "emp")
    public Employee getById(Integer id) {
    
      logger.info("【获取id={}的员工信息】");
      return employeeMapper.getById(id);
    }
    
    @CachePut(cacheManager = "emp")
    public Employee update(Employee employee) {
    
      logger.info("【新增员工,{}】", employee);
      employeeMapper.save(employee);
    
      return employee;
    }

    错误原因分析:

    1、查询1号员工;查到的结果会放在缓存中;
    key:1 value:employee对象(lastName=”zhangsan”; ….)
    2、以后查询还是之前的结果,value:employee对象(lastName=”zhangsan”,….)
    3、更新1号员工;【lastName:张三;gender:0】
    将方法的返回值也放进缓存了;
    key:传入的employee对象 value:employee对象(lastName:张三;gender:0; ….)
    4、查询1号员工?

    ​ 返回的是没有更新前的1号员工信息

    为什么是没更新前的?【1号员工没有在缓存中更新】

    原因是查询时的key与更新时使用的key不一样

    解决办法:将CachePut中的key设置成 key = “#employee.id”或key = “#result.id” (@Cacheable的key是不能用#result)

    正确的使用方式:

    // 默认使用参数值作为key
    @Cacheable(cacheNames = "emp")
    public Employee getById(Integer id) {
    
      logger.info("【获取id={}的员工信息】");
      return employeeMapper.getById(id);
    }
    
    // 同样使用id作为key,与Cacheable中的key保持一致
    @CachePut(cacheManager = "emp", key="#employee.id")
    public Employee update(Employee employee) {
    
      logger.info("【新增员工,{}】", employee);
      employeeMapper.save(employee);
    
      return employee;
    }
  • @CacheEvict注解

    @CacheEvict可以在目标方法之前或之后执行,可以通过属性beforeInvocation指定,默认是在目标方法执行完之后执行

    作用是清除缓存

    与Cacheable和CachePut相比,CacheEvict中多了两个属性,分别是allEntries和beforeInvocation

    • allEntries:是否清除这个缓存中所有的数据,默认为false,为true时清除这个缓存中的所有数据;与key属性是二选一,key是清除指定的数据
    • beforeInvocation:缓存的清除是否在目标方法之前执行,默认为false,表示在目标方法执行完之后执行清除缓存的操作,此时,如果目标方法执行的过程中出现异常,缓存就不会清除;为true时,表示在目标方法执行之前执行清除缓存操作,无论方法是否出现异常,缓存都会被清除

    同样,CacheEvict中的key要与Cacheable中的key一致

    @Cacheable(cacheNames = "emp", key="'emp_'+#id")
    public Employee getById(Integer id) {
    
      logger.info("【获取id={}的员工信息】");
      return employeeMapper.getById(id);
    }
    
    @CacheEvict(cacheNames = "emp", key = "'emp_'+#id", allEntries = false, beforeInvocation = false)
    public Integer delete(Integer id) {
    
      logger.info("删除id={}的员工信息", id);
      return employeeMapper.delete(id);
    }
  • @Caching注解

    @Caching是Cacheable、CachePut和CacheEvict三个注解的组合注解,用于定义复杂的缓存规则

    java
    @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);
    }

  • @CacheConfig注解

    标注在类上面,作用是抽取这个类中的缓存的公共配置

    // 表示该类中的所有缓存数据都从emp缓存组件中获取,类方法上的Cacheable、CachePut和CacheEvict注解就不需要再指定cacheNames了;CacheConfig中只有cacheNames属性而没有value属性(cacheNames与value属性的作用是一样的)
    @CacheConfig(cacheNames="emp") 
    @Service
    public class EmployeeService {
    
    @Cacheable
      public List<Employee> getAll() {
    
          logger.info("【获取所有员工信息】");
          return employeeMapper.getAll();
      }
    }
四、原理分析

1、SpringBoot中的缓存自动配置类是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、没有查到缓存就执行目标方法,然后将目标方法返回的结果放进缓存中;如果查到了缓存就直接返回,不执行目标方法

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

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

  • 2
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值