【Spring Boot】17.缓存

简介

在讲解Springboot相关组件以前,咱们先要了解在java的缓存插件体系,相似于咱们以前学习数据操做的数据源规范同样,也有一个标准,这个标准叫作JSR107,咱们能够经过在网上查询相关的信息。java

JSR107

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

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

1 应用程序缓存架构

图1

2 spring的缓存抽象

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

Cache接口为缓存的组件规范定义,包含缓存的各类操做集合; Cache接口下Spring提供了各类xxxCache的实现;如RedisCache,EhCacheCache , ConcurrentMapCache等;redis

每次调用须要缓存功能的方法时,Spring会检查检查指定参数的指定的目标方法是否已经被调用过;若是有就直接从缓存中获取方法调用后的结果,若是没有就调用方法并缓存结果后返回给用户。下次调用直接从缓存中获取。 使用Spring缓存抽象时咱们须要关注如下两点;spring

  1. 肯定方法须要被缓存以及他们的缓存策略
  2. 从缓存中读取以前缓存存储的数据

3 经常使用注解

在咱们开发应用程序中,比较经常使用的注解以下所示: ||| |-|-| |Cache |缓存接口,定义缓存操做。实现有:RedisCache、EhCacheCache、ConcurrentMapCache等| |CacheManager |缓存管理器,管理各类缓存(Cache)组件| |@Cacheable |主要针对方法配置,可以根据方法的请求参数对其结果进行缓存| |@CacheEvict |清空缓存| |@CachePut |保证方法被调用,又但愿结果被缓存。| |@EnableCaching |开启基于注解的缓存| |@keyGenerator |缓存数据时key生成策略| |@serialize |缓存数据时value序列化策略|sql

4 缓存使用测试

接下来咱们编写一个应用程序体验一下springboot中缓存的使用。docker

  1. 搭建基本环境
  • 建立项目,选择模块:cache web mysql mybatis
  • 导入数据库文件到数据库joyblack,脚本内容以下(核心章节咱们用的数据库)
joyblack.yml
/*
Navicat MySQL Data Transfer

Source Server         : docker
Source Server Version : 50505
Source Host           : 10.21.1.47:3306
Source Database       : joyblack

Target Server Type    : MYSQL
Target Server Version : 50505
File Encoding         : 65001

Date: 2018-12-20 09:45:44
*/

SET FOREIGN_KEY_CHECKS=0;

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

-- ----------------------------
-- Records of department
-- ----------------------------
INSERT INTO `department` VALUES ('1', '乡下冒险者公会');
INSERT INTO `department` VALUES ('2', '城市冒险者公会');

-- ----------------------------
-- Table structure for `user`
-- ----------------------------
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
  `id` int(11) NOT NULL,
  `user_name` varchar(20) NOT NULL,
  `login_name` varchar(20) NOT NULL,
  `department_id` int(11) NOT NULL DEFAULT 1,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

-- ----------------------------
-- Records of user
-- ----------------------------
INSERT INTO `user` VALUES ('1', '阿库娅', 'akuya', '1');
INSERT INTO `user` VALUES ('2', '克里斯汀娜', 'crustina', '1');
INSERT INTO `user` VALUES ('3', '惠惠', 'huihui', '1');
  1. 整合mybatis操做数据库(核心章节14.整和mybatis),咱们采用基于注解的方式使用。
pom.xml
<dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-cache</artifactId>
    </dependency>
application.yml
spring:
  datasource:
    username: root
    password: 123456
    url: jdbc:mysql://127.0.0.1:3306/joyblack?characterEncoding=utf8&serverTimezone=GMT
    driver-class-name: com.mysql.cj.jdbc.Driver
server:
  port: 8090
mybatis:
  configuration:
    # 开启驼峰映射规则
    map-underscore-to-camel-case: true
bean/User.class
package com.zhaoyi.aweb.bean;

public class User {
    private Integer id;
    private String loginName;
    private String userName;
    private Integer departmentId;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getLoginName() {
        return loginName;
    }

    public void setLoginName(String loginName) {
        this.loginName = loginName;
    }

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    public Integer getDepartmentId() {
        return departmentId;
    }

    public void setDepartmentId(Integer departmentId) {
        this.departmentId = departmentId;
    }
}
bean/Department.class
package com.zhaoyi.aweb.bean;

