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

本文涉及的产品
云数据库 Tair(兼容Redis),内存型 2GB
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
简介: 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.Cacheorg.springframework.cache.CacheManager接口来统一不同的缓存技术,

并支持使用JCache(JSR-107)注解简化我们开发。

image.png

Cache接口为缓存的组件规范定义,包含缓存的各种操作集合。Cache接口下Spring提供了各种xxxCache的实现;如RedisCache,EhCache , 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属性如下:

image.png

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,控制台输出如下:

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

注意:此时没有使用第三方缓存组件如Redis、memcached等,本地缓存内容是在内存中!


【4】@EnableCaching注解

Enables Spring’s annotation-driven cache management capability, similar to the support found in Spring’s {@code cache:*} XML namespace.


注解示例如下:

@Configuration
@EnableCaching
public class AppConfig {
     @Bean
     public MyService myService() {
  // configure and return a class having &#064;Cacheable methods
         return new MyService();
     }
//自定义CacheManager
     @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注解

源码如下:

 *//标明方法返回结果应该被缓存
 * //每次目标方法被调用前,都会根据给定的方法参数检查是否方法已经被调用(进行了缓存)
 *//默认使用方法参数得到缓存的key,但是你可以在key属性中使用SpELl表达式或者使用自定义的keyGenerator来指定key
 *//如果根据key没有从缓存里拿到值,就调用方法并将返回值进行缓存
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Cacheable {
  @AliasFor("cacheNames")
  String[] value() default {};
  @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

image.png


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

测试如下图:


【8】@CachePut注解使用

既调用方法,又更新缓存(先调用方法,然后将方法的返回结果放进缓存),常见场景如修改了数据库的某个数据,同时更新缓存。

示例如下:

// @CachePut(value = "emp",key="#result.id")
 @CachePut(value = "emp",key="#employee.id")
 public Employee updateEmp(Employee employee){
      System.out.println("调用更新方法!");
      employeeMapper.updateEmp(employee);
      return employee;
  }

注意,@Cacheable中的key是不能使用#result的 !


【9】@CacheEvict注解使用

默认先执行方法,然后根据参数作为key从缓存中删除数据。如果方法执行过程中抛出了异常,则不会删除缓存中目标数据。

示例如下:

@CacheEvict(value = "emp",key = "#id",allEntries = false)
public void deleEmp(Integer id){
     System.out.println("删除"+id+"号员工");
//   employeeMapper.deleteEmpById(id);
 }

allEntries :默认false,表示是否全部删除对应缓存组件中的数据。

beforeInvocation:默认false,表示是否在方法执行执行删除缓存。

其他属性见源码如下:

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface CacheEvict {
  /**
   * Alias for {@link #cacheNames}.
   */
  @AliasFor("cacheNames")
  String[] value() default {};
  /**
   * Names of the caches to use for the cache eviction operation.
   * <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 {};
  /**
   * 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 set.
   * <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, which
   * can only be used if {@link #beforeInvocation()} is {@code false}. 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>
   */
  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 cache
   * eviction operation conditional.
   * <p>Default is {@code ""}, meaning the cache eviction is always performed.
   * <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 "";
  /**
   * Whether all the entries inside the cache(s) are removed.
   * <p>By default, only the value under the associated key is removed.
   * 默认只删除对应的key的数据
   * <p>Note that setting this parameter to {@code true} and specifying a
   * {@link #key} is not allowed.
   * 需要注意的是#key与该属性设置为true不允许同时存在
   */
  boolean allEntries() default false;
  /**
   * Whether the eviction should occur before the method is invoked.
   * 是否在方法调用前清楚缓存
   * <p>Setting this attribute to {@code true}, causes the eviction to
   * occur irrespective of the method outcome (i.e., whether it threw an
   * exception or not).
   * 如果设置为true,则无论方法是否正常执行,都会在方法执行前清除缓存
   * <p>Defaults to {@code false}, meaning that the cache eviction operation
   * will occur <em>after</em> the advised method is invoked successfully (i.e.,
   * only if the invocation did not throw an exception).
   * 默认在方法正常成功执行后,清除缓存
   */
  boolean beforeInvocation() default false;
}

【10】@Caching注解

该注解用来构建复杂规则的缓存用例,源码如下:

/**
 * Group annotation for multiple cache annotations (of different or the same type).
 *
 * <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 Chris Beams
 * @since 3.1
 */
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Caching {
  Cacheable[] cacheable() default {};
  CachePut[] put() default {};
  CacheEvict[] evict() default {};
}

实例如下:

@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){
     System.out.println("调用复杂缓存方法");
     return employeeMapper.getEmpByLastName(lastName);
 }

第一次按照lastName进行缓存的同时(cacheable 注解),@CachePut注解也起作用–分别以id和email为key在缓存中放入数据。

再次进行查询的时候方法仍然会调用,因为@CachePut注解一直起作用!


【11】@CacheConfig注解使用

同一个类中不同方法汇总缓存组件名字一般都相同,可以使用@CacheConfig注解作用在类上配置共同属性值,默认对该类的所有方法起作用。

注解源码如下:

