首页 > 其他分享 >SpringBoot 1x 系列之(八)Spring Boot与缓存

SpringBoot 1x 系列之(八)Spring Boot与缓存

时间:2024-02-28 10:15:28浏览次数:21  
标签:缓存 return SpringBoot 1x Boot id Employee employee public

Spring Boot与缓存

JSR-107、Spring缓存抽象、整合Redis

缓存:加速系统访问,提升系统性能

热点数据、临时数据(如验证码)

1. JSR-107

1.1 背景

统一缓存的开发规范及提升系统的扩展性,J2EE发布了JSR-107缓存规范

1.2 JSR107简介

CacheManager与Cache的关系,类比连接池与连接

涉及的包

javax.cache.cache-api

Cache中定义了缓存的CRUD操作

用的很少

2. Spring缓存抽象

2.1 Spring缓存抽象简介

为了简化开发,Spring提供了自己的缓存抽象

Spring 3.1 以后

支持使用JCache(JSR-107)注解

真正要操作缓存的是Cache组件

Cache接口的实现不同,缓存技术就不一样【RedisCache、EhCacheCache、ConcurrentMapCache等】

@Cacheable(数据存在缓存中就不会再调用方法)、@CacheEvict、@CachePut(方法总会被调用):作用在方法上,简化常见的缓存操作

缓存过程中会涉及到两个问题:key生成策略、value序列化策略【对象序列化、对象转JSON等】

2.2 基本环境搭建

2.2.1 导入数据库文件

这里以代码的形式给出

/*springboot_cache.sql*/
/*
Navicat MySQL Data Transfer

Source Server         : 本地
Source Server Version : 50528
Source Host           : 127.0.0.1:3306
Source Database       : springboot_cache

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

Date: 2018-04-27 14:54:04
*/

SET FOREIGN_KEY_CHECKS=0;

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

-- ----------------------------
-- Table structure for employee
-- ----------------------------
DROP TABLE IF EXISTS `employee`;
CREATE TABLE `employee` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `lastName` varchar(255) DEFAULT NULL,
  `email` varchar(255) DEFAULT NULL,
  `gender` int(2) DEFAULT NULL,
  `d_id` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

2.2.2 创建maven项目并引入坐标

1)创建项目过程略过

2)创建主配置类

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

3)resources目录下创建application.yml

4)pom中引入坐标【pom关于项目部分的内容没有给出】

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.5.9.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <dependencies>
        <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.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-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
2.2.3 创建JavaBean封装数据
//Department
public class Department {
   
   private Integer id;
   private String departmentName;
   
   
   public Department() {
      super();
      // TODO Auto-generated constructor stub
   }
   public Department(Integer id, String departmentName) {
      super();
      this.id = id;
      this.departmentName = departmentName;
   }
   public Integer getId() {
      return id;
   }
   public void setId(Integer id) {
      this.id = id;
   }
   public String getDepartmentName() {
      return departmentName;
   }
   public void setDepartmentName(String departmentName) {
      this.departmentName = departmentName;
   }
   @Override
   public String toString() {
      return "Department [id=" + id + ", departmentName=" + departmentName + "]";
   }
}

//Employee
public class Employee {
   
   private Integer id;
   private String lastName;
   private String email;
   private Integer gender; //性别 1男  0女
   private Integer dId;
   
   
   public Employee() {
      super();
   }

   
   public Employee(Integer id, String lastName, String email, Integer gender, Integer dId) {
      super();
      this.id = id;
      this.lastName = lastName;
      this.email = email;
      this.gender = gender;
      this.dId = dId;
   }
   
   public Integer getId() {
      return id;
   }
   public void setId(Integer id) {
      this.id = id;
   }
   public String getLastName() {
      return lastName;
   }
   public void setLastName(String lastName) {
      this.lastName = lastName;
   }
   public String getEmail() {
      return email;
   }
   public void setEmail(String email) {
      this.email = email;
   }
   public Integer getGender() {
      return gender;
   }
   public void setGender(Integer gender) {
      this.gender = gender;
   }
   public Integer getdId() {
      return dId;
   }
   public void setdId(Integer dId) {
      this.dId = dId;
   }
   @Override
   public String toString() {
      return "Employee [id=" + id + ", lastName=" + lastName + ", email=" + email + ", gender=" + gender + ", dId="
            + dId + "]";
   }
}

注:由于JavaBean属性(dId)和数据库表字段(d_id)不是一一对应的,需要开启驼峰命名匹配规则

