SpringBoot框架学习(七)——缓存

十一、SpringBoot与缓存

1.概念

(1).JSR107

Java Caching定义了五个核心接口,分别是CachingProvider,CacheManager,Cache,Entry和Expiry
CachingProvider:
“缓存提供者”定义管理控制多个CacheManager,一个应用可以在运行期间访问多个CachingProvider
CacheManager:
“缓存管理器”定义控制管理多个Cache。一个CachingProvider对应一个CacheManager
Cache
将Entry的key-value对临时保存起来。一个CacheManager对应一个Cache
Entry
存储在Cache的key-value对
Expiry
“有效期”,对Cache保存时间的有效期可以在ExpiryPolicy中设置,超时就自动删除
在这里插入图片描述

(2).SpringBoot缓存抽象

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

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

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

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

在这里插入图片描述

2.Cache案例

(1)新建工程

在这里插入图片描述

(2)在数据库建表

DROP TABLE IF EXISTS department;
CREATE TABLE department (
  id NUMBER(11),
  departmentName varchar(255) DEFAULT NULL,
  PRIMARY KEY (id)
);

DROP TABLE IF EXISTS employee;
CREATE TABLE employee (
  id NUMBER(11) ,
  lastName varchar(255) DEFAULT NULL,
  email varchar(255) DEFAULT NULL,
  gender NUMBER(2) DEFAULT NULL,
  d_id NUMBER(11) DEFAULT NULL,
  PRIMARY KEY (id)
);

(3)创建一个package,然后创建属性对应的get set方法,进行数据封装

Department

package springboot.cache.bean;

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

package springboot.cache.bean;

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 + "]";
	}
}

(4)整合Mybatis数据库

<1>写配置信息
spring:
  datasource:
    username: SYSTEM
    password: 1
    url: jdbc:oracle:thin:@127.0.0.1:1521:xe
    driver-class-name: oracle.jdbc.OracleDriver
<2>使用注解版的Mybatis

@MapperScan自动进行扫描mapper接口所在的包(标记在运行类上)

@MapperScan("springboot.cache.mapper")
<3>编写接口,写出方法对应的SQL
@Mapper
public interface EmployeeMapper {

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

    @Insert("insert table employee (lastName,email,gender,d_Id) values(#{lastName},#{email},#{gender},#{dId})")
    public void insertEmp(Employee employee);

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

    @Delete("Delete from Employee where id=#{id}")
    public void deleteEmp(Integer id);
}
<4>编写测试类
@RunWith(SpringRunner.class)
@SpringBootTest
public class CacheApplicationTests {

    @Autowired
    EmployeeMapper employeeMapper;

    @Test
    public void contextLoads() {
        Employee employee = employeeMapper.getEmpById(1);
        System.out.println(employee);

    }

}

测试成功,接着写Service层和Controller层
Service

package springboot.cache.service;


import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.GetMapping;
import springboot.cache.bean.Employee;
import springboot.cache.mapper.EmployeeMapper;

@Service
public class EmployeeService {
    @Autowired
    EmployeeMapper employeeMapper;


    public Employee getEmp(Integer id) {
        System.out.println("Number:"+id+"is selecting");
        Employee employee = employeeMapper.getEmpById(id);
        return employee;
    }
}

Controller

package springboot.cache.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import springboot.cache.bean.Employee;
import springboot.cache.service.EmployeeService;

@RestController
public class EmployeeController {

    @Autowired
    EmployeeService employeeService;

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

    }
}

然后测试,来到localhost:/8083/emp/id端口
在这里插入图片描述
测试成功。

(5)缓存体验

<1>开启基于注解的缓存

首先在主类上开启缓存功能在这里插入图片描述

<2>标注缓存注解(Cacheable)

@Cacheable:根据方法的请求参数进行保存
@CacheEvict:清空缓存
@CachePut:调用+缓存方法

首先,在application.properties开启debug的时候会打印日志的功能

logging.level.springboot.cache.mapper=debug 
#将mapper包下的debug行为进行log打印在控制台上

然后在需要缓存的地方标记上@Cacheable,这样就可以将方法的运行结果进行缓存;以后再要相同的数据,直接从缓存中获取,不用调用方法

@Cacheable
    public Employee getEmp(Integer id) {
        System.out.println("Number:"+id+"is selecting");
        Employee employee = employeeMapper.getEmpById(id);
        return employee;
    }

附上一张Cacheable内部构造,通常以Map形式进行存储
在这里插入图片描述在EmployeeService上整理了一下笔记,然后进行了一下运行,检验一下我们的@Cacheable标签带来的作用。
在这里插入图片描述
这是通过数据库进行查询的结果
然后删除控制台日志,重新F5网页
在这里插入图片描述
发现没有打印新的日志,所以也就是说他内部也就没有再继续去查询数据库了,说明我们的缓存标签生效了。

<3>总结

