前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >SpringBoot与缓存

SpringBoot与缓存

作者头像
OY
发布2022-03-12 14:20:38
4100
发布2022-03-12 14:20:38
举报
文章被收录于专栏:OY_学习记录OY_学习记录

Spring Boot 与缓存

创建项目结构

  • 集成开发工具 IDEA 2020.2 , 使用 spring 项目搭建向导创建

一、搭建基本环境

  1. 导入数据库文件,创建出departmentemployee
代码语言:javascript
复制
create database springboot_cache;

CREATE TABLE `department` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `departmentName` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;


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;
  1. 创建 javaBean 封装数据

【Department.java】

代码语言:javascript
复制
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.java】

代码语言:javascript
复制
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 + "]";
	}
}
  1. 整合 MyBatis 操作数据库 1)、配置数据源信息
代码语言:javascript
复制
spring.datasource.url=jdbc:mysql://localhost:3306/springboot_cache?serverTimezone=GMT%2B8
spring.datasource.username=root
spring.datasource.password=1234
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

#配置mybatis驼峰命名法规则
mybatis.configuration.map-underscore-to-camel-case=true

#开启缓存
logging.level.com.oy.springboot.mapper=debug

​ 2)、使用注解版的 Mybatis:

  • @MapperScan 指定需要扫描的 mapper 接口所在的包
代码语言:javascript
复制
@MapperScan("com.oy.springboot.mapper")
@SpringBootApplication
public class SpringBoot01CacheApplication {

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

二、快速体验缓存

==步骤:==

① 开启基于注解的缓存 @EnableCaching

代码语言:javascript
复制
@MapperScan("com.oy.springboot.mapper")
@SpringBootApplication
@EnableCaching // 开启注解
public class SpringBoot01CacheApplication {

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

}

② 标注缓存注解即可

  • @Cacheable
  • @CacheEvict
  • @CachePut

【EmployeeMapper】

代码语言:javascript
复制
@Mapper
public interface EmployeeMapper {

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

    @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 insertEmpById(Integer id);

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

}

【EmployeeService】

代码语言:javascript
复制
@Service
public class EmployeeService {

    @Autowired
    EmployeeMapper employeeMapper;

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

【EmployeeController】

代码语言:javascript
复制
@RestController
public class EmployeeController {

    @Autowired
    EmployeeService employeeService;

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

测试