# application.yml
spring:
  datasource:
    url: jdbc:mysql://localhost:3306/spring_cache
    username: root
    password: 123456
mybatis:
  configuration:
    map-underscore-to-camel-case: true
2.2.4 整合MyBatis操作数据库
2.2.4.1 配置数据源信息
spring:
  datasource:
    url: jdbc:mysql://localhost:3306/spring_cache
    username: root
    password: 123456
#    driver-class-name: com.mysql.jdbc.Driver

注:spring.datasource.driver-class-name可以不写,根据url可以自动判断

2.2.4.2 使用注解版的MyBatis

1)@MapperScan指定需要扫描的mapper接口所在的包

//主配置类
@MapperScan("com.atguigu.springboot.mapper")
@SpringBootApplication
public class SpringBootCacheApplication {
    public static void main(String[] args) {
        SpringApplication.run(SpringBootCacheApplication.class, args);
    }
}

2)编写Mapper

@Mapper
public interface EmployeeMapper {

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

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

    @Update("update employee set lastName=#{lastName}, email=#{email}, gender=#{gender} where id=#{dId}")
    public void updateEmployeeById(Employee employee);

    @Delete("delete from employee where id=#{id}")
    public void deleteEmployeeById(Integer id);
}

3)编写Service

@Service
public class EmployeeService {

    @Autowired
    EmployeeMapper employeeMapper;

    public void insertEmployee(Employee employee){
        employeeMapper.insertEmployee(employee);
    }

    public Employee getEmployeeById(Integer id){
        Employee employee = employeeMapper.getEmployeeById(id);
        return employee;
    }

    public void updateEmployee(Employee employee){
        employeeMapper.updateEmployeeById(employee);
    }

    public void deleteEmployeeById(Integer id){
        employeeMapper.deleteEmployeeById(id);
    }
}

4)编写Controller

@RestController
public class EmployeeController {

    @Autowired
    EmployeeService employeeService;

    @GetMapping("/emp/{id}")
    public Employee getEmployee(@PathVariable("id") Integer id){
        Employee employee = employeeService.getEmployeeById(id);
        return employee;
    }
}

测试成功!

2.3 快速体验缓存

2.3.1 使用步骤
  1. 开启基于注解的缓存【@EnableCaching】
  2. 标注缓存注解即可【@Cacheable、@CacheEvict、@CachePut】,缓存一般用在Service层
2.3.2 @Cacheable

将方法的返回值进行缓存,以后再要相同的数据,直接从缓存中取,不用再调用方法,一般用于查询方法

2.3.2.1 属性

1)cacheName/value:指定缓存组件的名字,将方法的返回值放在哪个缓存中,是数组的方式,可以指定多个缓存;【必填】

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

2)key:缓存数据时使用的key,默认是使用方法的参数值

支持SpEL,如#id,使用参数id的值作为key,取出参数id的值,SpEL支持的写法见下表

名字 位置 描述 示例
methodName root object 当前被调用的方法名 #root.methodName
method root object 当前被调用的方法 #root.method.name
target root object 当前被调用的目标对象 #root.target
targetClass root object 当前被调用的目标对象类 #root.targetClass
args root object 当前被调用的方法的参数列表 #root.args[0]
caches root object 当前方法调用使用的缓存列表(如@Cacheable(value={"cache1", "cache2"})),则有两个cache #root.caches[0].name
argument name evaluation context 方法参数的名字. 可以直接 #参数名 ,也可以使用 #p0或#a0 的形式,0代表参数的索引; #iban 、 #a0 、 #p0

@Cacheable的key是不能用#result的

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

注:key与keyGenerator二选一使用即可

4)cacheManager:指定缓存管理器

5)cacheResolver:指定缓存解析器

6)condition:指定符合条件的情况下才从缓存中取和放入缓存

使用SpEL进行指定

7)unless:否定缓存,条件为true不会进行缓存,可以通过#result 获取到方法返回值进行条件判断

如:unless = “#result == null”,缓存结果如果为null就不进行缓存

注:当同时存在condition和unless,如果满足condition条件同时又满足unless条件,那么依然不会进行缓存

8)sync:是否使用异步模式,默认不使用异步模式,如果使用异步模式,就不支持unless

2.3.2.2 运行流程

1)@Cacheable标注的方法运行之前,CacheManager按照cacheNames指定的名字获取Cache(缓存组件),第一次获取Cache如果没有会自动创建Cache组件。

