SpringBoot整合Cache

一、Spring Boot与缓存

Java Caching定义了5个核心接口,分别是CachingProvider, CacheManager, Cache, EntryExpiry

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

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

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

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

  • 使用Spring缓存抽象时我们需要关注以下两点:
  • 确定方法需要被缓存以及他们的缓存策略
  • 从缓存中读取之前缓存存储的数据

1.1 缓存注解

名称含义
Cache缓存接口,定义缓存操作。实现有:RedisCache、EhCacheCache、 ConcurrentMapCache等
CacheManager缓存管理器,管理各种缓存(Cache)组件
@Cacheable主要针对方法配置,能够根据方法的请求参数对其结果进行缓存
@CacheEvict清空缓存,主要在删除数据后清理之前缓存的值
@CachePut保证方法被调用,又希望结果被缓存,主要用于数据修改后对缓存的数据进行刷新
@EnableCaching开启基于注解的缓存
keyGenerator缓存数据时key生成策略
serialize缓存数据时value序列化策略

1.2 注解主要参数

参数名解释示例
value | cacheNames缓存的名称,在 spring 配置文件中定义,必须指定至少一个,二选一@Cacheable(value=”mycache”) 或者 @Cacheable(cacheNames={”cache1”,”cache2”}
key | keyGenerator缓存的 key,可以为空,如果指定要按照 SpEL 表达 式编写,如果不指定,则缺省按照方法的所有参数进行组合,二选一@Cacheable(value=”testcache”,key=”#userName”)
condition缓存的条件,可以为空,使用 SpEL 编写,返回 true 或者 false,只有为 true 才进行缓存/清除缓存,在调用方法之前之后都能判断@Cacheable(value=”testcache”,condition=”#userName.length()>2”)
allEntries (@CacheEvict )是否清空所有缓存内容,缺省为 false,如果指定为 true,则方法调用后将立即清空所有缓存@Cacheable(value=”testcache”,condition=”#userName.length()>2”)
beforeInvocation (@CacheEvict)是否在方法执行前就清空,缺省为 false,如果指定 为 true,则在方法还没有执行的时候就清空缓存, 缺省情况下,如果方法执行抛出异常,则不会清空缓存@CachEvict(value=”testcache”, beforeInvocation=true)
unless (@CachePut) (@Cacheable)用于否决缓存的,不像condition,该表达式只在方 法执行之后判断,此时可以拿到返回值result进行判断。条件为true不会缓存,fasle才缓存@Cacheable(value=”testcache”,unless=”#result == null”)

1.3 SpEl表达式

名字位置描述示例
methodNameroot object当前被调用的方法名#root.methodName
methodroot object当前被调用的方法#root.method.name
targetroot object当前被调用的目标对象#root.target
targetClassroot object当前被调用的目标对象类#root.targetClass
argsroot object当前被调用的目标对象类#root.args[0]
cachesroot object当前方法调用使用的缓存列表(如@Cacheable(value={“cache1”, “cache2”})),则有两个cache#root.caches[0].name
argument nameevaluation context方法参数的名字,可以直接 #参数名 ,也可以使用 #p0或#a0 的 形式,0代表参数的索引;#iban 、 #a0 、 #p0
resultevaluation context方法执行后的返回值(仅当方法执行之后的判断有效,如 ‘unless’,’cache put’的表达式 ’cache evict’的表达式 beforeInvocation=false)#result

1.4 基本使用

包结构

在这里插入图片描述

  1. pom文件

     <parent>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-parent</artifactId>
            <version>2.6.2</version>
            <relativePath/> <!-- lookup parent from repository -->
        </parent>
    
    <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <scope>runtime</scope>
            </dependency>
            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
                <optional>true</optional>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-test</artifactId>
                <scope>test</scope>
            </dependency>
            <!--druid数据源-->
            <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>druid-spring-boot-starter</artifactId>
                <version>1.2.8</version>
            </dependency>
            <!--mybatis 依赖-->
            <dependency>
                <groupId>org.mybatis.spring.boot</groupId>
                <artifactId>mybatis-spring-boot-starter</artifactId>
                <version>2.2.1</version>
            </dependency>
            <!--缓存依赖-->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-cache</artifactId>
            </dependency>
        </dependencies>
    
  2. yml配置文件,配置数据源连接信息和mybatis的配置

    spring:
      datasource:
        type: com.alibaba.druid.pool.DruidDataSource
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql:///test?serverTimezone=UTC
        username: root
        password: root123456
    
    mybatis:
      configuration:
        #    开启mybatis-plus的驼峰命名
        map-underscore-to-camel-case: true
        #    开启sql打印
        log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
    debug: true
    
    
  3. 在启动类上开启Cache的注解扫描,并配置Mapper接口的扫描路径

    /**
     * 1.开启基于注解的缓存 @EnableCaching
     */
    @EnableCaching
    @MapperScan("com.veo.mapper")
    @SpringBootApplication
    public class SpringbootCacheApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(SpringbootCacheApplication.class, args);
        }
    
    }
    
  4. 创建对象

    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    public class Department implements Serializable {
        private Integer id;
        private String departmentName;
    }
    
    //------------------------------------------------
    
    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    public class Employee implements Serializable {
        private Integer id;
        private String lastName;
        private String email;
        private Integer gender;
        private Integer dId;
    }
    
  5. 创建对象的Mapper接口

    @Repository
    public interface EmployeeMapper {
    
        @Select("select * from employee")
        List<Employee> getAllEmployees();
    
        @Select("select * from employee where id=#{id}")
        Employee getEmployeeById(Integer id);
    
    
        @Update("update employee set last_name=#{lastName},email=#{email},gender=#{gender},d_id=#{dId} where id=#{id}")
        void updateEmployeeById(Employee employee);
    
        @Delete("delete from employee where id=#{id}")
        void deleteEmployeeById(Integer id);
    
        @Select("select * from employee wehre last_name=#{lastName}")
        Employee getEmployeeByLastName(String lastName);
    
        @Insert("insert into employee values(null,#{lastName},#{email},#{gender},#{dId})")
        void saveEmployee(Employee employee);
    }
    
    @Repository
    public interface DeptMapper {
    
        @Select("select * from department where id=#{id}")
        Department getDepartmentById(Integer id);
    }
    
  6. 创建Service接口

    public interface EmployeeService {
    
        Employee getEmployeeById(Integer id);
    
        List<Employee> getAllEmployees();
    
        Employee updateEmployeeById(Employee employee);
    
        void deleteEmployeeById(Integer id);
    
        Employee getEmployeeByLastName(String lastName);
    }
    
public interface DeptService {

    Department getDepartmentById(Integer id);
}
  1. 创建接口的实现类,在接口的实现类中使用Cache注解

    @Service
    public class EmployeeServiceImpl implements EmployeeService {
    
        @Autowired
        EmployeeMapper employeeMapper;
    
        /**
         *   cacheNames/value:指定缓存组件的名字;将方法的返回结果放在哪个缓存中,是数组的方式,可以指定多个缓存;
         *   key:缓存数据使用的key;可以用它来指定。默认是使用方法参数的值  1-方法的返回值
         *   编写SpEL; #id;参数id的值   #a0  #p0  #root.args[0]
         *   key = "#methodName+'['+#id+']'"  等价与 getEmp[2]
         *   keyGenerator:key的生成器;可以自己指定key的生成器的组件id
         *       key/keyGenerator:二选一使用;
         *    cacheManager:指定缓存管理器;或者cacheResolver指定获取解析器,二选一使用 
         *    condition:指定符合条件的情况下才缓存;
         *          condition = "#id>0"
         *          condition = "#a0>1":第一个参数的值》1的时候才进行缓存
         *    unless:否定缓存;当unless指定的条件为true,方法的返回值就不会被缓存;可以获取到结果进行判断
         *          unless = "#result == null"
         *          unless = "#a0==2":如果第一个参数的值是2,结果不缓存,返回结果为true不缓存,为false才缓存
         *    sync:是否使用异步模式,使用异步模式后unless不可以使用
         * @param id
         * @return
         */
        @Cacheable(cacheNames = "emp",keyGenerator = "myKeyGenerator",condition = "#id>1 and #id<5",unless = "#id==2 and #result==null")
        @Override
        public Employee getEmployeeById(Integer id) {
            System.out.println("getEmployeeById" + id);
            return employeeMapper.getEmployeeById(id);
        }
    
        @Override
        public List<Employee> getAllEmployees() {
            System.out.println("getAllEmployees");
            return employeeMapper.getAllEmployees();
        }
    
        @Override
        public Employee updateEmployeeById(Employee employee) {
            System.out.println("updateEmployeeById" + employee.getId());
            employeeMapper.updateEmployeeById(employee);
        }
    
        @Override
        public void deleteEmployeeById(Integer id) {
            System.out.println("deleteEmployeeById" + id);
            employeeMapper.deleteEmployeeById(id);
    
        }
    
        @Override
        public Employee getEmployeeByLastName(String lastName) {
            System.out.println("getEmployeeByLastName" + lastName);
            return employeeMapper.getEmployeeByLastName(lastName);
        }
    }
    
@Service
public class DeptServiceImpl implements DeptService {

    @Autowired
 DeptMapper deptMapper;

    @Override
    public Department getDepartmentById(Integer id) {
        return deptMapper.getDepartmentById(id);
    }
}
  1. 编写Controller

    @RestController
    public class EmployeeController {
    
        @Autowired
        EmployeeService employeeService;
    
        @GetMapping("/getAllEmp")
        public List<Employee> getAllEmployee(){
            return employeeService.getAllEmployees();
        }
    
        @GetMapping("/emp/{id}")
        public Employee getEmployeeById(@PathVariable("id")Integer id){
            return employeeService.getEmployeeById(id);
        }
    
        @GetMapping("/emp")
        public Employee updateEmployee(Employee employee){
            return employeeService.updateEmployeeById(employee);;
        }
    
        @GetMapping("/delEmp/{id}")
        public String deleteEmployee(@PathVariable("id") Integer id){
            employeeService.deleteEmployeeById(id);
            return "success";
        }
        
        @GetMapping("/getEmpByName/{name}")
        public Employee getEmpByName(@PathVariable("name") String lastName){
            return employeeService.getEmployeeByLastName(lastName);
        }
    }
    
  2. 配置自定义的keyGenerator

    @Configuration
    public class MyCacheConfig {
    
        @Bean
        public SimpleKeyGenerator keyGenerator(){
            return new SimpleKeyGenerator(){
                @Override
                public Object generate(Object target, Method method, Object... params) {
                    //自定key返回格式getEmployeeById[id]
                    return method.getName() + "[" + Arrays.asList(params) + "]";
                }
            };
        }
    }
    

结果:调用getEmployeeById(Integer id)方法后会缓存id为2,3,4并且返回结果不为空的对象到缓存中。

使用@CachePut、@CacheEvict、@Caching注解

	@Cacheable(cacheNames = "emp",key = "#id")
    @Override
    public Employee getEmployeeById(Integer id) {
        System.out.println("getEmployeeById" + id);
        return employeeMapper.getEmployeeById(id);
    }

/**
     * @CachePut:既调用方法,又更新缓存数据;同步更新缓存
     * 修改了数据库的某个数据,同时更新缓存;
     * 运行时机:
     *  1、先调用目标方法
     *  2、将目标方法的结果缓存起来
     *  3、可以使用spEl的result来获取方法返回的结果
     *
     * 测试步骤:
     *  1、查询1号员工;查到的结果会放在缓存中;
     *          key:1  value:lastName:张三
     *  2、以后查询还是之前的结果,从缓存中拿到的数据
     *  3、更新1号员工;【lastName:zhangsan;gender:0】
     *          将方法的返回值也放进缓存了;
     *          key:为传入的employee对象  值:返回的employee对象;
     *  4、查询1号员工?为什么是没更新前的?【1号员工没有在缓存中更新】
     *      应该是更新后的员工,但是查询发现还是第一次缓存的结果,并不是更新后的数据,主要为修改和查询时的key不同
     *      查询时key 为传入的参数的员工id,查询时key为传入的employee对象,此时缓存中有查询id的缓存所以没有更新
     *      解决方法:统一指定存入缓存中的key
     *          key = "#employee.id":使用传入的参数的员工id;
     *          key = "#result.id":使用返回后的id
     *      @Cacheable的key是不能用#result 方法执行之前先查缓存
     * @param employee
     */
    @CachePut(value = "emp",key = "#result.id")
    @Override
    public Employee updateEmployeeById(Employee employee) {
        System.out.println("updateEmployeeById" + employee.getId());
        employeeMapper.updateEmployeeById(employee);
        return employee;
    }

	/**
     * @CacheEvict:缓存清除
     *  key:指定要清除的数据
     *  allEntries = true:指定清除这个缓存中所有的数据
     *  beforeInvocation = false:缓存的清除是否在方法之前执行
     *      默认代表缓存清除操作是在方法执行之后执行;如果出现异常缓存就不会清除
     *  beforeInvocation = true:
     *      代表清除缓存操作是在方法运行之前执行,无论方法是否出现异常,缓存都清除
     */
    @CacheEvict(value = "emp",key = "#id",beforeInvocation = true,allEntries = true)
    @Override
    public void deleteEmployeeById(Integer id) {
        System.out.println("deleteEmployeeById" + id);
//        employeeMapper.deleteEmployeeById(id);
    }

 /**
     * @Caching 定义复杂的缓存规则
     * 这要定义的复杂规则中有@CachePut注解,则该方法一定会被执行
     * 所以根据名字查询方法一定会被调用,不会从缓存中拿数据
     * @param lastName
     * @return
     */
    @Caching(
            cacheable = {
                    @Cacheable(value="emp",key = "#lastName")
            },
            put = {
                    @CachePut(value="emp",key = "#result.id"),
                    @CachePut(value="emp",key = "#result.email")
            }
    )
    @Override
    public Employee getEmployeeByLastName(String lastName) {
        System.out.println("getEmployeeByLastName" + lastName);
        return employeeMapper.getEmployeeByLastName(lastName);
    }

抽取公共配置到@CacheConfig中

@CacheConfig(cacheNames = "emp",keyGenerator = "myKeyGenerator")
@Service
public class EmployeeServiceImpl implements EmployeeService {
    @Autowired
    EmployeeMapper employeeMapper;

    @Cacheable()
    @Override
    public Employee getEmployeeById(Integer id) {
        System.out.println("getEmployeeById" + id);
        return employeeMapper.getEmployeeById(id);
    }


    @CachePut()
    @Override
    public Employee updateEmployeeById(Employee employee) {
        System.out.println("updateEmployeeById" + employee.getId());
        employeeMapper.updateEmployeeById(employee);
        return employee;
    }


    @Override
    public List<Employee> getAllEmployees() {
        System.out.println("getAllEmployees");
        return employeeMapper.getAllEmployees();
    }

    @CacheEvict(beforeInvocation = true,allEntries = true)
    @Override
    public void deleteEmployeeById(Integer id) {
        System.out.println("deleteEmployeeById" + id);
//        employeeMapper.deleteEmployeeById(id);
        int i = 10/0;
    }
}

使用小结

  1. @Cacheable
    • 先去缓存中根据key查询数据,查到数据就返回,查不到执行目标方法后将返回的结果缓存,默认key为方法的参数
    • 不可以使用spEl的result来获取返回的结果
    • 可以指定缓存的添加和key的生成方式
  2. @CachePut
    • 先调用目标方法将方法的返回结果缓存,并根据key更新缓存中的数据,和@Cacheable使用的是要指定一致的key,否则会根据默认方法的参数进行缓存,查询的时候就不是更新之后的数据而还是先前的数据
    • 目标方法一定会得到执行
  3. @CacheEvict
    • 和上面注解一起使用时,也要配置一致的key,否则key默认为方法的参数值,默认是在方法执行之后清除对应key的缓存,方法执行有异常就不清除缓存
    • allEntries = true:指定清除这个缓存中所有的数据
    • beforeInvocation = true:代表清除缓存操作是在方法运行之前执行,无论方法是否出现异常,缓存都清除
  4. @Caching
    • 定义复杂的缓存规则
    • 其中有@CachePut注解时,目标方法一定会得到执行
  5. @CacheConfig
    • 抽取公共的配置属性到类上面

1.5 基本原理

查看缓存自动配置类CacheAutoConfiguration,自动配置类导入了CacheConfigurationImportSelector类,查看此类发现该类导入了

  • org.springframework.boot.autoconfigure.cache.GenericCacheConfiguration
  • org.springframework.boot.autoconfigure.cache.JCacheCacheConfiguration
  • org.springframework.boot.autoconfigure.cache.EhCacheCacheConfiguration
  • org.springframework.boot.autoconfigure.cache.HazelcastCacheConfiguration
  • org.springframework.boot.autoconfigure.cache.InfinispanCacheConfiguration
  • org.springframework.boot.autoconfigure.cache.CouchbaseCacheConfiguration
  • org.springframework.boot.autoconfigure.cache.RedisCacheConfiguration
  • org.springframework.boot.autoconfigure.cache.CaffeineCacheConfiguration
  • org.springframework.boot.autoconfigure.cache.SimpleCacheConfiguration 默认生效
  • org.springframework.boot.autoconfigure.cache.NoOpCacheConfiguration

十个CacheConfiguration配置类对象,默认生效的是SimpleCacheConfiguration查看该类发现该类型向容器中注入了一个ConcurrentMapCacheManager的缓存CacheManager,查看该类主要实现了CacheManager接口中的getCache(),方法返回Cache对象,在ConcurrentMapCacheManager中重写了该方法并返回一个ConcurrentMapCache对象来对Cache进行操作,在该ConcurrentMapCache中使用ConcurrentMap来存放Cache。

debug查看@Cacheable注解的运行流程如下:

  1. 方法运行之前,先去查询ConcurrentMapCache(缓存组件),根据cacheNames来获取Cache对象,由于第一次获取Cache组件还没有创建,此时Cache会自动创建,之后会去Cache中使用一个key(默认就是方法的参数),去缓存中查找,没有查到缓存就调用目标方法,并将目标方法返回的结果,放进缓存中。
  2. key默认是使用keyGenerator生成的,默认使用SimpleKeyGenerator生成key,生成策略如下:
    • 如果没有参数:key=new SimpleKey();
    • 如果有一个参数:key=参数的值
    • 如果有多个参数:key=new SimpleKey(params);

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

1.6 整合redis实现缓存

  1. 导入Redis依赖后默认的CacheManager为RedisCacheManager,之后就使用Redis来作为缓存工具

     <parent>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-parent</artifactId>
            <version>2.6.2</version>
            <relativePath/> <!-- lookup parent from repository -->
        </parent>
    
    <dependencies>
            <!--redis依赖-->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-data-redis</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <scope>runtime</scope>
            </dependency>
            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
                <optional>true</optional>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-test</artifactId>
                <scope>test</scope>
            </dependency>
            <!--druid数据源-->
            <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>druid-spring-boot-starter</artifactId>
                <version>1.2.8</version>
            </dependency>
            <!--mybatis 依赖-->
            <dependency>
                <groupId>org.mybatis.spring.boot</groupId>
                <artifactId>mybatis-spring-boot-starter</artifactId>
                <version>2.2.1</version>
            </dependency>
            <!--缓存依赖-->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-cache</artifactId>
            </dependency>
        </dependencies>
    
  2. yml配置Redis的连接信息

    spring:
      datasource:
        type: com.alibaba.druid.pool.DruidDataSource
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql:///ssm?serverTimezone=UTC
        username: root
        password: root
      redis:
        host: 127.0.0.1
        port: 6379
    
    mybatis:
      configuration:
        #    开启mybatis-plus的驼峰命名
        map-underscore-to-camel-case: true
        #    开启sql打印
        log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
    debug: true
    
    
  3. 默认RedisTemplate保存对象时使用jdk序列化机制来将对象序列化后保存到Redis中,这时去Redis中查看对象为乱码的格式,可以在容器中注入自定义的RedisTemplate使用json方法来序列化对象,方式如下,乱码如图:
    在这里插入图片描述

    • 对象实现序列化接口
    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    public class Employee implements Serializable {
        private Integer id;
        private String lastName;
        private String email;
        private Integer gender;
        private Integer dId;
    }
    
    //------------------------------------------------
    
    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    public class Department implements Serializable {
        private Integer id;
        private String departmentName;
    }
    
    
    • 配置序列化为json的自定义RedisTemplate对象并注入容器中
    @Configuration
    public class MyRedisConfig {
    
        //Employee的Redis序列化器
        @Bean("empRedisTemplate")
        public RedisTemplate<Object, Employee> empRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
            RedisTemplate<Object, Employee> template = new RedisTemplate<>();
            Jackson2JsonRedisSerializer<Employee> serializer = new Jackson2JsonRedisSerializer<Employee>(Employee.class);
            //根据源码查看可以自定义设置默认的序列化器,参考源码配置
            template.setDefaultSerializer(serializer);
            template.setConnectionFactory(redisConnectionFactory);
            return template;
        }
    
        //Department的Redis序列化器
        @Bean("deptRedisTemplate")
        public RedisTemplate<Object, Department> deptRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
            RedisTemplate<Object, Department> template = new RedisTemplate<>();
            Jackson2JsonRedisSerializer<Department> serializer = new Jackson2JsonRedisSerializer<Department>(Department.class);
            //根据源码查看可以自定义设置默认的序列化器
            template.setDefaultSerializer(serializer);
            template.setConnectionFactory(redisConnectionFactory);
            return template;
        }
    }
    
    • 测试
    @SpringBootTest
    class SpringbootCacheApplicationTests {
    
        @Autowired
        EmployeeService employeeService;
    
        @Autowired
        DeptService deptService;
    
        /**
         * 操作k-v都为object
         */
        @Autowired
        RedisTemplate redisTemplate;
    
        /**
         * 操作k-v都为字符串
         */
        @Autowired
        StringRedisTemplate stringRedisTemplate;
    
        @Autowired
        RedisTemplate<Object, Employee> employeeRedisTemplate;
    
        @Autowired
        RedisTemplate<Object, Department> departmentRedisTemplate;
    
        /**
    	 * Redis常见的五大数据类型
    	 *  String(字符串)、List(列表)、Set(集合)、Hash(散列)、ZSet(有序集合)
    	 *  stringRedisTemplate.opsForValue()[String(字符串)]
    	 *  stringRedisTemplate.opsForList()[List(列表)]
    	 *  stringRedisTemplate.opsForSet()[Set(集合)]
    	 *  stringRedisTemplate.opsForHash()[Hash(散列)]
    	 *  stringRedisTemplate.opsForZSet()[ZSet(有序集合)]
    	 */
        @Test
        void contextLoads2() {
            Employee employee = employeeService.getEmployeeById(2);
            employeeRedisTemplate.opsForValue().set("emp-01",employee);
            System.out.println(employeeRedisTemplate.opsForValue().get("emp-01"));
            Department department = deptService.getDepartmentById(1);
            departmentRedisTemplate.opsForValue().set("dept-01",department);
            System.out.println(departmentRedisTemplate.opsForValue().get("dept-01"));
        }
    
    }
    

在这里插入图片描述

  1. 使用RedisCacheManager缓存数据时,RedisCacheManager内部使用jdk的序列化方法进行缓存,所以在redis中会出入之前的乱码,这时可以自定义配置RedisCacheManager,配置如下:

    @Configuration
    public class MyRedisConfig {
    
        //Employee的Redis序列化器
        @Bean
        public RedisTemplate<Object, Employee> empRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
            RedisTemplate<Object, Employee> template = new RedisTemplate<>();
            Jackson2JsonRedisSerializer<Employee> serializer = new Jackson2JsonRedisSerializer<Employee>(Employee.class);
            //根据源码查看可以自定义设置默认的序列化器
            template.setDefaultSerializer(serializer);
            template.setConnectionFactory(redisConnectionFactory);
            return template;
        }
    
    
        //Department的Redis序列化器
        @Bean
        public RedisTemplate<Object, Department> deptRedisTemplate(RedisConnectionFactory redisConnectionFactory) {
            RedisTemplate<Object, Department> template = new RedisTemplate<>();
            Jackson2JsonRedisSerializer<Department> serializer = new Jackson2JsonRedisSerializer<Department>(Department.class);
            //根据源码查看可以自定义设置默认的序列化器
            template.setDefaultSerializer(serializer);
            template.setConnectionFactory(redisConnectionFactory);
            return template;
        }
    
        /**
         * 默认的序列化器
         * 有多个序列化器是要使用@Primay指定一个默认的序列化器
         * 要使用指定的序列化器是要在其缓存注解上面配置
         * @param redisConnectionFactory
         * @return
         */
        @Primary
        @Bean
        public RedisCacheManager defaultRedisCacheManager(RedisConnectionFactory redisConnectionFactory){
            RedisCacheConfiguration configuration = RedisCacheConfiguration.defaultCacheConfig()
                            //配置对key的序列化,这里使用string来序列化
                            .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
                            //配置对value的序列化,使用jsong来序列化
                            .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()))
                            //不保存null值的缓存
                            .disableCachingNullValues()
                            //设置key的前缀 默认为: prefix + cache name + "::" + cache entry key
                            .prefixCacheNameWith("->");
    
            RedisCacheManager cacheManager = RedisCacheManager.builder(redisConnectionFactory)
                    //配置RedisCacheManager的相关配置信息并将缓存中的内容和spring中的事务同步
                    .cacheDefaults(configuration).transactionAware().build();
    
            return cacheManager;
        }
    
        /**
         * 针对于Employee序列化的RedisCacheManager
         * @Bean 默认使用方法名作为bean的id
         * @param redisConnectionFactory
         * @return
         */
        @Bean
        public RedisCacheManager empRedisCacheManager(RedisConnectionFactory redisConnectionFactory){
            RedisCacheConfiguration configuration = RedisCacheConfiguration.defaultCacheConfig()
                    //配置对key的序列化,这里使用string来序列化
                    .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
                    //配置对value的序列化,使用jsong来序列化
                    .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new Jackson2JsonRedisSerializer<Employee>(Employee.class)))
                    //不保存null值的缓存
                    .disableCachingNullValues()
                    //设置key的前缀 默认为: prefix + cache name + "::" + cache entry key
                    .prefixCacheNameWith("->");
    
            RedisCacheManager cacheManager = RedisCacheManager.builder(redisConnectionFactory)
                    //配置RedisCacheManager的相关配置信息并将缓存中的内容和spring中的事务同步
                    .cacheDefaults(configuration).transactionAware().build();
    
            return cacheManager;
        }
    
        /**
         * 针对于Department序列化的RedisCacheManager
         * @Bean 默认使用方法名作为bean的id
         * @param redisConnectionFactory
         * @return
         */
        @Bean
        public RedisCacheManager deptRedisCacheManager(RedisConnectionFactory redisConnectionFactory){
            RedisCacheConfiguration configuration = RedisCacheConfiguration.defaultCacheConfig()
                    //配置对key的序列化,这里使用string来序列化
                    .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
                    //配置对value的序列化,使用jsong来序列化
                    .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new Jackson2JsonRedisSerializer<Department>(Department.class)))
                    //不保存null值的缓存
                    .disableCachingNullValues()
                    //设置key的前缀 默认为: prefix + cache name + "::" + cache entry key
                    .prefixCacheNameWith("->");
    
            RedisCacheManager cacheManager = RedisCacheManager.builder(redisConnectionFactory)
                    //配置RedisCacheManager的相关配置信息并将缓存中的内容和spring中的事务同步
                    .cacheDefaults(configuration).transactionAware().build();
    
            return cacheManager;
        }
    
    }
    
  2. 应用自定义配置的RedisCacheManager于缓存注解上面

    @CacheConfig(cacheNames = "dept",cacheManager = "deptRedisCacheManager")
    @Service
    public class DeptServiceImpl implements DeptService {
    
        @Autowired
        DeptMapper deptMapper;
    
        @Cacheable
        @Override
        public Department getDepartmentById(Integer id) {
            return deptMapper.getDepartmentById(id);
        }
    }
    
  3. 也可以编码方式使用缓存

    @Service
    public class DeptServiceImpl implements DeptService {
    
        @Autowired
        DeptMapper deptMapper;
    
        @Autowired
        @Qualifier("deptRedisCacheManager")
        RedisCacheManager redisCacheManager;
    
        @Override
        public Department getDepartmentById(Integer id) {
            Department department = deptMapper.getDepartmentById(id);
            Cache deptCache = redisCacheManager.getCache("dept");
            deptCache.put("->dept:" + id, department);
            return department;
        }
    }
    
  4. redis数据如下:

在这里插入图片描述
spring-cache官网文档

  • 6
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值