  • 第一次发送请求:
  • 控制台输出
  • 发送第二次请求,查看控制台没有发生改变(说明缓存生效

三、缓存原理

① 重要的概念&缓存注解

注解

描述

Cache

缓存接口,定义缓存的操作。实现有:RedisCache、EhCacheCache、ConcurrentMapCache 等

CacheManager

缓存管理器,管理各种缓存(Cache )组件

@Cacheable

主要针对方法配置,能根据方法的请求参数对其结果进行缓存

@CacheEvict

清空缓存

@CachePut

保证方法被调用,又希望结构别缓存

@EnableCaching

开启基于注解的缓存

keyGenerator

缓存数据时 key 生成策略

serialize

缓存数据时 value 序列化策略

② CacheManager

将方法的运行结果进行缓存:以后再要相同的数据,直接从缓存中获取,不在调用方法: CacheManager 管理多个 Cache 组件的,对缓存的真正 CRUD(增删查改)操作在 Cache 组件中,每一个缓存组件有自己的唯一一个名字。

原理

  1. 自动配置类:CacheAutoConfiguration
  2. 缓存的配置类:
代码语言:javascript
复制
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
  1. SimpleCacheConfiguration 配置类默认生效

在【application.properties】配置

代码语言:javascript
复制
debug=true

控制台输出日志:

  1. 给容器中注册一个 CacheManager: ConcurrentMaCacheManager: 可以获取和创建 ConcurrentMapCache 类型的缓存组件;它的作用将数据保存砸 ConcurrentMap 中。

③ @Cacheble

运行流程

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

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

核心

  • 使用 CacheManager【ConcurrentMapCacheManager】按照名字得到 Cache【ConcurrentMapCache】组件
  • key 使用 keyGenerator 生成的,默认是 SimpleKeyGenerator

属性

  • CacheNames/value: 指定缓存组件的名字;将方法的返回结果放在哪个缓冲区,是数组的方式,可以指定多个缓存:
  • key: 缓存数据使用 key: 可以用它来指定,默认是使用方法参数 1-方法的返回值
    • 编写 SqEL: #id; 参数 id 的值 #a0 #p0 #root.args[0] getEmp[2]
  • keyGenerator: key 的生成器:可以自己指定 key 的生成器的组件 id
    • key / keyGenerator: 二选一使用
  • cacheManager: 指定的缓存管理器: 或者 cacheResolver 指定获取解析器
  • condition: 指定符号条件的情况下才缓存:
    • eg: condition = “#a0 >1” : 第一个参数的值 > 1 的时候才会被缓存; 可以获取到结果进行判断
  • unless: 否定缓存: 当 unless 指定的条件为 true, 方法的返回值就不会被缓存;可以获取到结果进行判断
    • unless = “#result == null”
    • unless = “#a0 ==2”: 如果第一个参数的值为 2,结果不缓存;
  • sync: 是否使用异步模式

示例

代码语言:javascript
复制
@Cacheable(value = {"emp"},keyGenerator = "myKeyGenerator",condition = "#a0>1",unless = "#a0==2")
public Employee getEmp(Integer id){
    System.out.println("查询"+id+"号员工");
    Employee emp = employeeMapper.getEmpById(id);
    return emp;
}

注意使用keyGenerator需要自行配置(参考):

【keyGenerator】以 getEmp[2] 为例

代码语言:javascript
复制
@Configuration
public class MyCacheConfig {

    @Bean("myKeyGenerator")
    public KeyGenerator keyGenerator(){
        return new KeyGenerator(){
            @Override
            public Object generate(Object o, Method method, Object... objects) {
                // 拼接getEmp[2] 作为keyGenerator
                return method.getName() + "["+ Arrays.asList(objects).toString()+"]";
            }
        };
    }
}

④ @CachePut

即调用目标的方法,有更新缓存数据;同步更新缓存 修改了数据库的某个数据,同时更新缓存;

运行机制

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

测试步骤

  1. 查询 1 号员工: 查到的结果会放在缓存中:
  1. 更新 1 号员工:【lastName: AAA; gender:0】
  1. 在次查询 1 号员工(缓存没有更新)

解决方式

代码语言:javascript
复制
@CachePut(value = "emp", key = "#result.id")
public Employee updateEmp(Employee employee){
    System.out.println("updateEmp:"+ employee);
    employeeMapper.updateEmp(employee);
    return employee;
}

可设置参数约束条件:

  • key : 传入的 employee 对象 值: 返回的 employee 对象
    • key = “#employee.id”: 使用返回后的 id
    • key =”#result.id”: 使用返回后的 id

    ==注意:@Cacheable 的 key 是不能用#result==

⑤ @CacheEvict

缓存清除

key: 指定要清除的数据

beforeInvocaion = false: 缓存的清除是否在方法之前执行

​ 默认代表缓存清除的操作是在方法执行之后;如果出现异常缓存就不会清除

beforeInvocation = true: 代表清除缓存操作在方法运行之前执行,无论方法是否出现异常,缓存都要清除。

代码语言:javascript
复制
@CacheEvict(value = "emp", key = "#id")
public void deleteEmp(Integer id) {
    System.out.println("deleteEmp:" + id);
    //employeeMapper.deleteEmp(id);

    //模拟异常
    //int i = 10/0;
}

⑥ @Caching

定义复杂的缓存规则

代码语言:javascript
复制
@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){
    return employeeMapper.getEmpByLastName(lastName);
}

⑦ CacheConfig

抽取缓存的公共配置

代码语言:javascript
复制
@CacheConfig(cacheNames="emp"/*,cacheManager = "employeeCacheManager"*/)
@Service
public class EmployeeService {

四、整合 Redis 作为缓存

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

  1. 安装 redis: 使用 Docker
代码语言:javascript
复制
docker pull redis
  1. 引入 redis 的 starter
代码语言:javascript
复制
docker run -d -p 6379:6379 --name myredis redis
  1. 配置 redis (下载软件 RedisDesktopManager)

4、Redis 常见 的五大数据类型