 *@CacheConfig在类级别上提供了共享与缓存相关的公共设置的机制
 当类上面添加了该注解,就意味着提供了一系列默认设置关于该类上定义的缓存操作
@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 {};
   * 使用在该类上面的KeyGenerator类型的bean 实例名字
   * <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 "";
}

完整示例如下:

@CacheConfig(cacheNames = "emp")
@Service
public class EmployeeService {
    @Autowired
    EmployeeMapper employeeMapper;
    @Cacheable(key = "#id")
    public Employee getEmp(Integer id){
        System.out.println("查询"+id+"号员工");
        Employee empById = employeeMapper.getEmpById(id);
        return empById;
    }
    @CachePut(/*value = "emp",*/key="#employee.id")
    public Employee updateEmp(Employee employee){
        System.out.println("调用更新方法!");
        employeeMapper.updateEmp(employee);
        return employee;
    }
    @CacheEvict(/*value = "emp",*/key = "#id",allEntries = true)
    public void deleEmp(Integer id){
        System.out.println("删除"+id+"号员工");
//        employeeMapper.deleteEmpById(id);
//        int i=10/0;
    }
    @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){
        System.out.println("调用复杂缓存方法");
        return employeeMapper.getEmpByLastName(lastName);
    }
}

如果方法上指定了该属性值(@CacheConfig中配置的属性),则使用方法上指定的值:

//如这里put 中将#result.id为key放入emp1缓存组件中
 @Caching(
            cacheable ={
                    @Cacheable(/*value ="emp",*/key = "#lastName")
            },
            put = {
                    @CachePut(value = "emp1",key = "#result.id"),
                    @CachePut(/*value = "emp",*/key = "#result.email")
            }
    )
    public Employee getEmpByLastName(String lastName){
        System.out.println("调用复杂缓存方法");
        return employeeMapper.getEmpByLastName(lastName);
    }

【12】整合第三方缓存-Redis

项目中引入Redis组件后,就不再使用默认的SimpleCacheConfiguration,而是使用RedisCacheConfiguration。

其源码如下:

@Configuration
@AutoConfigureAfter(RedisAutoConfiguration.class)
@ConditionalOnBean(RedisTemplate.class)
@ConditionalOnMissingBean(CacheManager.class)
@Conditional(CacheCondition.class)
class RedisCacheConfiguration {
  private final CacheProperties cacheProperties;
  private final CacheManagerCustomizers customizerInvoker;
  RedisCacheConfiguration(CacheProperties cacheProperties,
      CacheManagerCustomizers customizerInvoker) {
    this.cacheProperties = cacheProperties;
    this.customizerInvoker = customizerInvoker;
  }
//向容器中注册RedisCacheManager 
  @Bean
  public RedisCacheManager cacheManager(RedisTemplate<Object, Object> redisTemplate) {
  //给cacheManager  赋予redisTemplate,用来进行通信
    RedisCacheManager cacheManager = new RedisCacheManager(redisTemplate);
    cacheManager.setUsePrefix(true);
    List<String> cacheNames = this.cacheProperties.getCacheNames();
    if (!cacheNames.isEmpty()) {
      cacheManager.setCacheNames(cacheNames);
    }
    return this.customizerInvoker.customize(cacheManager);
  }
}

【13】自定义RedisCacheManager

默认使用的JDK序列化机制在redis中存的是序列化数据,非直观显示的json数据。如果想要在redis中存放为json数据,可以做如下考虑:自定义RedisCacheManager。

自定义RedisCacheManager源码示例如下:

@Configuration
public class MyRedisConfig {
    @Bean
    public RedisTemplate<Object,Employee> empRedisTemplate(RedisConnectionFactory connectionFactory){
        RedisTemplate<Object,Employee> template = new RedisTemplate<Object, Employee>();
        template.setConnectionFactory(connectionFactory);
        Jackson2JsonRedisSerializer<Employee> serializer = new Jackson2JsonRedisSerializer<Employee>(Employee.class);
        template.setDefaultSerializer(serializer);
        return template;
    }
    @Bean
    public RedisCacheManager myCacheManager(RedisTemplate<Object,Employee> empRedisTemplate) {
        RedisCacheManager cacheManager = new RedisCacheManager(empRedisTemplate);
        //默认将CacheName作为key的前缀
        cacheManager.setUsePrefix(true);
        return cacheManager;
    }
}

这样在操作Employee时在redis中存放的即为json数据!但是这种方式有明显局限性,基本不用考虑。

