SpringBoot 从入门到光头 第十一章 SpringBoot 与缓存


SpringBoot 从入门到光头 —— 第十一章 SpringBoot 与缓存


1. JSR107 缓存规范

Java Caching 定义了 5 个接口,分别是 CachingProviderCacheManagerCacheEntry 以及 Expiry

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

Cache

2. Spring 缓存抽象

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

  • Cache 接口为缓存的组件规范定义,包含缓存的各种操作组合
  • Cache 接口下的 Spring 提佛难过了各种 xxxCache 的实现,如 RedisCacheEhCacheCacheConcurrentMapCache
  • 每次调用需要缓存的功能时,Spring 会检查制定参数的制定目标方法是否已经被缓存调用过,如果有就直接从缓存中获取方法调用后的结果,如果没有就调用方法,并缓存结果后返回给用户。下次调用直接从缓存中获取。
  • 使用 Spring 缓存抽象是需要关注以下两点:
    1. 确定方法需要被缓存以及他们的缓存策略
    2. 从缓存中读取之前缓存存储的数据

3. 几个重要的概念 & 缓存注解

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

4. 缓存 @Cacheable 初体验

4.1. 示例代码

department 数据表

create table department
(
    id              int auto_increment
        primary key,
    department_name varchar(50) not null,
    constraint department_id_uindex
        unique (id)
);

employee 数据表

create table employee
(
    id        int auto_increment
        primary key,
    last_name varchar(50) not null,
    email     varchar(50) null,
    gender    int         null,
    d_id      int         null,
    constraint employee_id_uindex
        unique (id)
);

pom.xml

<dependencies>
    <dependency>
        <groupId>org.mybatis.spring.boot</groupId>
        <artifactId>mybatis-spring-boot-starter</artifactId>
        <version>2.2.1</version>
    </dependency>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <scope>runtime</scope>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
        <version>2.6.0</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>

application.yaml

spring:
  application:
    name: cache

  datasource:
    url: jdbc:mysql://localhost:3306/spring_cache
    username: root
    password: root
    driver-class-name: com.mysql.cj.jdbc.Driver

mybatis:
  configuration:
    map-underscore-to-camel-case: true

logging:
  level:
    com:
      gregperlinli:
        cache:
          mapper:
            debug

com.yourname.cache.CacheApplicaton

/**
 * Build basic environment<br/>
 * 1. Import database file<br/>
 * 2. Create JavaBean to encapsulate data<br/>
 * 3. Integrate mybatis  to operate database<br/>
 *      1. Configure data sources<br/>
 *      2. Configure mybatis using annotated version<br/>
 *          1) use &#123;@code @MapperScan&#125; to specify the package where the mapper interface to be scanned is located<br/>
 * Quick experience cache<br/>
 * 1. Enable annotation based caching &#123;@code @EnableCaching&#125;>br/>
 * 2. Annotate cache annotation<br/>
 *      1. &#123;@code @Cacheable&#125;<br/>
 *      2. &#123;@code @CacheEvict&#125;<br/>
 *      3. &#123;@code @CachePut&#125;<br/>
 * @author gregperlinli
 */
@SpringBootApplication
@MapperScan("com.gregperlinli.cache.mapper")
@EnableCaching
public class CacheApplication &#123;
    public static void main(String[] args) &#123;
        SpringApplication.run(CacheApplication.class, args);
    &#125;
&#125;

com.yourname.cache.bean.Department

/**
 * @author gregPerlinLi
 * @since 2022-01-17
 */
public class Department &#123;
    private Integer id;
    private String departmentName;
  
    public Department() &#123;
        super();
    &#125;
    public Department(Integer id, String departmentName) &#123;
        super();
        this.id = id;
        this.departmentName = departmentName;
    &#125;
    public Integer getId() &#123;
        return id;
    &#125;
    public void setId(Integer id) &#123;
        this.id = id;
    &#125;
    public String getDepartmentName() &#123;
        return departmentName;
    &#125;
    public void setDepartmentName(String departmentName) &#123;
        this.departmentName = departmentName;
    &#125;
    @Override
    public String toString() &#123;
        return "Department&#123;" +
                "id=" + id +
                ", departmentName='" + departmentName + '\'' +
                '&#125;';
    &#125;