public class Department {
    private Integer id;
    private String departmentName;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getDepartmentName() {
        return departmentName;
    }

    public void setDepartmentName(String departmentName) {
        this.departmentName = departmentName;
    }
}
mapper/UserMapper.class
package com.zhaoyi.aweb.mapper;

import com.zhaoyi.aweb.bean.User;
import org.apache.ibatis.annotations.Delete;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.Update;

public interface UserMapper {

    @Insert(" insert into user(id, login_name, user_name, department_id) values (#{id}, #{loginName}, #{userName}, #{departmentId})")
    int insertUser(User user);

    @Delete("delete from user where id = #{id}")
    int deleteUser(Integer id);

    @Select("select * from user where id = #{id}")
    User getUserById(Integer id);

    @Update("update user set user_name = #{userName} where id = #{id}")
    int updateUser(User user);
}
mapper/DepartmentMapper.class
package com.zhaoyi.aweb.mapper;

import com.zhaoyi.aweb.bean.Department;
import org.apache.ibatis.annotations.*;

public interface DepartmentMapper {

    // insert a derpartment.
    // @Options(useGeneratedKeys = true, keyProperty = "id") may you want get insert data generated id.
    @Insert("insert into department(id,department_name) values(#{id}, #{departmentName})")
    int insertDepartment(Department department);

    // delete a department by id.
    @Insert("delete from department where id = #{id}")
    int deleteDepartment(Integer id);

    // query a department by id.
    @Select("select * from department where id = #{id}")
    Department getDepartmentById(Integer id);

    // update a department information.
    @Update("update department set department_name=#{departmentName} where id=#{id}")
    int updateDepartment(Department department);
}
  1. 因为mapper中没有添加@Mapper注解,咱们要告诉springboot扫描对应的mapper包
aweb/SpringBootApplication.class
package com.zhaoyi.aweb;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@MapperScan(value = "com.zhaoyi.aweb.mapper")
@SpringBootApplication
public class AwebApplication {
    public static void main(String[] args) {
        SpringApplication.run(AwebApplication.class, args);
    }

}
  1. 接下来,编写一个controller测试一下环境是否OK
controller/UserController.class
package com.zhaoyi.aweb.controller;

import com.zhaoyi.aweb.bean.User;
import com.zhaoyi.aweb.mapper.UserMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class UserController {

    @Autowired
    private UserMapper userMapper;

    @RequestMapping("/user/insert")
    public User insertUser(User user){
        userMapper.insertUser(user);
        return user;
    }

    @RequestMapping("/user/delete/{id}")
    public Integer insertUser(@PathVariable("id") Integer id){
        return userMapper.deleteUser(id);
    }

    @RequestMapping("/user/select/{id}")
    public User getUser(@PathVariable("id") Integer id){
       return userMapper.getUserById(id);
    }

    @RequestMapping("/user/update")
    public User updateUser(User user){
        userMapper.updateUser(user);
        return user;
    }
}

访问 localhost:8090/user/select/1,获得:数据库

{"id":1,"loginName":"akuya","userName":"阿库娅","departmentId":1}
  1. 编写一个service

前面咱们至关于复习了一遍以前的操做,咱们如今先作一下改变,通常来讲,controller调用的service相关的东西,所以,咱们将对mapper的操做提到servicer一层,咱们添加一个service包:express

service.UserService.class
package com.zhaoyi.aweb.service;

import com.zhaoyi.aweb.mapper.UserMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.zhaoyi.aweb.bean.User;

@Service
public class UserService {
    @Autowired
    private UserMapper userMapper;

    public User getUser(Integer id){
        System.out.println("查询:" + id);
        return userMapper.getUserById(id);
    }
}

接下来,咱们从新写一下controller的方法,以下所示:apache

controller/UserController.class
package com.zhaoyi.aweb.controller;

import com.zhaoyi.aweb.bean.User;
import com.zhaoyi.aweb.mapper.UserMapper;
import com.zhaoyi.aweb.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class UserController {
    @Autowired
    private UserService userService;

    @RequestMapping("/user/select/{id}")
    public User getUser(@PathVariable("id") Integer id){
       return userService.getUser(id);
    }

}
  1. 开启缓存相关配置

