SpringBoot - 缓存入门详解与注解使用实例

【1】JSR107

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中添加javax.cache依赖:

<dependency>
    <groupId>javax.cache</groupId>
    <artifactId>cache-api</artifactId>
</dependency>

项目整合缓存结构示意图如下:

这里写图片描述

实际项目应用中,很少使用JSR107进行整合,通常使用Spring的缓存抽象 !


【2】Spring缓存抽象

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

概念(注解) 描述
Cache 缓存接口,定义缓存操作。实现有:RedisCache、EhCacheCache、ConcurrentMapCache等
CacheManager 缓存管理器,管理各种缓存(Cache)组件
keyGenerator 缓存数据时key生成策略
serializer 缓存数据时value序列化策略
@Cacheable 主要针对方法配置,能够根据方法的请求参数对其结果进行缓存
@CacheEvict 根据方法的请求参数清空缓存
@CachePut 保证方法被调用,又将结果进行缓存
@EnableCaching 开启基于注解的缓存

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

每次调用需要缓存功能的方法时,Spring会检查检查指定参数的指定的目标方法是否已经被调用过。如果有就直接从缓存中获取方法调用后的结果,如果没有就调用方法并缓存结果后返回给用户,下次调用直接从缓存中获取。

使用Spring缓存抽象时我们需要关注以下两点:

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

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


【3】SpringBoot整合Spring缓存示例

① 搭建基本环境

基本环境如下:pom文件引入依赖,配置MyBatis数据源,编写bean、service、controller和Mapper注解类。

依赖示例如下:

    <dependency>
        <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-cache</artifactId>
        </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.mybatis.spring.boot</groupId>
        <artifactId>mybatis-spring-boot-starter</artifactId>
        <version>1.3.2</version>
    </dependency>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <scope>runtime</scope>
    </dependency>

两个bean属性如下:

bean 属性
Employee id lastName gender email dId
Department id departmentName

EmployeeMapper 注解示例(DepartmentMapper类同):

public interface EmployeeMapper {

    @Select("select * from employee where id=#{id}")
    public Employee getEmpById(Integer id);

    @Insert("insert into employee(lastName,email,gender,d_id) values(#{lastName},#{email},#{gender},#{dId})")
    public void insertEmp(Employee employee);

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

EmployeeService示例如下:

@Service
public class EmployeeService {

    @Autowired
    EmployeeMapper employeeMapper;

    public Employee getEmp(Integer id){
        System.out.println("查询"+id+"号员工");
        Employee empById = employeeMapper.getEmpById(id);
        return empById;

    }
}

EmployeeController示例如下:

@RestController
public class EmployeeController {

    @Autowired
    EmployeeService employeeService;


    @GetMapping("/emp/{id}")
    public Employee getEmp(@PathVariable("id") Integer id){
       return employeeService.getEmp(id);
    }
}

数据源配置如下(根据需要自定义配置):

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

# 开启驼峰命名匹配规则
mybatis.configuration.map-underscore-to-camel-case=true
#mybatis.configuration.cache-enabled=true

主程序如下:

@MapperScan(value = "com.web.springboot.mapper")
//不用在每个mapper上添加@Mapper注解
@SpringBootApplication
public class SpringBoot01CacheApplication {

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

② 使用@EnableCaching和@Cacheable测试

@EnableCaching开启基于注解的Spring Cache,@Cacheable作用于方法上,标明该方法结果可被缓存。

源码示例如下:

@MapperScan(value = "com.web.springboot.mapper")
@SpringBootApplication
// 主程序添加@EnableCaching注解
@EnableCaching
public class SpringBoot01CacheApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringBoot01CacheApplication.class, args);
    }
}
// 具体方法上添加@Cacheable注解
@Cacheable(cacheNames = "emp")
public Employee getEmp(Integer id){
     System.out.println("查询"+id+"号员工");
     Employee empById = employeeMapper.getEmpById(id);
     return empById;

 }

测试,连续两次获取id为1的Employee,控制台输出如下:

这里写图片描述

即,只进行了一次查询,第二次从缓存中直接获取!


【4】@EnableCaching注解