&#125;

com.yourname.cache.bean.Employee


/**
 * @author gregPerlinLi
 * @since 2022-01-17
 */
public class Employee &#123;
    private Integer id;
    private String lastName;
    private String email;
    /**
     * 1. Male  2. Female
     */
    private Integer gender;
    private Integer dId;

    public Employee() &#123;
    &#125;
    public Employee(Integer id, String lastName, String email, Integer gender, Integer dId) &#123;
        this.id = id;
        this.lastName = lastName;
        this.email = email;
        this.gender = gender;
        this.dId = dId;
    &#125;
    public Integer getId() &#123;
        return id;
    &#125;
    public void setId(Integer id) &#123;
        this.id = id;
    &#125;
    public String getLastName() &#123;
        return lastName;
    &#125;
    public void setLastName(String lastName) &#123;
        this.lastName = lastName;
    &#125;
    public String getEmail() &#123;
        return email;
    &#125;
    public void setEmail(String email) &#123;
        this.email = email;
    &#125;
    public Integer getGender() &#123;
        return gender;
    &#125;
    public void setGender(Integer gender) &#123;
        this.gender = gender;
    &#125;
    public Integer getdId() &#123;
        return dId;
    &#125;
    public void setdId(Integer dId) &#123;
        this.dId = dId;
    &#125;
    @Override
    public String toString() &#123;
        return "Employee&#123;" +
                "id=" + id +
                ", lastName='" + lastName + '\'' +
                ", email='" + email + '\'' +
                ", gender=" + gender +
                ", dId=" + dId +
                '&#125;';
    &#125;
&#125;

com.yourname.cache.mapper.DepartmentMapper

/**
 * @author gregPerlinLi
 * @since 2022-01-17
 */
@Mapper
public interface EmployeeMapper &#123;
    /**
     * Get employee by id
     *
     * @param id id
     * @return employee
     */
    @Select(value = "SELECT * FROM employee WHERE id = #&#123;id&#125;")
    Employee getEmployeeById(Integer id);
    /**
     * Update employee
     *
     * @param employee employee
     */
    @Update(value = "UPDATE employee SET last_name = #&#123;lastName&#125;, email = #&#123;email&#125;, gender = #&#123;gender&#125;, d_id = #&#123;dId&#125; WHERE id = #&#123;id&#125;")
    void updateEmployee(Employee employee);
    /**
     * Delete employee
     *
     * @param id id
     */
    @Delete(value = "DELETE FROM employee WHERE id = #&#123;id&#125;")
    void deleteEmployee(Integer id);
    /**
     * Insert employee
     *
     * @param employee employee
     */
    @Insert(value = "INSERT INTO employee(last_name, email, gender, d_id) VALUES(#&#123;lastName&#125;, #&#123;email&#125;, #&#123;gender&#125;, #&#123;dId&#125;)")
    void insertEmployee(Employee employee);
&#125;

com.yourname.cache.service.EmployeeService

/**
 * @author gregPerlinLi
 * @since 2022-01-17
 */
public interface EmployeeService &#123;
    /**
     * Get employee
     *
     * @param id id
     * @return employee
     */
    Employee getEmployee(Integer id);
&#125;

com.yourname.cache.service.impl.EmployeeServiceImpl

/**
 * @author gregPerlinLi
 * @since 2022-01-17
 */