这样,咱们就保证controller只和service进行交互了。咱们开始新知识了,通常使用缓存,咱们须要以下步骤:

  • 开启基于注解的缓存;
  • 标注缓存注解。

很简单吧?

如今,咱们每访问一次select url,都会在控制台打印一次

查询:1

也就是说,当前都会调用service的getUser在数据库进行查询操做。接下来咱们为该方法提供缓存效果,即在启动类中添加注解@EnableCaching

AwebApplication.class
package com.zhaoyi.aweb;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;

@EnableCaching
@MapperScan(value = "com.zhaoyi.aweb.mapper")
@SpringBootApplication
public class AwebApplication {
    public static void main(String[] args) {
        SpringApplication.run(AwebApplication.class, args);
    }

}

而后咱们去标注缓存注解在对应的服务方法上

service/UserService.class
@Cacheable(value = {"user"})
    public User getUser(Integer id){
        System.out.println("查询:" + id);
        return userMapper.getUserById(id);
    }

这一次咱们再次访问,发现除了第一次会打印查询记录以外,其余的查询都不会打印了(一个id只会进行一次查询,即第一次),显然已经作了缓存了。

5 缓存工做原理

仍是得先从自动配置类源码入手。

CacheAutoConfiguration.class
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package org.springframework.boot.autoconfigure.cache;

import java.util.List;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.couchbase.CouchbaseAutoConfiguration;
import org.springframework.boot.autoconfigure.data.jpa.EntityManagerFactoryDependsOnPostProcessor;
import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
import org.springframework.boot.autoconfigure.hazelcast.HazelcastAutoConfiguration;
import org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cache.CacheManager;
import org.springframework.cache.interceptor.CacheAspectSupport;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.ImportSelector;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.orm.jpa.AbstractEntityManagerFactoryBean;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.util.Assert;

@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 {
    public CacheAutoConfiguration() {
    }

    @Bean
    @ConditionalOnMissingBean
    public CacheManagerCustomizers cacheManagerCustomizers(ObjectProvider<CacheManagerCustomizer<?>> customizers) {
        return new CacheManagerCustomizers((List)customizers.orderedStream().collect(Collectors.toList()));
    }

    @Bean
    public CacheAutoConfiguration.CacheManagerValidator cacheAutoConfigurationValidator(CacheProperties cacheProperties, ObjectProvider<CacheManager> cacheManager) {
        return new CacheAutoConfiguration.CacheManagerValidator(cacheProperties, cacheManager);
    }

    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;
        }
    }

    static class CacheManagerValidator implements InitializingBean {
        private final CacheProperties cacheProperties;
        private final ObjectProvider<CacheManager> cacheManager;

        CacheManagerValidator(CacheProperties cacheProperties, ObjectProvider<CacheManager> cacheManager) {
            this.cacheProperties = cacheProperties;
            this.cacheManager = cacheManager;
        }

        public void afterPropertiesSet() {
            Assert.notNull(this.cacheManager.getIfAvailable(), () -> {
                return "No cache manager could be auto-configured, check your configuration (caching type is '" + this.cacheProperties.getType() + "')";
            });
        }
    }

    @Configuration
    @ConditionalOnClass({LocalContainerEntityManagerFactoryBean.class})
    @ConditionalOnBean({AbstractEntityManagerFactoryBean.class})
    protected static class CacheManagerJpaDependencyConfiguration extends EntityManagerFactoryDependsOnPostProcessor {
        public CacheManagerJpaDependencyConfiguration() {
            super(new String[]{"cacheManager"});
        }
    }
}