2)去Cache中查找缓存的内容,使用一个key,默认就是方法的参数;

key是按照某种策略生成的,默认是使用SimpleKeyGenerator生成的key

SimpleKeyGenerator生成key的默认策略:

  1. 如果没有参数:key=new SimpleKey();
  2. 如果有一个参数:key=参数的值
  3. 如果有多个参数:key=new SimpleKey(params);|

3)没有查到缓存就调用目标方法(@Cacheable标注的方法)

4)将目标方法的返回值放进缓存中

总结如下:

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

  1. 使用CacheManager【ConcurrentMapCacheManager】按照名字得到Cache【ConcurrentMapCache】组件
  2. key使用keyGenerator生成的,默认是SimpleKeyGenerator
2.3.2.3 实验

实验是基于2.2.4.2构建的注解版Mybatis项目

1)keyGenerator属性练习

首先我们需要在容器中放一个自定义的KeyGenerator

自定义的CacheConfig如下:

@Configuration
public class CacheConfig {

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

使用自定义的KeyGenerator

@Service
public class EmployeeService {

    @Autowired
    EmployeeMapper employeeMapper;

    @Cacheable(value = "emp", keyGenerator = "myKeyGenerator")
    public Employee getEmployeeById(Integer id){
        Employee employee = employeeMapper.getEmployeeById(id);
        return employee;
    }
}

2)condition属性练习

使用condition属性

@Service
public class EmployeeService {

    @Autowired
    EmployeeMapper employeeMapper;

  	//当@Cacheable标注方法的第一个参数大于1并且方法名为getEmployeeById时才对返回值进行缓存
    @Cacheable(value = "emp", condition = "#a0>1 and #root.methodName eq 'getEmployeeById'")
    public Employee getEmployeeById(Integer id){
        Employee employee = employeeMapper.getEmployeeById(id);
        return employee;
    }
}
2.3.3 @CachePut

@CachePut: 既调用方法,又更新缓存数据;同步更新缓存

取缓存用的key和更新用的key必须是同一个key,才能达到修改数据库同步缓存的效果

修改了数据库的某个数据,同时更新缓存;

2.3.3.1 属性

同@Cacheable

2.3.3.2 运行流程
  1. 先调用目标方法
  2. 将目标方法的结果缓存起来

更新的值对应的键和查询方法的键不是一个

2.3.3.3 实验

实验步骤:
1、查询1号员工;查到的结果会放在缓存中,
​ key: 1 value: id为1的employee对象
2、以后查询还是之前的结果
3、更新1号员工: 【lastName :zhangsan; gender:0】
​ 将方法的返回值也放进缓存了;
​ key:传入的employee对象 值:返回的employee对象;
4、查询1号员工?
​ 应该是更新后的员工,但实际却是更新前的【1号员工没有在缓存中更新】,这是因为更新的值对应的键和查询方法的键不是一个
​ key = "#employee. id" :使用传入参数的employee对象id;
​ key = "#result. id":使用返回后的employee对象id
​ @Cacheable的key不能用#result

是不是删掉emp缓存中的所有数据

制定了allEntries,就不用指定key了

2.3.4 @CacheEvict

缓存清除

2.3.4.1 属性

除@Cacheable内说明的属性外,还有以下属性:

1)allEntries
指定清除时是否要清除这个缓存中所有的数据,默认为true,指定该参数时,就不需要再指定key了
2)beforeInvocation

缓存的清除是否在方法之前执行

beforeInvocation = false:默认代表缓存清除操作是在方法执行之后执行;如果方法内出现异常缓存就不会清除
beforeInvocation =true:代表清除缓存操作是在方法运行之前执行,无论方法是否出现异常,缓存都清除

2.3.5 @Caching

定义复杂的缓存规则

2.3.5.1 属性

可以设置@Cacheable、@CachePut、@CacheEvict数组

2.3.5.2 实验

Mapper

@Select("select * from employee where lastName=#{lastName}")
public Employee getEmployeeByLastName(String lastName);

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

Service

@CacheConfig(cacheNames = "emp")
@Service
public class EmployeeService {

    @Autowired
    EmployeeMapper employeeMapper;

    @Caching(cacheable = {
            @Cacheable(key = "#lastName")
    },put = {
            @CachePut(key = "#result.id"),
            @CachePut(key = "#result.email")
    })
    public Employee getEmployeeByLastName(String lastName){
        return employeeMapper.getEmployeeByLastName(lastName);
    }