  • String(字符串)、List(列表)、Set(集合)、Hash(散列)、Zset(有序集合)
  • stringRedisTemplate.opsForList()[List(列表)]
  • stringRedisTemplate.opsForSet()[Set(集合)]
  • stringRedisTemplate.opsForHash()[Hash(散列)]
  • stringRedisTemplate.opsForZSet()[ZSet(有序集合)]

5、测试缓存

原理:CacheManager === Cache 缓存组件来实际缓存中存取数据

① 引入 redis 的 starter,容器中保存的是 RedisCacheManager;

代码语言:javascript
复制
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

​ 在配置文件中配置

代码语言:javascript
复制
spring.redis.host=192.168.64.129

② RedisCacheManager 帮我们创建 RedisCache 来作为缓存组件;RedisCache 通过操作 redis 缓存数据的

代码语言:javascript
复制
@Autowired
StringRedisTemplate stringRedisTemplate; // 操作k-v都是字符串的

@Autowired
RedisTemplate redisTemplate;// k-v都是对象的
@Test
public void test1(){
    // 给redis中保存数据
    stringRedisTemplate.opsForValue().append("msg","hello");
    String msg = stringRedisTemplate.opsForValue().get("msg");
    System.out.println(msg);

    stringRedisTemplate.opsForList().leftPush("mylist","1");
    stringRedisTemplate.opsForList().leftPush("mylist","2");
}

③ 默认保存数据 k-v 都是 Object; 利用序列化保存;

​ 1)引入了 redis 的 starter, cacheManager 变为 RedisChacheManager;

代码语言:javascript
复制
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

​ 2) 默认创建的 RedisCacheManager 操作 redis 的时候使用的是 RedisTemplate<Object, Object>

代码语言:javascript
复制
@Autowired
EmployeeMapper employeeMapper;

@Autowired
StringRedisTemplate stringRedisTemplate; // 操作k-v都是字符串的

@Autowired
RedisTemplate redisTemplate;// k-v都是对象的

// 测试保存对象
@Test
public void test02(){
    Employee empById = employeeMapper.getEmpById(1);
    redisTemplate.opsForValue().set("emp-01",empById);
}

​ 3) RedisTemplate<Object, Object> 是默认使用 jdk 的序列化机制

【MyRedisTemplateConfig】

代码语言:javascript
复制
@Configuration
public class MyRedisTemplateConfig {

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

测试

代码语言:javascript
复制
@Resource
RedisTemplate<Object, Employee> employeeRedisTemplate;
@Test
public void test02(){
    Employee empById = employeeMapper.getEmpById(1);
    // 1.将数据以 JSON的方式保存
    // 2. redisTemplate默认的序列化规则,改变默认的序列化规则
    employeeRedisTemplate.opsForValue().set("emp-01",empById);
}

① 自定义 CacheManager

1、 SpringBoot 1.x 版本的 RedisCacheManager 配置
代码语言:javascript
复制
//自定义CacheManager

    @Bean
    public RedisCacheManager empCacheManager(RedisTemplate<Object, Employee> empRedisTemplate) {

        //将我们自定义的RedisTemplate作为参数,Spring会自动为我们注入

        RedisCacheManager cacheManager = new RedisCacheManager(empRedisTemplate);

        //使用前缀,默认会将CacheName作为key的前缀,最好设置为true,因为缓存可能有很多类
        cacheManager.setUsePrefix(true);
        return cacheManager;

    }

}

但是如果我们仅仅自定义这一个 CacheManager 则只能操作 Employee 这一种类型的数据,因为这个 CacheMananger 只实现了 Employee 的泛型,操作其他类型就会报错(可以正常缓存其他类型的数据,但是从缓存中查询出的数据在反序列化时会报错)。这时我们就需要自定义多个 CacheManager,比如增加一个可以缓存 Department 类型的 CacheMananger:

代码语言:javascript
复制
@Bean
   public RedisCacheManager deptCacheManager(RedisTemplate<Object, Department> deptRedisTemplate) {

       RedisCacheManager cacheManager = new RedisCacheManager(deptRedisTemplate);

       //使用前缀,默认会将CacheName作为key的前缀

       cacheManager.setUsePrefix(true);
       return cacheManager;
   }

当容器中有多个 RedisCacheManager 的时候,需要使用@Primary 指定一个默认的

2、SpringBoot 2.x 版本的 RedisCacheManager 配置
代码语言:javascript
复制
@Bean
public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
    RedisCacheConfiguration cacheConfiguration =
            RedisCacheConfiguration.defaultCacheConfig()
                    // 设置缓存过期时间为一天
                    .entryTtl(Duration.ofDays(1))
                    .disableCachingNullValues()     // 禁用缓存空值,不缓存null校验
                    .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new
                            GenericJackson2JsonRedisSerializer()));// 设置CacheManager的值序列化方式为json序列化,可加入@Class属性
    // 设置默认的cache组件
    return RedisCacheManager.builder(redisConnectionFactory).cacheDefaults(cacheConfiguration).build();
}
3、测试
代码语言:javascript
复制
@Cacheable(cacheNames = "dept",cacheManager ="cacheManager")
public Department getDeptById(Integer id) {
    System.out.println("查询部门:" + id);
    Department department = deptMapper.getDeptById(id);
    return department;
}
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2020-09-15,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Spring Boot 与缓存
    • 一、搭建基本环境
      • 二、快速体验缓存
        • ① 开启基于注解的缓存 @EnableCaching
        • ② 标注缓存注解即可
      • 三、缓存原理
        • ① 重要的概念&缓存注解
        • ② CacheManager
        • ③ @Cacheble
        • ④ @CachePut
        • ⑤ @CacheEvict
        • ⑥ @Caching
        • ⑦ CacheConfig
      • 四、整合 Redis 作为缓存
        • ① 自定义 CacheManager
    相关产品与服务
    云数据库 Redis
    腾讯云数据库 Redis(TencentDB for Redis)是腾讯云打造的兼容 Redis 协议的缓存和存储服务。丰富的数据结构能帮助您完成不同类型的业务场景开发。支持主从热备,提供自动容灾切换、数据备份、故障迁移、实例监控、在线扩容、数据回档等全套的数据库服务。
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档