Service层

    /**
     *  将方法的运行结果存储到缓存,以后再要相同的数据,就可以直接从缓存中提取,不用调用方法了
     *
     *  CacheManager管理多个Cache组件,对缓存的真正的Crud操作在Cache组件中,每一个缓存组件都有一个自己的名字
     *
     *  属性解释:
     *  cacheName/value : 指定缓存组件的名字;讲方法的返回结果放在哪个缓存中,是数组的方式,可以指定多个缓存
     *
     *  key:缓存数据使用的key,可以用它来指定,默认是使用方法参数的值   比如我们传入一个Integer id,那么这个id就是返回值
     *  将id通过KV表达式保存起来
     *  #id表示参数的值,等同于#a0 #p0 #root.args[0]
     *
     *  以此为例@Cacheable(value="emp",key="#id")意思就是缓存组件名字是emp,缓存数据的key是id的值,#代表取出值
     *  编写spel表达式的时候要知道 #id等同于#a0 #p0 #root.args[0]
     *  keyGenerator:key的生成器,自己制定key的生成id
     *  key/keyGenerator二选一
     *  【此功能以cache.MycacheConfig为例】
     *  编写自定义的keyGenerator,让Cacheable()内的keyGenerator可以取代key(二选一)
     *
     *
     *  cacheManager:指定缓存管理器或者使用cacheResolver指定获取解析器
     *  condition:指定符合条件的情况下才进行缓存
     *  condition = "#{id>0}"
     *  unless:否定缓存,当unless指定的条件为true的时候,方法的返回值就不会被缓存,可以获取到结果进行判断
     *  unless="#result==null"
     *  sync:是否使用异步模式
     *
     *
     *  原理:
     *  1.自动配置类CacheAutoConfiguration
     *  2.配置缓存类
     *
     *
     *  运行流程
     *  @Cacheable*  1.方法运行之前先去检查Cache(缓存组件),按照cacheNames指定的名字获取;
     *  (CacheManager先获取对应的缓存),第一次获取缓存如果没有Cache组件会自动创建
     *  2.Cache中查找缓存的内容,使用一个Key,默认就是方法的参数;
     *  key是按照某种策略生成的,默认就是使用KeyGenerator生成的
     *      SimpleGenerator生成key的策略:
     *          (1)如果没有参数:key=new Simple()
     *          (2)如果有一个参数:key=参数的值
     *          (3)如果有一个参数:key=new SimpleKey(params);
     *  3.没有查到缓存就调用目标方法
     *  4.将目标的方法返回的结果,放进缓存中
     *
     * @Cacheable标注的方法执行之前先检查缓存中是否存在,默认按照参数的值生成key去查询缓存,如果没有就将结果放入缓存。
     *
     *
     * 核心:
     * 1.使用CacheManagerConcurrentMapCacheManager】按照名字得到CacheConcurrentMapCurrent】组件
     * 2.key使用keyGenerator生成的,默认是SimpleKeyGenerator
     * @param id
     * @return
     */

Service层

@Cacheable(cacheNames = {"emp"},keyGenerator = "myKeyGeneratorConfig")
    public Employee getEmp(Integer id) {
        System.out.println("Number:"+id+"is selecting");
        Employee employee = employeeMapper.getEmpById(id);
        return employee;
    }

Controller层

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

    }
<4>CachePut

同步更新缓存
Service层

/**
     * @CachePut:调用方法+更新缓存数据 修改数据库某个数据并且更新缓存
     *
     * 调用时机
     * 1.先调用目标方法
     * 2.将目标方法的结果缓存起来
     *
     * 测试步骤
     * 1.查询1001号员工,结果会放到缓存中
     * @存储的方法类似于KV存储方法,但是key是内部自然生成且自增的
     * 2.以后查询还是之前的结果
     * 3.即使缓存保留后,最数据库进行操作,也不影响缓存,缓存会自动进行更新操作
     * @更新之后传入的实际上是一个employee的对象,value是更新的内容,而不是直接对应着,之前自动生成的key去更新
     * @所以我们就要自动指定出来key,去做到真正的自动更新
     *
     * 例:key = "#employee.id":使用传入的参数的id
     *     key = "#result.id":使用返回的参数id
     *     但是@cacheable的key不能用result,因为@cacheable是在查找前搜索id,而@CachePut可以用,因为是缓存完了更新完了去搜索id
     */

    @CachePut(value = "emp",key = "#result.id")
    public Employee updateEmp(Employee employee) {
        employeeMapper.updateEmp(employee);
        return employee;
    }

Controller层

@GetMapping("/emp")
    public Employee updateEmp(Employee employee) {
        return employeeService.updateEmp(employee);
    }
<5>CacheEvict

Service层

/**
     * @CacheEvict : 缓存清除
     * key:指定要清除的数据
     * allEntries :选择是否删除所有缓存  true就是删除所有的了
     * beforeInvocation = false : 缓存的清除是否在方法之前执行
     * 默认代表是在方法执行之后执行,但是如果是true就是在执行方法之前清除缓存,不论方法是否有异常
     */
    @CacheEvict(value = "emp")
    public void deleteEmp(Integer id){
        employeeMapper.deleteEmp(id);
    }

Controller层

@GetMapping("/delemp")
    public String deleteEmp(Integer id) {
        employeeService.deleteEmp(id);
        return "success";
    }

成功删除缓存
在这里插入图片描述

<6>Caching

在这里我们以select 语句为例子,Caching更像是一个集合标签,可以在里面写上上面提到的三种方法

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

Controller

@GetMapping("/emp/lastName/{lastName}")
    public Employee getEmpByLastName(@PathVariable("lastName") String lastName) {
        return employeeService.getEmpBylastName(lastName);
    }

Service

@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);
    }
<7>总结

我们发现上面所有都是从emp的缓存中提取数据,也就是value="emp"这样很麻烦,所以有一个其他的办法,让所有的标签固定的从一个地方来获取到同一张表的缓存,直接在类上加上@CacheConfig,进行集中配置,抽取缓存的公共属性

@CacheConfig(cacheNames = "emp")//抽取缓存的公共属性
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值