    @Cacheable
    public Employee getEmployeeById(Integer id){
      Employee employee = employeeMapper.getEmployeeById(id);
      return employee;
    }
}

Controller

@RestController
public class EmployeeController {
	@GetMapping("/emp/lastname/{lastname}")
    public Employee getEmployeeByName(@PathVariable("lastname") String lastname){
        Employee employee = employeeService.getEmployeeByLastName(lastname);
        return employee;
    }

    @GetMapping("/emp/{id}")
    public Employee getEmployee(@PathVariable("id") Integer id){
        Employee employee = employeeService.getEmployeeById(id);
        return employee;
    }
}

​ 首先访问http://localhost:8080/emp/lastname/giegie ,执行后emp缓存中会同时存在lastName和employee对象、id和employee对象、email和employee对象三组键值对,再次访问http://localhost:8080/emp/lastname/giegie ,仍然会访问数据库,这是因为@Caching中同时设置了@Cacheable和@CachePut,由于存在@CachePut,所以即使缓存中有lastName和employee对象的键值对,也会先执行方法,查询数据库。然后访问http://localhost:8080/emp/2 ,不会访问数据库,因为缓存中已经存在了id和employee对象的键值对。

2.3.6 @CacheConfig

标注在类上,抽取缓存的公共配置,在@CacheConfig设置过的属性,当前类内其他的缓存注解就不需要再次设置了,所有的缓存注解都会生效。

2.3.6.1 属性

参考@Cacheable

2.4 缓存的工作原理

1)自动配置类:CacheAutoConfiguration

2)缓存的配置类【这些配置是有顺序的,越往上越优先配置】

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.GuavaCacheConfiguration
org.springframework.boot.autoconfigure.cache.SimpleCacheConfiguration
org.springframework.boot.autoconfigure.cache.NoOpCacheConfiguration

3)哪个配置类默认生效:SimpleCacheConfiguration

注:通过开启自动配置报告,可以看到哪些配置类生效了

# application.yml
debug: true

4)SimpleCacheConfiguration给容器中注册了一个CacheManager【ConcurrentMapCacheManager】

5)ConcurrentMapCacheManager可以获取和创建ConcurrentMapCache类型的缓存组件

6)ConcurrentMapCache的作用是将数据保存在ConcurrentMap中

3. 整合redis

​ 默认使用的是ConcurrentMapCacheManager的ConcurrentMapCache作为缓存组件,将数据保存在ConcurrentMap<Object, Object>中

开发中一般使用缓存中间件,如redis、memecached、ehcache。

对于SpringBoot支持的缓存配置,导入相应的依赖后相应的配置就会生效。

jedis:redis的客户端

3.1 安装redis

通过Docker或本地安装

3.2 引入依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

3.3 配置redis

如果redis不在本地,需要在全局中添加如下配置

spring:
  redis:
    host: localhost
    # 默认为本地

3.4 redis的自动配置

自动配置类:RedisAutoConfiguration

自动配置类中提供了Spring用来简化操作redis的RedisTemplate (操作k-v都是字符串的)和StringRedisTemplate (操作k-v都是对象的)

3.5 操作redis

3.5.1 操作五大数据类型

redis常见的五大数据类型
String (字符串)、List (列表)、Set (集合)、Hash (散列)、ZSet (有序集合)

stringRedisTemplate. opsForValue()[简化操作String (字符串) ]
stringRedisTemplate . opsForlist()[简化操作List (列表) ]
stringRedisTemplate. opsForSet()[简化操作Set (无序集合) ]
stringRedisTemplate. opsForHash()[简化操作Hash (散列) ]
stringRedisTemplate. opsForZSet()[简化操作ZSet (有序集合) ]

RedisTemplate对这些操作也是支持的。

注:StringRedisTemplate和RedisTemplate中的方法就相当于redis中的命令,虽然操作redis的方式不同,但实现的功能是相同的

3.5.2 操作对象

要求对象必须是可序列化的(实现了Serializable接口)

1)使用默认序列化机制存储对象

默认如果保存对象,使用jdk序列化机制,将序列化后的数据保存到redis中
redisTemplate. opsForValue(). set( "emp-01 ", emp);

2)以json的方式保存对象
(1)自己将对象转为json;
(2)改变redisTemplate默认的序列化规则;