继续往下看!!!


【14】自定义RedisTemplate<Object,Object>

替换容器中默认配置的RedisTemplate<Object,Object>,源码示例如下:

  //替换默认注册的RedisTemplate<Object,Object>,id为redisTemplate
    @Bean
    public RedisTemplate<Object,Object> redisTemplate(RedisConnectionFactory connectionFactory){
        RedisTemplate<Object,Object> template = new RedisTemplate<Object, Object>();
        template.setConnectionFactory(connectionFactory);
        Jackson2JsonRedisSerializer<Object> serializer = new Jackson2JsonRedisSerializer<Object>(Object.class);
        template.setDefaultSerializer(serializer);
        return template;
    }

这样,无需替换容器中的RedisCacheManager,即可将任意类型进行JSON化存放Redis中!



但是同样有坑,存的时候没有问题,取出来的时候就有问题了,异常如下:


所以,使用默认的序列化机制,存取对象都没问题,但是在Redis中又不友好,上面的方法显然都不行,继续往下看!!


【15】SpringBoot2.0下整合Redis

pom依赖

<!--整合Redis-->
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-data-redis</artifactId>
  <!-- 1.5的版本默认采用的连接池技术是jedis  2.0以上版本默认连接池是lettuce,
  在这里采用jedis,所以需要排除lettuce的jar -->
  <exclusions>
    <exclusion>
      <groupId>io.lettuce</groupId>
      <artifactId>lettuce-core</artifactId>
    </exclusion>
  </exclusions>
</dependency>
<!-- 添加jedis客户端 -->
<dependency>
  <groupId>redis.clients</groupId>
  <artifactId>jedis</artifactId>
</dependency>
<!--spring2.0集成redis所需common-pool2-->
<!-- 必须加上,jedis依赖此  -->
<!-- spring boot 2.0 的操作手册有标注
地址是:https://docs.spring.io/spring-boot/docs/2.0.3.RELEASE/reference/htmlsingle/-->
<dependency>
  <groupId>org.apache.commons</groupId>
  <artifactId>commons-pool2</artifactId>
  <version>2.5.0</version>
  <!--<version>2.4.2</version>-->
</dependency>

自定义Redis配置类

package com.hh.config;
@Configuration
// 必须加,使配置生效
@EnableCaching
public class MyRedisConfig extends CachingConfigurerSupport {
    private static final Logger log = LoggerFactory.getLogger(MyRedisConfig.class);
    @Autowired
    private JedisConnectionFactory jedisConnectionFactory;
    @Bean
    @Override
    public CacheManager cacheManager() {
        // 初始化缓存管理器,在这里我们可以缓存的整体过期时间什么的,我这里默认没有配置
        log.info("初始化 -> [{}]", "CacheManager RedisCacheManager Start");
        RedisCacheManager.RedisCacheManagerBuilder builder = RedisCacheManager
                .RedisCacheManagerBuilder
                .fromConnectionFactory(jedisConnectionFactory);
        return builder.build();
    }
    @Bean
    public RedisTemplate<String, Object> redisTemplate(JedisConnectionFactory jedisConnectionFactory ) {
        //设置序列化
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(om);
        // 配置redisTemplate
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<String, Object>();
        redisTemplate.setConnectionFactory(jedisConnectionFactory);
        RedisSerializer stringSerializer = new StringRedisSerializer();
        redisTemplate.setKeySerializer(stringSerializer); // key序列化
        redisTemplate.setValueSerializer(jackson2JsonRedisSerializer); // value序列化
        redisTemplate.setHashKeySerializer(stringSerializer); // Hash key序列化
        redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer); // Hash value序列化
        redisTemplate.afterPropertiesSet();
        return redisTemplate;
    }
    @Override
    @Bean
    public CacheErrorHandler errorHandler() {
        // 异常处理,当Redis发生异常时,打印日志,但是程序正常走
        log.info("初始化 -> [{}]", "Redis CacheErrorHandler");
        CacheErrorHandler cacheErrorHandler = new CacheErrorHandler() {
            @Override
            public void handleCacheGetError(RuntimeException e, Cache cache, Object key) {
                log.error("Redis occur handleCacheGetError:key -> [{}]", key, e);
            }
            @Override
            public void handleCachePutError(RuntimeException e, Cache cache, Object key, Object value) {
                log.error("Redis occur handleCachePutError:key -> [{}];value -> [{}]", key, value, e);
            }
            @Override
            public void handleCacheEvictError(RuntimeException e, Cache cache, Object key)    {
                log.error("Redis occur handleCacheEvictError:key -> [{}]", key, e);
            }
            @Override
            public void handleCacheClearError(RuntimeException e, Cache cache) {
                log.error("Redis occur handleCacheClearError:", e);
            }
        };
        return cacheErrorHandler;
    }
}