Enables Spring’s annotation-driven cache management capability, similar to the support found in Spring’s {@code

@Configuration
@EnableCaching
public class AppConfig {

     @Bean
     public MyService myService() {
  // configure and return a class having &#064;Cacheable methods
         return new MyService();
     }

     @Bean
     public CacheManager cacheManager() {
       // configure and return an implementation of Spring's CacheManager SPI
        SimpleCacheManager cacheManager = new SimpleCacheManager();
        cacheManager.setCaches(Arrays.asList(new ConcurrentMapCache("default")));
        return cacheManager;
     }
}

等同于Spring xml配置如下:

<beans>
      <cache:annotation-driven/>

      <bean id="myService" class="com.foo.MyService"/>

      <bean id="cacheManager" class="org.springframework.cache.support.SimpleCacheManager">
          <property name="caches">
              <set>
                  <bean class="org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean">
                      <property name="name" value="default"/>
                  </bean>
              </set>
          </property>
      </bean>
</beans>

源码说明如下:

 * In both of the scenarios above, {@code @EnableCaching} and {@code
 * <cache:annotation-driven/>} are responsible for registering the necessary Spring
 * components that power annotation-driven cache management, such as the
 * {@link org.springframework.cache.interceptor.CacheInterceptor CacheInterceptor} and the
 * proxy- or AspectJ-based advice that weaves the interceptor into the call stack when
 * {@link org.springframework.cache.annotation.Cacheable @Cacheable} methods are invoked.
 *
 * <p>If the JSR-107 API and Spring's JCache implementation are present, the necessary
 * components to manage standard cache annotations are also registered. This creates the
 * proxy- or AspectJ-based advice that weaves the interceptor into the call stack when
 * methods annotated with {@code CacheResult}, {@code CachePut}, {@code CacheRemove} or
 * {@code CacheRemoveAll} are invoked.
 *
 * <p><strong>A bean of type {@link org.springframework.cache.CacheManager CacheManager}
 * must be registered</strong>, as there is no reasonable default that the framework can
 * use as a convention. And whereas the {@code <cache:annotation-driven>} element assumes
 * a bean <em>named</em> "cacheManager", {@code @EnableCaching} searches for a cache
 * manager bean <em>by type</em>. Therefore, naming of the cache manager bean method is
 * not significant.

【5】@Cacheable注解

源码如下:

/**
 * Annotation indicating that the result of invoking a method (or all methods
 * in a class) can be cached.
 *//标明方法返回结果应该被缓存
 * <p>Each time an advised method is invoked, caching behavior will be applied,
 * checking whether the method has been already invoked for the given arguments.
 * //每次目标方法被调用前,都会根据给定的方法参数检查是否方法已经被调用(进行了缓存)
 * 
 * A sensible default simply uses the method parameters to compute the key, but
 * a SpEL expression can be provided via the {@link #key} attribute, or a custom
 * {@link org.springframework.cache.interceptor.KeyGenerator} implementation can
 * replace the default one (see {@link #keyGenerator}).
 *//默认使用方法参数得到缓存的key,但是你可以在key属性中使用SpELl表达式或者使用自定义的keyGenerator来指定key

 * <p>If no value is found in the cache for the computed key, the target method
 * will be invoked and the returned value stored in the associated cache. Note
 * that Java8's {@code Optional} return types are automatically handled and its
 * content is stored in the cache if present.
 *//如果根据key没有从缓存里拿到值,就调用方法并将返回值进行缓存
 * <p>This annotation may be used as a <em>meta-annotation</em> to create custom
 * <em>composed annotations</em> with attribute overrides.
 *
 * @author Costin Leau
 * @author Phillip Webb
 * @author Stephane Nicoll
 * @author Sam Brannen
 * @since 3.1
 * @see CacheConfig
 */
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Cacheable {

    /**
     * Alias for {@link #cacheNames}.
     */
    @AliasFor("cacheNames")
    String[] value() default {};

    /**
     * Names of the caches in which method invocation results are stored.
     * <p>Names may be used to determine the target cache (or caches), matching
     * the qualifier value or bean name of a specific bean definition.
     * @since 4.2
     * @see #value
     * @see CacheConfig#cacheNames
     */
    @AliasFor("value")
    String[] cacheNames() default {};
// value等同于cacheNames,指定方法结果应该被存放的Cache组件名字

    /**
     * Spring Expression Language (SpEL) expression for computing the key dynamically.
     * <p>Default is {@code ""}, meaning all method parameters are considered as a key,
     * unless a custom {@link #keyGenerator} has been configured.
     * // 默认方法的所有参数作为key,除非配置了自定义的keyGenerator
     * <p>The SpEL expression evaluates against a dedicated context that provides the
     * following meta-data:
     * <ul>
     * <li>{@code #root.method}, {@code #root.target}, and {@code #root.caches} for
     * references to the {@link java.lang.reflect.Method method}, target object, and
     * affected cache(s) respectively.</li>
     * <li>Shortcuts for the method name ({@code #root.methodName}) and target class
     * ({@code #root.targetClass}) are also available.
     * <li>Method arguments can be accessed by index. For instance the second argument
     * can be accessed via {@code #root.args[1]}, {@code #p1} or {@code #a1}. Arguments
     * can also be accessed by name if that information is available.</li>
     * </ul>
     */
    String key() default "";

    /**
     * The bean name of the custom {@link org.springframework.cache.interceptor.KeyGenerator}
     * to use.
     * <p>Mutually exclusive with the {@link #key} attribute.
     * @see CacheConfig#keyGenerator
     */
    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>Mutually exclusive with the {@link #cacheResolver}  attribute.
     * @see org.springframework.cache.interceptor.SimpleCacheResolver
     * @see CacheConfig#cacheManager
     */
    String cacheManager() default "";

    /**
     * The bean name of the custom {@link org.springframework.cache.interceptor.CacheResolver}
     * to use.
     * @see CacheConfig#cacheResolver
     */
    String cacheResolver() default "";

    /**
     * Spring Expression Language (SpEL) expression used for making the method
     * caching conditional.
     * <p>Default is {@code ""}, meaning the method result is always cached.
     * <p>The SpEL expression evaluates against a dedicated context that provides the
     * following meta-data:
     * <ul>
     * <li>{@code #root.method}, {@code #root.target}, and {@code #root.caches} for
     * references to the {@link java.lang.reflect.Method method}, target object, and
     * affected cache(s) respectively.</li>
     * <li>Shortcuts for the method name ({@code #root.methodName}) and target class
     * ({@code #root.targetClass}) are also available.
     * <li>Method arguments can be accessed by index. For instance the second argument
     * can be accessed via {@code #root.args[1]}, {@code #p1} or {@code #a1}. Arguments
     * can also be accessed by name if that information is available.</li>
     * </ul>
     */
    String condition() default "";

    /**
     * Spring Expression Language (SpEL) expression used to veto method caching.
     * <p>Unlike {@link #condition}, this expression is evaluated after the method
     * has been called and can therefore refer to the {@code result}.
     * <p>Default is {@code ""}, meaning that caching is never vetoed.
     * <p>The SpEL expression evaluates against a dedicated context that provides the
     * following meta-data:
     * <ul>
     * <li>{@code #result} for a reference to the result of the method invocation. For
     * supported wrappers such as {@code Optional}, {@code #result} refers to the actual
     * object, not the wrapper</li>
     * <li>{@code #root.method}, {@code #root.target}, and {@code #root.caches} for
     * references to the {@link java.lang.reflect.Method method}, target object, and
     * affected cache(s) respectively.</li>
     * <li>Shortcuts for the method name ({@code #root.methodName}) and target class
     * ({@code #root.targetClass}) are also available.
     * <li>Method arguments can be accessed by index. For instance the second argument
     * can be accessed via {@code #root.args[1]}, {@code #p1} or {@code #a1}. Arguments
     * can also be accessed by name if that information is available.</li>
     * </ul>
     * @since 3.2
     */
    String unless() default "";

    /**
     * Synchronize the invocation of the underlying method if several threads are
     * attempting to load a value for the same key. The synchronization leads to
     * a couple of limitations:
     * <ol>
     * <li>{@link #unless()} is not supported</li>
     * <li>Only one cache may be specified</li>
     * <li>No other cache-related operation can be combined</li>
     * </ol>
     * This is effectively a hint and the actual cache provider that you are
     * using may not support it in a synchronized fashion. Check your provider
     * documentation for more details on the actual semantics.
     * @since 4.3
     * @see org.springframework.cache.Cache#get(Object, Callable)
     */
    boolean sync() default false;

}

CacheManager管理多个Cache组件,对缓存真正的CRUD操作在Cache组件中,每一个Cache组件有自己唯一一个名字。


@Cacheable注解属性简解如下:

cacheNames/value:指定缓存组件的名字,必须指定至少一个。

@Cacheable(value=”mycache”) 
@Cacheable(value={”cache1”,”cache2”}

key:缓存数据使用的key,可以根据该属性进行自定义设置,默认使用方法的参数值。

@Cacheable(value=”testcache”,key=”#userName”)
@Cacheable(value=”testcache”,key=”#root.args[0]”)
@Cacheable(value=”testcache”,key=”#root.methodName+'['+#id+']'”)

keyGenerator : key的生成器,可以自己指定keyGenerator 组件id(自定义keyGenerator )。

cacheManager:指定缓存管理器;cacheResolver指定获取解析器;二者二选一使用。

condition:缓存的条件,可以为空,使用 SpEL 编写,返回 true 或者 false,只有为 true 才进行缓存/清除缓存,在调用方法之前之后都能判断。

@Cacheable(value=”testcache”,condition=”#id>2”)
@Cacheable(value=”testcache”,condition=”#a0>2”)

unless : 当unless的条件为true时,方法的返回值就不缓存。该表达式只在方法执行之后判断,此时可以拿到返回值result进行判断。条件为true不会缓存,fasle才缓存。

@Cacheable(value=”testcache”,unless=”#result == null”)

sync:是否启用异步模式。默认采用同步方式,在方法执行完将结果放入缓存,可以设置为true,启用异步模式。需要注意的是,异步模式不能与unless同时使用。


【6】Cache SpEL available metametadata

名字 位置 描述 示例
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代表参数的索引; #a0,#p0
result evaluation context 方法执行后的返回值(仅当方法执行之后的判断有效,如‘unless’,’cache put’的表达式 ’cache evict’的表达式beforeInvocation=false) #result

【7】注册并使用自定义keyGenerator

编写自定义的keyGenerator:

@Configuration
public class MyCacheConfig {

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

在方法处指定keyGenerator:

@Cacheable(cacheNames = "emp",keyGenerator = "keyGenerator")
public Employee getEmp(Integer id){
     System.out.println("查询"+id+"号员工");
     Employee empById = employeeMapper.getEmpById(id);
     return empById;
 }

测试如下图:

这里写图片描述

猜你喜欢

转载自blog.csdn.net/j080624/article/details/80883023