//定义一个我们自己的配置类,用于向容器中放组件
@Configuration
public class RedisConfig {

  	//向容器中放一个自定义的RedisTemplate,指定泛型,专门用于序列化Employee的对象
    @Bean
    public RedisTemplate<Object, Employee> empredisTemplate(
            RedisConnectionFactory redisConnectionFactory)
            throws UnknownHostException {
        RedisTemplate<Object, Employee> template = new RedisTemplate<Object, Employee>();
        template.setConnectionFactory(redisConnectionFactory);
      	//更改默认的序列化规则
        Jackson2JsonRedisSerializer<Employee> employeeJackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<Employee>(Employee.class);
        template.setDefaultSerializer(employeeJackson2JsonRedisSerializer);
        return template;
    }
}

测试

@RunWith(SpringRunner.class)
@SpringBootTest
public class CRUDTest {
    @Autowired
    RedisTemplate<Object, Employee> empredisTemplate;
  
  	@Test
    public void testRedis(){
        ValueOperations<Object, Employee> operations = empredisTemplate.opsForValue();
        operations.set("emp", new Employee(1, "hello redis", "[email protected]", 2, null));
    }
}

这样序列化为JSON就成功了

3.6 缓存原理

​ 通过配置类给容器中添加CacheManager,CacheManager创建Cache缓存组件,Cache来实际给缓存中存取数据。

​ 对应到redis,引入redis的starter后,容器中保存的是RedisCacheManager,RedisCacheManager帮我们创建RedisCache来作为缓存组件,RedisCache通过操作redis缓存数据。

3.7 测试

这里的测试调用了一个查询数据的方法

@CacheConfig(cacheNames = "emp")
@Service
public class EmployeeService {

    @Autowired
    EmployeeMapper employeeMapper;
  
    @Cacheable
    public Employee getEmployeeById(Integer id){
        Employee employee = employeeMapper.getEmployeeById(id);
        return employee;
    }
}

测试发现存入redis的数据如下

默认保存数据k-v都是object,是利用序列化保存的,整个缓存流程如下:

1)引入了redis的starter后,RedisCacheConfiguration会生效

2)RedisCacheConfiguration会首先加载RedisAutoConfiguration

3)RedisAutoConfiguration会向容器中放入RedisTemplate<Object, Object>

4)RedisAutoConfiguration加载完成后,RedisCacheConfiguration进行加载,将上一步放入的RedisTemplate<Object, Object>作为参数,调用下面的方法,向容器中放入RedisCacheManager

5)从下图中可以看到,初始化RedisCacheManager的时候传入了RedisTemplate<Object, Object>

6)RedisTemplate<object, object> 默认使用的是jdk的序列化机制

那么如何保存为json呢,通过自定义CacheManager实现,因为redis提供的CacheManager入参已经写死了泛型,RedisTemplate<Object, Object>,即使我们自定义RedisTemplate<Object, Employee>放入容器,创建RedisCacheManager时也不会传入我们自定义的RedisTemplate<Object, Employee>

自定义CacheManager

@Configuration
public class RedisConfig {

    @Bean
    public RedisTemplate<Object, Employee> empredisTemplate(
            RedisConnectionFactory redisConnectionFactory)
            throws UnknownHostException {
        RedisTemplate<Object, Employee> template = new RedisTemplate<Object, Employee>();
        template.setConnectionFactory(redisConnectionFactory);
        Jackson2JsonRedisSerializer<Employee> employeeJackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<Employee>(Employee.class);
        template.setDefaultSerializer(employeeJackson2JsonRedisSerializer);
        return template;
    }

    @Bean
    public RedisCacheManager cacheManager(RedisTemplate<Object, Employee> empredisTemplate) {
        RedisCacheManager cacheManager = new RedisCacheManager(empredisTemplate);
        cacheManager.setUsePrefix(true);
        return cacheManager;
    }
}

存入redis的数据如下

这里还有个问题,存入的数据有个前缀

这是因为我们在创建RedisCacheManager后,设置了使用前缀,默认将cachename作为key的前缀

cacheManager.setUsePrefix(true);

当多个实体类共用同一个RedisCacheManager时,就会出现问题。

比如,还是按照上面的RedisCacheManager,这个RedisCacheManager是单独针对Employee的,现在又多了一个实体类,Department,操作Department时也需要用到缓存

@Service
public class DepartmentService {

    @Autowired
    DepartmentMapper departmentMapper;