测试实例如下:

  @Autowired
  StringRedisTemplate stringRedisTemplate;
  @Autowired
  RedisTemplate redisTemplate;
  @Test
  public void testRedis(){
    // 测试redis
    if(stringRedisTemplate.hasKey("hello")){
      String hello = stringRedisTemplate.opsForValue().get("hello");
      System.out.println("从redis中获取 key-hello--value : "+hello);
      stringRedisTemplate.opsForValue().set("jane","is a boy");
    }
    SysUser sysUser = new SysUser();
    sysUser.setId(1L);
    sysUser.setDataType(1);
    sysUser.setIdentity("4125XXXX");
    redisTemplate.opsForValue().set("sysUser",sysUser);
    SysUser sysUser2 = (SysUser) redisTemplate.opsForValue().get("sysUser");
    System.out.println(sysUser2);
  }

测试结果如下图:

如果使用Redis作为Cache实现,那么数据将会自动被缓存到Redis中。@Cacheable加了该注解的方法将会首先尝试从Redis中获取数据,如果没有数据则执行具体方法,将方法结果缓存到Redis中。再次查询时,将直接从Redis中获取,不会再执行具体方法。

需要注意的是,该种方式不用显示手动使用RedisTemplate进行存取!因为数据是存储在Redis中的,即使应用重启,再次查询同样直接从Redis中获取,不再执行具体方法

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


相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore &nbsp; &nbsp; ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库&nbsp;ECS 实例和一台目标数据库&nbsp;RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&amp;RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
目录
相关文章
|
3天前
|
Java 数据库连接 测试技术
SpringBoot入门(4) - 添加内存数据库H2
SpringBoot入门(4) - 添加内存数据库H2
18 4
SpringBoot入门(4) - 添加内存数据库H2
|
9天前
|
Java Spring
在使用Spring的`@Value`注解注入属性值时,有一些特殊字符需要注意
【10月更文挑战第9天】在使用Spring的`@Value`注解注入属性值时,需注意一些特殊字符的正确处理方法,包括空格、引号、反斜杠、新行、制表符、逗号、大括号、$、百分号及其他特殊字符。通过适当包裹或转义,确保这些字符能被正确解析和注入。
|
14天前
|
缓存 JavaScript 搜索推荐
vue中的一个内置组件Keep-Alive的作用及使用方法介绍——缓存不活动的组件实例
vue中的一个内置组件Keep-Alive的作用及使用方法介绍——缓存不活动的组件实例
53 1
|
3天前
|
Java 应用服务中间件 数据库连接
SpringBoot入门(2) - SpringBoot HelloWorld
SpringBoot入门(2) - SpringBoot HelloWorld
11 2
SpringBoot入门(2) - SpringBoot HelloWorld
|
5天前
|
Java 数据库连接 测试技术
SpringBoot入门(4) - 添加内存数据库H2
SpringBoot入门(4) - 添加内存数据库H2
15 2
SpringBoot入门(4) - 添加内存数据库H2
|
5天前
|
前端开发 Java 数据库
SpringBoot入门(3) - 对Hello world进行MVC分层
SpringBoot入门(3) - 对Hello world进行MVC分层
18 1
SpringBoot入门(3) - 对Hello world进行MVC分层
|
5天前
|
Java 应用服务中间件 数据库连接
SpringBoot入门(2) - SpringBoot HelloWorld
SpringBoot入门(2) - SpringBoot HelloWorld
14 1
 SpringBoot入门(2) - SpringBoot HelloWorld
|
4天前
|
XML 安全 Java
SpringBoot入门(1) - SpringBoot简介
SpringBoot是在SpringFramework基础上发展起来的框架,旨在简化Spring应用的初始搭建及开发过程。它通过“约定优于配置”原则减少繁重的配置工作,提供开箱即用的特性,支持快速开发,同时自动配置Spring,提高开发效率。
28 11
|
4天前
|
前端开发 Java 数据库
SpringBoot入门(3) - 对Hello world进行MVC分层
本文介绍了如何在Spring Boot项目中实现MVC分层架构,通过将代码划分为controller、service、dao和entity四个部分,实现高内聚低耦合的设计。示例项目包括用户增删查改功能,详细展示了各层的具体实现及运行测试。
23 11
|
4天前
|
Java 应用服务中间件 数据库连接
SpringBoot入门(2) - SpringBoot HelloWorld
本文介绍了如何使用Spring Boot创建一个简单的Web应用程序,包括项目初始化、添加依赖、编写代码和运行程序等步骤。通过添加`spring-boot-starter-web`模块,快速搭建了一个能够响应HTTP请求的Web服务。此外,文章还探讨了如何配置服务器(如Tomcat)以及Spring Boot提供的其他常用starter模块。示例源码可在GitHub上获取。
23 11