咱们经过打断点方式,能够查看配置相关的信息,这里就不一一列出了。其运行过程大体以下:

  1. 方法运行以前,先查询Cache(缓存组件),按照@CacheName指定的名字获取(``CacheManager`获取相应的缓存);第一次获取缓存时,若是没有对应的组件,会先自动建立。 总之,第一步获取了一个缓存组件。

  2. 在组件中经过咱们提供的key,默认是方法的参数,以后,在缓存内部,根据咱们提供的key又根据某种策略生成内部的key,默认使用SimpleKeyGenerator生成key(若是没有参数:key = new SimpleKey(),若是有一个参数,key=参数的值,多个参数 key = SimpleKey(param))查询对应的值。

  3. 若是没有从对应的key中查到值,就会调用目标方法(缓存标注的方法)返回的结果放到缓存(key对应的值)之中;若是有值,则直接将结果返回。

@Cacheable标注的方法执行以前先来检查缓存中有没有这个数据,默认按照参数的值查找缓存,若是没有就运行方法结果放入缓存。之后再次调用就能够直接使用缓存中的数据。

核心:

  1. 使用CacheManager按照名字获得Cache组件,若没有配置,默认就是ConcurrentMapCacheManager组件;

  2. key是使用KeyGenerator生成的,默认使用SimpleKeyGenerator;

6 @Cacheable分析

CacheEnable主要用于标准方法,表示对该方法的返回结果进行缓存保存。

经过观察CacheEnable源码以下所示:

//
// 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 "";

    String keyGenerator() default "";

    String cacheManager() default "";

    String cacheResolver() default "";

    String condition() default "";

    String unless() default "";

    boolean sync() default false;
}

从中能够查询到咱们能够定制的属性8个,其中valuecacheNames是相同功效的两个注解参数。咱们接下来分析几个经常使用的,cacheManager放到后面。

  1. value&cacheNames 缓存组件的名称,在 spring 配置文件中定义,必须指定至少一个。
@Cacheable(value="mycache")

他等同于

 @Cacheable(value={"cache1","cache2"}
  1. key 缓存的 key,能够为空,若是指定要按照 SpEL 表达式编写,若是不指定,则缺省按照方法的全部参数进行组合。
@Cacheable(value = {"user"}, key="#root.methodName+'[' + #id + ']'")

此方法自定义了key的值为:methodname[id]。其中id为咱们传入的参数的值。

其语法特色遵循以下取值方式: |名字| 位置 |描述 |示例| |-|-|-|-| |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", "cache2"})),则有两个cache #root.caches[0].name |argument name |evaluation context| 方法参数的名字. 能够直接 #参数名 ,也可使用 #p0或#a0 的形式,0表明参数的索引; #iban 、 #a0 、  #p0  |result |evaluation context| 方法执行后的返回值(仅当方法执行以后的判断有效,如‘unless’,’cache put’的表达式 ’cache evict’的表达式beforeInvocation=false) #result ||||

  1. keyGenerator 和key二选一,指定key的生成策略,即用于指定自定义key生成器(KeyGenerator)。

咱们来测试一下这个功能,首先编写配置文件

config/CacheConfig.class
package com.zhaoyi.aweb.config;

import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.lang.reflect.Method;

@Configuration
public class CacheConfig {

    @Bean
    public KeyGenerator keyGenerator(){
        return new KeyGenerator() {
            @Override
            public Object generate(Object o, Method method, Object... objects) {
                return method.getName() + "[" + Arrays.asList(objects).toString()  + "]";
            }
        };
    }
}

咱们自定义了一个key的生成器,接下来只须要在缓存注解处标注咱们定义的生成器便可:

service/UserService.class
@Cacheable(value = {"user"}, keyGenerator="keyGenerator")
    public User getUser(Integer id){
        System.out.println("查询:" + id);
        return userMapper.getUserById(id);
    }

keyGenerator在孤独部分的是咱们加入容器的bean的名字,@Bean不指定name时,默认就是方法名做为类名,所以咱们此时的bean的名字就是keyGenerator

经过断点,咱们能够发现,当查询用户1的信息时,该方式生成的key相似为:getUser[[1]]

  1. cacheManager 指定缓存管理器
  2. cacheResolver 和cacheManager二选一,缓存解析器
  3. condition 缓存的条件,能够为空,使用SpEL编写,返回 true 或者 false,只有为 true才进行缓存/清除缓存,在调用方法以前以后都能判断。
@Cacheable(value = {"user"}, condition = "#a0 > 1")
    public User getUser(Integer id){
        System.out.println("查询:" + id);
        return userMapper.getUserById(id);
    }

condition代表只对id大于2的用户信息进行缓存。

#a0表明第1个参数,你也能够直接#参数名提取对应的参数值。

  1. unless(@CachePut)(@Cacheable) 用于否决缓存,不像condition,该表达式只在方法执行以后判断,此时能够拿到返回值result进行判断。条件为true不会缓存,fasle才缓存。例如:
@Cacheable(value="testcache",unless="#result == null")

#result表示返回结果。

sync

是否使用异步模式,默认false,是否等待方法执行完才返回结果,另外,该配置若是生效,则@unless注解失效。

@CachePut

缓存机制若是对一个常常变化的值进行操做的话,显然咱们须要一个更新机制,例如当编号为1的用户被修改了以后,也但愿经过返回结果缓存或者更新(若是对应的key已经有了的话)他的缓存信息。这个功能就是由@CachePut来完成。

他的特色是标注方法在进行操做以后,对结果进行缓存。咱们不难想象,他的使用方式和@Cacheable一模一样,对其指定一样的和@Cacheable同样的key便可。

为了测试一下,咱们为controller添加一个update方法

controller/UserController.class
@RequestMapping("/user/update")
    public User updateUser(User user){
        return userService.updateUser(user);
    }

而后对service使用@CachePut注解,注意指定和读取时使用的同样的key(即用户ID):

service/UserService.class
@Cacheable(value = "user", key = "#id")
    public User getUser(Integer id){
        System.out.println("查询:" + id);
        return userMapper.getUserById(id);
    }

    @CachePut(value = "user", key="#user.id")
    public User updateUser(User user) {
        userMapper.updateUser(user);
        return userMapper.getUserById(user.getId());
    }

咱们和@Cacheable同样用了相同的缓存组件user,以及一致的key生成策略——用户ID,同时查询了更新后的用户信息做为返回值,确保@CachePut将其放进缓存。

接下来咱们先查询id为1的用户信息,重复查两次,确保对当前结果进行了缓存,访问localhost:8090/user/select/1

{"id":1,"loginName":"akuya","userName":"阿库娅","departmentId":1}

如今,咱们将userName修改成“阿库娅520”,访问localhost:8090/user/update?id=1&userName=阿库娅520,确保修改为功以后,咱们再来访问localhost:8090/user/select/1:

{"id":1,"loginName":"akuya","userName":"阿库娅520","departmentId":1}

获得了咱们更新以后的结果,说明@CachePut达到了咱们想要的需求。

为了测试@CachePut的效果,能够先去除更新@CachePut的注解,这样咱们就能够发现,即使咱们修改了用户信息,缓存的信息仍是旧用户信息,添加了@CachePut以后,结果就实时更新了。

@Cacheable不同的是,@CachePut是优先调用方法,再将结果存储在缓存中;而@Cacheable则是先判断缓存中是否存在对应的key,不存在才调用方法。所以咱们能够推导出@Cacheable指定key的时候是不能使用#result这样的语法的(聪明的你应该很容易理解)。

8 @CacheEvict

缓存清除,例如咱们删除一个用户的时候,可使用该注解删除指定的缓存信息。

一样须要指定缓存组件,以及key,有了以前的经验,咱们应该写这样的service方法就能够了:

service/UserService.class
@CacheEvict(value = "user", key="#id")
    public void deleteUser(Integer id) {
        System.out.println("删除");
        userMapper.deleteUser(id);
    }

咱们也指定了user,也指定了其key的值。

相比较以前的注解,CacheEvict还有一些特殊的注解,例如:

  1. allEntries 默认值false,标志为true以后,会清除指定组件中的全部缓存信息,例如
@CacheEvict(value = "user", allEntries = true)

该注解就会在方法执行后,将user组件中的全部缓存都清除。

  1. beforeInvocation 默认为false,若是设置为true,那么清除缓存操做会发生在方法执行以前。这种状况比较特殊,默认状况下,咱们是方法执行后才进行清缓存操做,显然若是在方法运行过程当中出现异常,就不会清除缓存。因此,若是有特殊要求,咱们可使用该参数,让缓存一开始就清除掉,而无论你方法是否运行成功与否。

9 @Caching

若是咱们的缓存规则比较复杂,须要用到以上多个注解的特性,那么能够考虑使用@Caching注解,查询其源码咱们就能够知道,他其实就是其余注解的组合体:

org.springframework.cache.annotation/Caching.interface
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Caching {
    Cacheable[] cacheable() default {};

    CachePut[] put() default {};

    CacheEvict[] evict() default {};
}

为了使用@Caching,咱们能够编写一个这样的service,经过loginName查询用户信息,而后呢,将返回结果放在几个不一样的key值之中,这样,咱们直接经过id或者其余为查询条件查询时,就能够直接从 以前的查询中,拿到结果,而无需再从数据库中查询了:

mapper/UserMapper.class
@Select("select * from user where login_name = #{loginName}")
    User getUserByLoginName(String loginName);
service/UserService.class
@Caching(
        cacheable = {
            @Cacheable(value = "user", key = "#loginName")
        },
        put = {
            @CachePut(value="user", key = "#result.id"),
            @CachePut(value="user", key = "#result.userName")
        }
    )
    public User getUserByLoginName(String loginName){
        return userMapper.getUserByLoginName(loginName);
    }

咱们经过loginName查询到结果以后,还想要经过userName以及id做为缓存的key保存到内存中,这些key值都只能从返回结果中取到。咱们前面有提到,@cacheable是没办法拿到返回结果的。所以咱们使用@CachePut来完善了咱们的需求。

接下来经过测试就会发现,只要咱们经过loginName查询了akuya的数据,咱们再经过id为1(就是akuya)来查询akuya信息,发现这时候就直接获得结果,无需查询数据库了。由于咱们的缓存组件中,经过userService.getUserByLoginName方法的执行,已经就id、loginName以及userName对akuya的信息进行了缓存。

10 @CacheConfig

仔细观察咱们以前缓存属性配置你会发现,不少属性很是的啰嗦,例如value="user",重复指定了N次,咱们能不能经过某个注解,直接一次性将这些配置指定了呢?

有,他就是CacheConfig,用于抽取缓存的公共配置。如下是其源码:

org.springframework.cache.annotation.CacheConfig
/*
 * Copyright 2002-2015 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.cache.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * {@code @CacheConfig} provides a mechanism for sharing common cache-related
 * settings at the class level.
 *
 * <p>When this annotation is present on a given class, it provides a set
 * of default settings for any cache operation defined in that class.
 *
 * @author Stephane Nicoll
 * @author Sam Brannen
 * @since 4.1
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CacheConfig {

	/**
	 * Names of the default caches to consider for caching operations defined
	 * in the annotated class.
	 * <p>If none is set at the operation level, these are used instead of the default.
	 * <p>May be used to determine the target cache (or caches), matching the
	 * qualifier value or the bean names of a specific bean definition.
	 */
	String[] cacheNames() default {};

	/**
	 * The bean name of the default {@link org.springframework.cache.interceptor.KeyGenerator} to
	 * use for the class.
	 * <p>If none is set at the operation level, this one is used instead of the default.
	 * <p>The key generator is mutually exclusive with the use of a custom key. When such key is
	 * defined for the operation, the value of this key generator is ignored.
	 */
	String keyGenerator() default "";

	/**
	 * The bean name of the custom {@link org.springframework.cache.CacheManager} to use to
	 * create a default {@link org.springframework.cache.interceptor.CacheResolver} if none
	 * is set already.
	 * <p>If no resolver and no cache manager are set at the operation level, and no cache
	 * resolver is set via {@link #cacheResolver}, this one is used instead of the default.
	 * @see org.springframework.cache.interceptor.SimpleCacheResolver
	 */
	String cacheManager() default "";

	/**
	 * The bean name of the custom {@link org.springframework.cache.interceptor.CacheResolver} to use.
	 * <p>If no resolver and no cache manager are set at the operation level, this one is used
	 * instead of the default.
	 */
	String cacheResolver() default "";

}

源码也加了很多注释,但其内在属性都是提取的公共配置,咱们直接在service类上指定以后,若是内部方法没有特殊指定,都会套用咱们使用该注解指定的值。例如,咱们抽取出公共value属性。

@Service
@CacheConfig(cacheNames = "user")
public class UserService {

这样,咱们整个service的缓存方法注解,若是都是用的user组件,就无需特殊指定,直接删除便可。经过该注解能够自动的帮咱们指定这个值。

cacheNames和value是同一个意思,不过@CacheConfig没对value作兼容,因此咱们这里必须写cacheNames.

通过这些练习,或许您会发现,缓存内部存储的数据咱们能看到吗?若是能看到就行了。能够吗?固然能够。因此,咱们接下来要学习的redis,为咱们解决这个问题。

相关文章
相关标签/搜索