    @Cacheable("dept")
    public Department getDepartmentById(Integer id){
        return departmentMapper.getDepartmentById(id);
    }
}
@RestController
public class DepartmentController {

    @Autowired
    DepartmentService departmentService;

    @GetMapping("/dept/{id}")
    public Department getDepartment(@PathVariable("id") Integer id){
        return departmentService.getDepartmentById(id);
    }
}

向缓存中存数据的时候是没有问题的

但是再次刷新页面,从缓存中取数据就会报错,查询到的数据不能反序列化回来

这是因为存的是dept的json数据,CacheManager使用RedisTemplate<Object, Employee>操作redis,没有办法反序列化Department。

解决方法是定义两套RedisCacheManager和RedisTemplate,我们自己的配置类如下:

@Configuration
public class RedisConfig {

    @Bean
    public RedisTemplate<Object, Employee> empredisTemplate(
            RedisConnectionFactory redisConnectionFactory)
            throws UnknownHostException {
        RedisTemplate<Object, Employee> template = new RedisTemplate<Object, Employee>();
        template.setConnectionFactory(redisConnectionFactory);
        Jackson2JsonRedisSerializer<Employee> employeeJackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<Employee>(Employee.class);
        template.setDefaultSerializer(employeeJackson2JsonRedisSerializer);
        return template;
    }

//    @Primary:设置默认缓存管理器
    @Primary
    @Bean
    public RedisCacheManager cacheManager(RedisTemplate<Object, Employee> empredisTemplate) {
        RedisCacheManager cacheManager = new RedisCacheManager(empredisTemplate);
        cacheManager.setUsePrefix(true);
        return cacheManager;
    }

    @Bean
    public RedisTemplate<Object, Department> departmentRedisTemplate(
            RedisConnectionFactory redisConnectionFactory)
            throws UnknownHostException {
        RedisTemplate<Object, Department> template = new RedisTemplate<Object, Department>();
        template.setConnectionFactory(redisConnectionFactory);
        Jackson2JsonRedisSerializer<Department> employeeJackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<Department>(Department.class);
        template.setDefaultSerializer(employeeJackson2JsonRedisSerializer);
        return template;
    }

    @Bean
    public RedisCacheManager deptcacheManager(RedisTemplate<Object, Department> departmentRedisTemplate) {
        RedisCacheManager cacheManager = new RedisCacheManager(departmentRedisTemplate);
        cacheManager.setUsePrefix(true);
        return cacheManager;
    }
}

​ 由于有两个RedisCacheManager,我们需要用@Primary 注解设置其中一个为默认缓存管理器,同时需要在用到缓存注解的地方通过cacheManager 属性指定要使用的缓存管理器,如果使用的是默认的缓存管理器,就不需要指定。

@Service
public class DepartmentService {

    @Autowired
    DepartmentMapper departmentMapper;

    @Cacheable(value = "dept",cacheManager = "deptcacheManager")
    public Department getDepartmentById(Integer id){
        return departmentMapper.getDepartmentById(id);
    }
}

通过编码的方式使用缓存

以上都是通过注解的方式使用缓存的,那么如何通过编码的方式使用缓存呢

@Service
public class DepartmentService {

    @Autowired
    DepartmentMapper departmentMapper;

    @Qualifier("deptcacheManager")
    @Autowired
    RedisCacheManager deptcacheManager;

    public Department getDepartmentById(Integer id){
        Department department = departmentMapper.getDepartmentById(id);
      	//使用缓存管理器得到缓存组件,进行API调用即可
        deptcacheManager.getCache("dept").put("12", department);
        return department;
    }
}

这样就可以通过编码使用缓存了

这里遇到一个没有解决的问题,我在容器中是放了两个RedisCacheManager的

@Bean
public RedisCacheManager deptcacheManager(RedisTemplate<Object, Department> departmentRedisTemplate) {
    RedisCacheManager cacheManager = new RedisCacheManager(departmentRedisTemplate);
    cacheManager.setUsePrefix(true);
    return cacheManager;
}
@Primary
@Bean
public RedisCacheManager cacheManager(RedisTemplate<Object, Employee> empredisTemplate) {
  RedisCacheManager cacheManager = new RedisCacheManager(empredisTemplate);
  cacheManager.setUsePrefix(true);
  return cacheManager;
}

如果我通过如下的编码方式操作缓存

@Service
public class DepartmentService {

    @Autowired
    DepartmentMapper departmentMapper;