@Service
public class EmployeeServiceImpl implements EmployeeService &#123;
    @Autowired
    EmployeeMapper employeeMapper;

    /**
     * &#123;@code @Cacheable&#125; Cache the running results of the method, when you need the same data in the future, you can get it directly from the cache<br/>
     * &#123;@code CacheManager&#125; manage multiple cache components. The real CRUD operation on the cache is in the cache component, and each cache component has its own unique name<br/>
     * The attribute of &#123;@code @Cacheable&#125;<br/>
     *      &#123;@code cacheNames/value&#125;: Specifies the name of the cache<br/>
     *      &#123;@code key&#125;: The key used to cache data, The default is to use the value of the method parameter, 1-The return value of method<br/>
     *          use SpEL expression: &#123;@code #id&#125;; Value of parameter ID equals to &#123;@code #a0, #p0, #root.args[0]&#125;<br/>
     *      &#123;@code keyGenerator&#125;: The generator of key, you can specify the ID of the key generator<br/>
     *          You can only choose one of the &#123;@code Key&#125; or &#123;@code KeyGenerator&#125;<br/>
     *      &#123;@code cacheManager&#125;: Specify cache manager<br/>
     *      &#123;@code cacheResolver&#125;: Specify cache resolver<br/>
     *      &#123;@code condition&#125;: Specifies that the cache is only cached if the conditions are met<br/>
     *      &#123;@code unless&#125;: Negative caching. When the condition specified by unless is true, the return value of the method will not be cached, the results can be obtained for judgment <br/>
     *      &#123;@code  sync&#125;: Use asynchronous mode
     */
    @Override
    @Cacheable(cacheNames = &#123;"employee"&#125;, key = "#id", condition = "#id > 0", unless = "#result == null")
    public Employee getEmployee(Integer id) &#123;
        System.out.println("Query " + id + " employee.");
        return employeeMapper.getEmployeeById(id);
    &#125;
&#125;

com.yourname.cache.controller.EmployeeController

/**
 * @author gregPerlinLi
 * @since 2022-01-17
 */
@RestController
public class EmployeeController &#123;
    @Autowired
    EmployeeService employeeService;

    @GetMapping(value = "/emp/&#123;id&#125;")
    public Employee getEmployee(@PathVariable("id") Integer id) &#123;
        return employeeService.getEmployee(id);
    &#125;
&#125;

4.2. @Cachable 注解的具体用法

@Cacheable 注解将方法的运行结果进行缓存,当以后需要用到相同的数据时,可以直接从缓存中获取,而不需要调用方法

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

几个属性:

  • cacheNames指定缓存组件的名字
  • key缓存数据使用的 Key,可以用它来指定,默认是使用方法参数的值,通常需要编写 SpEL 表达式来实现,例如:#id → 参数 ID 的值,也可以是 #a0#p0#root.args[0],详细的 SpEL 表达式后面会讲到
  • keyGeneratorKey 的生成器;可以自己指定 Key 的生成器组件 ID(注意⚠️:key/keyGenerator 只能二选一使用)
  • cacheManager指定缓存管理器
  • cacheResolver指定缓存解析器
  • condition指定符合条件的情况下获取缓存
  • unless否定缓存,当 unless 指定条件为 true,方法的返回值就不会被缓存,可以获取到结果进行判断
  • sync是否使用异步模式

4.3. Spring 的基本缓存架构

CompostieCacheManager

5. 缓存的工作原理

  1. 自动配置类: CacheAutoConfiguration

  2. 缓存的配置类:

    CacheConfigurationClasses

  3. 默认生效的配置类: SimpleCacheConfiguration

  4. SimpleCacheConfiguration 的作用是给容器中注册了一个 CacheManagerConcurrentMapCacheManager

  5. CacheManager 可以获取和创建 ConcurrentMapCache 类型的缓存组件,其作用是将数据保存到 ConcurrentMap

6. 缓存的运行流程(以 @Cacheable 为例)

  1. 方法运行之前,先去查询 Cache(缓存组件),按照 cacheNames 指定的名字获取(CacheManager 先获取相应的缓存),第一次获取缓存的时候如果没有 Cache 组件会自动创建。
  2. Cache 中查找缓存的内容,使用一个 Key,默认是方法的参数;Key 是按照某种策略生成的,默认是使用 SimpleKeyGenerator 生成的
    • SimpleKeyGenerator 生成 Key 的默认策略:
      • 如果没有参数:key = new SimpleKey
      • 如果有一个参数:key = paramValue
      • 如果有多个参数:key = SimpleKey(params)
  3. 没有查到缓存,就调用目标方法
  4. 将目标方法返回的结果,放进缓存中

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

核心:

  1. 使用 cacheManagerConcurrentMapCacheManager」按照名字得到 CacheConcurrentMapCache」组件
  2. Key 使用 KeyGeneratorSimpleKeyGenerator」生成的

7. 缓存 @Cacheable 的其他属性

  • cacheNames指定缓存组件的名字,将方法返回结果放在哪个缓存中,使用数组的方式,可以指定多个缓存

  • key缓存数据使用的 Key,可以用它来指定,默认是使用方法参数的值,通常需要编写 SpEL 表达式来实现,例如:#id → 参数 ID 的值,也可以是 #a0#p0#root.args[0],详细的 SpEL 表达式后面会讲到

    示例:

    @Cacheable(cacheNames = &#123;"employee"&#125;, key = "#root.methodName + '[' + #id + ']'")
    
  • keyGeneratorKey 的生成器;可以自己指定 Key 的生成器组件 ID(注意⚠️:key/keyGenerator 只能二选一使用)

    示例:

    com.yourname.cache.config.MyCacheConfig

    /**
     * @author gregPerlinLi
     * @since 2022-01-17
     */
    @Configuration
    public class MyCacheConfig &#123;
        @Bean(value = "myKeyGenerator")
        public KeyGenerator keyGenerator() &#123;
            return (target, method, params) -> method.getName() + "[" + Arrays.asList(params).toString() + "]";
        &#125;
    &#125;
    
    @Cacheable(cacheNames = &#123;"employee"&#125;, keyGenerator = "myKeyGenerator")
    
  • cacheManager指定缓存管理器

  • cacheResolver指定缓存解析器

  • condition指定符合条件的情况下获取缓存

    示例:

    @Cacheable(cacheNames = &#123;"employee"&#125;, keyGenerator = "myKeyGenerator", condition = "#id > 1 and #root.methodName eq 'emp'")
    
  • unless否定缓存,当 unless 指定条件为 true,方法的返回值就不会被缓存,可以获取到结果进行判断

    示例:

    @Cacheable(cacheNames = &#123;"employee"&#125;, keyGenerator = "myKeyGenerator", ccondition = "#id > 1 and #root.methodName eq 'emp'", unless = "#a0 == 2")
    
  • sync是否使用异步模式

8. 缓存 SpEL 表达式的使用

名字 位置 描述 示例
methodName Root object 当前被调用的方法名 #root.methodName
mathod 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"}),则有两个 cahce #root.caches[0].name
argumentName Evaluation context 方法参数的名字,可以直接 #paramName,也可以使用 #p0#a0 的形式,0 代表参数的索引 #iban#a0#p0
result Evaluation context 方法执行后的返回值(仅当方法执行之后的判断有效,如 unlesscache put 的表达式 cache evict 的表达式 beforeInvocation = false #result

9. 缓存 @CachePut 的使用

@CatchPut 既调用方法,又更新缓存数据,达到同步更新缓存的效果(取缓存和放缓存部分应该使用同一个 Key)

运行时机:

  1. 先调用目标方法
  2. 将目标方法的结果缓存起来

示例代码:

com.yourname.cache.service.EmployeeService

/**
 * @author gregPerlinLi
 * @since 2022-01-17
 */
public interface EmployeeService &#123;
    /**
     * Update employee
     *
     * @param employee employee
     * @return employee
     */
    Employee updateEmployee(Employee employee);
&#125;

com.yourname.cache.service.impl.EmployeeServiceImpl

/**
 * @author gregPerlinLi
 * @since 2022-01-17
 */
@Service
public class EmployeeServiceImpl implements EmployeeService &#123;
    @Autowired
    EmployeeMapper employeeMapper;

    /**
     * &#123;@code @CatchPut&#125;: Both method calls and cache updates<br/>
     * A data in the database is modified and the cache is updated at the same time<br/>
     * Running time:<br/>
     * 1. Call the target method<br/>
     * 2. Cache the results of the target method<br/>
     * Testing procedure:<br/>
     * 1. Query employee 1, the query results will be put into the cache<br/>
     *      key: 1; value: XiaoMing<br/>
     * 2. Future query or previous results<br/>
     * 3. Update employee 1「lastName:gregPerlinLi; gender:0」<br/>
     *      The return value of the method is also put into the cache<br/>
     *      key: Incoming employee object; value: Returned employee object<br/>
     * 4. Query employee 1 again<br/>
     *      Employee 1 is not updated in the cache<br/>
     *      &#123;@code key = "#employee.id"&#125;: Employee ID using the incoming in parameter<br/>
     *      &#123;@code key = "#result.id"&#125;: Employee ID using the returned parameter (The key of &#123;@code @Cacheable&#125; cannot use this method)<br/>
     */
    @Override
    @CachePut(value = "employee", key = "#result.id")
    public Employee updateEmployee(Employee employee) &#123;
        System.out.println("Update employee: " + employee);
        employeeMapper.updateEmployee(employee);
        return employee;
    &#125;
&#125;

com.yourname.cache.controller.EmpoloyeeController

/**
 * @author gregPerlinLi
 * @since 2022-01-17
 */
@RestController
public class EmployeeController &#123;
    @Autowired
    EmployeeService employeeService;

    @GetMapping(value = "/emp")
    public Employee updateEmployee(Employee employee) &#123;
        return employeeService.updateEmployee(employee);
    &#125;
&#125;

10. 缓存 @CacheEvict 的使用

@CacheEvict 用于清除缓存,有以下的属性

  • value:使用的缓存
  • key:指定要清除的 Key
  • allEntries:是否要清除缓存内的所有 Key(默认是false
  • beforeInvocation:是否在方法之前执行清除缓存(默认是 false,即缓存清除操作实在方法执行之后执行,如果出现异常,缓存将不会清除)

示例代码:

com.yourname.cache.service.EmployeeService

/**
 * @author gregPerlinLi
 * @since 2022-01-17
 */
public interface EmployeeService &#123;
    /**
     * Delete employee
     *
     * @param id id
     */
    void deleteEmployee(Integer id);
&#125;

com.yourname.cache.service.impl.EmployeeServiceImpl

/**
 * @author gregPerlinLi
 * @since 2022-01-17
 */
@Service
public class EmployeeServiceImpl implements EmployeeService &#123;
    @Autowired
    EmployeeMapper employeeMapper;

    /**
     * &#123;@code @CatchEvict&#125;: Clear the cache<br/>
     *      &#123;@code key&#125;: Specify the data to purge<br/>
     *      &#123;@code allEntries = true&#125;: Specifies to clear all data<br/>
     *      &#123;@code beforeInvocation&#125;: Execute before method (Default is &#123;@code false&#125;)<br/>
     */
    @Override
    @CacheEvict(value = "employee", key = "#id")
    public void deleteEmployee(Integer id)&#123;
        System.out.println("Delete employee id: " + id);
        // employeeMapper.deleteEmployee(id);
    &#125;
&#125;

com.yourname.cache.controller.EmployeeController

/**
 * @author gregPerlinLi
 * @since 2022-01-17
 */
@RestController
public class EmployeeController &#123;
    @GetMapping(value = "/del-emp/&#123;id&#125;")
    public String deleteEmployee(@PathVariable("id") Integer id) &#123;
        employeeService.deleteEmployee(id);
        return "success";
    &#125;
&#125;

11. 缓存 @Caching & @CacheConfig 的使用

@Caching 为组合注解,可以用来定义复杂的缓存规则

@CacheConfig 用于抽取缓存的公共配置

示例代码:

com.yourname.cache.mapper.EmployeeMapper

/**
 * @author gregPerlinLi
 * @since 2022-01-17
 */
@Mapper
public interface EmployeeMapper &#123;
    /**
     * Get employee by last name
     *
     * @param lastName last name
     * @return employee
     */
    @Select(value = "SELECT * FROM employee WHERE last_name = #&#123;lastName&#125;")
    Employee getEmployeeByLastName(String lastName);
&#125;

com.yourname.cache.service.EmployeeService

/**
 * @author gregPerlinLi
 * @since 2022-01-17
 */
public interface EmployeeService &#123;
    /**
     * Get employee by last name
     * @param lastName last name
     * @return employee
     */
    Employee getEmployeeByLastName(String lastName);
&#125;

com.yourname.cache.service.impl.EmployeeServiceImpl

/**
 * @author gregPerlinLi
 * @since 2022-01-17
 */
@Service
@CacheConfig(cacheNames = "employee")
public class EmployeeServiceImpl implements EmployeeService &#123;
    @Autowired
    EmployeeMapper employeeMapper;

    /**
     * &#123;@code @Caching&#125;: Define complex caching rules
     */
    @Override
    @Caching(
            cacheable = &#123;
                    @Cacheable(/*value = "employee",*/ key = "#lastName")
            &#125;,
            put = &#123;
                    @CachePut(/*value = "employee",*/ key = "#result.id"),
                    @CachePut(/*value = "employee",*/ key = "#result.email")
            &#125;
    )
    public Employee getEmployeeByLastName(String lastName) &#123;
        return employeeMapper.getEmployeeByLastName(lastName);
    &#125;
&#125;

com.yourname.cache.controller.EmployeeController

/**
 * @author gregPerlinLi
 * @since 2022-01-17
 */
@RestController
public class EmployeeController &#123;
    @GetMapping(value = "/emp/last-name/&#123;lastName&#125;")
    public Employee getEmployeeByLastName(@PathVariable("lastName") String lastName) &#123;
        return employeeService.getEmployeeByLastName(lastName);
    &#125;
&#125;

12. 搭建 Redis 环境 & 测试

Spring 缓存抽象默认使用的是 ConcurrentMapCacheManager,其将数据保存在一个 ConcurrentMap<Object, Object> 中。而在实际开发中,我们通常会使用缓存中间件,如:Redis、Memcached、Ehcache等,这里以整合 Redis 作为缓存为例

Redis 是一个开源(BSD 许可)的,内存中的数据结构储存系统,它可以用作数据库、缓存和消息中间件。

搭建 Redis 环境:

  1. 安装 Redis(使用 Docker 安装)

  2. 使用客户端连接 Redis 数据库(这里推荐使用 RedisInsight)

  3. 引入 Redis 的 starter

    pom.xml

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>
    
  4. 配置 Redis

    application.yaml

    spring:
      redis:
        host: 127.0.0.1
        port: 16379
    
  5. 测试缓存

    原理: CacheManagerCache 缓存组件来实际给缓存中存取数据

    1. 引入 Redis 的 starte 之后,容器中保存的是 RedisCacheManager
    2. RedisCacheManager 创建 RedisCache 作为缓存组件,RedisCache 通过操作 Redis 还书数据的
    3. 默认保存数据 K-V 均是对象时,利用序列化来保存
      1. 引入了 Redis 的 startercacheManager 变为 RedisCacheManager
      2. 默认创建的 RedisCacheManager,在操作 Redis 的时候,使用的是 RedisTemplateObject<Object, Object>
      3. RedisTemplate<Object, Object> 是默认使用 JDK 的序列化机制

示例代码:

com.yourname.cache.config.MyRedisConfig

/**
 * @author gregPerlinLi
 * @since 2022-01-17
 */
@Configuration
public class MyRedisConfig &#123;
    @Bean
    public RedisTemplate<Object, Employee> employeeRedisTemplate(RedisConnectionFactory redisConnectionFactory) throws UnknownHostException &#123;
        RedisTemplate<Object, Employee> template = new RedisTemplate<Object, Employee>();
        template.setConnectionFactory(redisConnectionFactory);
        Jackson2JsonRedisSerializer<Employee> serializer = new Jackson2JsonRedisSerializer<Employee>(Employee.class);
        template.setDefaultSerializer(serializer);
        return template;
    &#125;
&#125;

com.yourname.cache.test.RedisTest

/**
 * @author gregperlinli 
 */
@SpringBootTest
class RedisTest &#123;
    @Autowired
    EmployeeMapper employeeMapper;
    /**
     * Operating string
     */
    @Autowired
    StringRedisTemplate stringRedisTemplate;
    /**
     * Operating object
     */
    @Autowired
    RedisTemplate redisTemplate;
    @Autowired
    RedisTemplate<Object, Employee> employeeRedisTemplate;

    /**
     * Five common data types of Redis:<br/>
     * String, List, Set, Hash, ZSet(Ordered set)<br/>
     * &#123;@code stringRedisTemplate.opsForValue()&#125;[String]<br/>
     * &#123;@code stringRedisTemplate.opsForList()&#125;[List]<br/>
     * &#123;@code stringRedisTemplate.opsForSet()&#125;[Set]<br/>
     * &#123;@code stringRedisTemplate.opsForHash()&#125;[Hash]<br/>
     * &#123;@code stringRedisTemplate.opsForZSet()&#125;[ZSet]<br/>
     */
    @Test
    public void test01() &#123;
        // Save data to Redis
        // stringRedisTemplate.opsForValue().append("msg", "hello");
        String msg = stringRedisTemplate.opsForValue().get("msg");
        System.out.println(msg);
        stringRedisTemplate.opsForList().leftPush("my_list", "1");
        stringRedisTemplate.opsForList().leftPush("my_list", "2");
        stringRedisTemplate.opsForList().leftPush("my_list", "3");
    &#125;
    /**
     * Test save object
     */
    @Test
    public void test02() &#123;
        // By default, if the object is saved, the JDK serialization mechanism is used to save the serialized data to Redis
        // redisTemplate.opsForValue().set("Employee01", employeeMapper.getEmployeeById(1));
        // 1. Save data in JSON
        // 2. Default serialization rule of RedisTemplate
        employeeRedisTemplate.opsForValue().set("Employee01", employeeMapper.getEmployeeById(1));
    &#125;
&#125;

13. 自定义 CacheManager

由于 RedisTemplate<Object, Object> 默认使用的是 JDK 的序列化机制,要想实现 JSON 序列化机制,需要自定义 CacheManager 来实现

示例代码:

com.yourname.cache.config.MyRedisConfig

/**
 * @author gregPerlinLi
 * @since 2022-01-17
 */
@Configuration
public class MyRedisConfig &#123;
    /**
     * Configure a universal cache manager
     *
     * @param redisConnectionFactory redis connection factory
     * @return redis cache manager
     */
    @Bean
    public RedisCacheManager employeeCacheManager(RedisConnectionFactory redisConnectionFactory) &#123;
        // Use the default configuration of the cache
        RedisCacheConfiguration configuration = RedisCacheConfiguration.defaultCacheConfig();
        // Use GenericJackson2JsonRedisSerializer as serializer
        configuration = configuration.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()));
        RedisCacheManager.RedisCacheManagerBuilder builder = RedisCacheManager.builder(redisConnectionFactory).cacheDefaults(configuration);
        return builder.build();
    &#125;
&#125;


文章作者: gregPerlinLi
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 gregPerlinLi !
  目录