    @Autowired
    RedisCacheManager deptcacheManagers;

    public Department getDepartmentById(Integer id){
        Department department = departmentMapper.getDepartmentById(id);
        deptcacheManagers.getCache("dept").put("12", department);
        return department;
    }
}

这里的deptcacheManagers仍然能获取到值,非常奇怪???

标签:缓存,return,SpringBoot,1x,Boot,id,Employee,employee,public
From: https://www.cnblogs.com/wzzzj/p/18039117

相关文章

  • SpringBoot 1x 系列之(七)自定义starter
    自定义starterstarters原理、自定义starters如何自定义starter:​ 1、这个场景需要使用到的依赖是什么?​ 2、如何编写自动配置@Configuration//指定这个类是一个配置类@ConditionalOnXXX//在指定条件成立的情况下自动配置类生效@AutoConfigureAfter//指定自动配置类的......
  • SpringBoot 1x 系列之(六)Spring Boot启动配置原理
    SpringBoot启动配置原理启动原理、运行流程、自动配置原理几个重要的事件回调机制(这几个事件回调机制可供我们进行干预)配置在META-INF/spring.factoriesApplicationContextInitializerSpringApplicationRunListener只需要放在ioc容器中(@Component标注)ApplicationRunnerCo......
  • SpringBoot 1x 系列之(五)SpringBoot与数据访问
    SpringBoot与数据访问JDBC、MyBatis、SpringDataJPASpringBoot底层是使用的SpringData作为数据访问的默认处理方式。1.整合基本JDBC与数据源Pom依赖<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-jdbc</artifactId><......
  • SpringBoot 1x 系列之(四)Spring Boot与Web开发
    SpringBoot与Web开发Thymeleaf、Web定制、容器定制1.如何使用SpringBoot创建SpringBoot应用,选中我们需要的模块SpringBoot已经默认将这些场景配置好了,我们只需要在配置文件中指定少量配置就可以运行起来编写业务代码2.SpringBoot对静态资源的映射规则普通的web应用......
  • SpringBoot 1x 系列之(三)SpringBoot与日志
    SpringBoot与日志日志框架、日志配置1.日志框架JDBC和数据库驱动:JDBC是统一的接口层(抽象层),面向JDBC进行开发,而不直接面向数据库驱动,这样的好处是数据库驱动会不断的出现新产品,如果直接面向数据库驱动开发,那么,每次更换数据库驱动,开发的代码就要做相应的调整,而面向JDBC开发,不管......
  • SpringBoot 1x 系列之(二)SpringBoot 配置
    SpringBoot配置配置文件、加载顺序、配置原理1.配置文件SpringBoot默认使用两种类型的配置文件作为一个全局配置文件,配置文件名固定,用于修改SpringBoot自动配置的默认值application.propertiesapplication.y(a)ml1.1YAML简介YAML(YAMLAin'tMarkupLanguage)递归缩写......
  • SpringBoot 1x 系列之(一)SpringBoot 入门
    SpringBoot入门SpringBoot和微服务概念的简介、SpringBootHelloWorld入门程序、内部原理1.SpringBoot简介简化Spring应用开发(整个J2EE开发)的一个框架整个Spring技术栈的一个大整合.........J2EE开发的一站式解决方案注:SpringBoot使用嵌入式的Servlet容器,应用无......
  • spring boot 中使用MybatisPlus的自动填充createTime和updateTime
    首先需要在实体类的字段上加上注解,并且将类型更改为LocalDateTime@TableField(fill=FieldFill.INSERT)@JsonInclude(value=JsonInclude.Include.NON_NULL)@JsonFormat(pattern="yyyy-MM-ddHH:mm:ss")privateLocalDateTimecreateTime;@TableFie......
  • springboot学习过程中的特殊错误
     这是我在学习使用springboot过程中遇到的一个小问题,询问了gpt但是并没有解决我的报错,在网上浏览信息后最终知道了是哪里出了问题 就如这个好哥哥说的一样,mybatis自带的方法不会出现问题,所以问题出在了实体类定义上面,加了@Tableld的注解就解决了问题。......
  • springboot 统一处理请求非法参数
    通过拦截器和过滤器实现,话不多说上代码。1、重写HttpServletRequestWrapper读取body里面的内容。publicclassRequestWrapperextendsHttpServletRequestWrapper{privatefinalStringbody;publicRequestWrapper(HttpServletRequestrequest){super......