SpringBoot缓存


点击这,效率更高!!!
源代码


ONE、搭建web环境

  • 使用springboot初始化器创建一个项目
    b5347e4fc283a7effa11ae1f57d6e6f4.png

  • 选择抽象的缓存模块
    cab0c56fcba00a16a7f77f412ff9d467.png

  • 所有需要选择的模块:

98a7d018149e51ac2e4dcfa929289b02.png

  • 创建需要的数据库
    03d94b1158905dbdb820332eaaaaf2e9.png

在数据库里创建一些表

  • department表
    a40708612755cf8514d56c355cd1edde.png

  • employee表
    96febadafe58de643199808b3fee4c9f.png

一、创建javabean封装数据

  • 创建一个bean包,创建javabean封装数据库里的数据

里面包括有参构造器、无参构造器、toString方法和getter和setter

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

    @Override
    public String toString() {
        return "Employee{" +
                "id=" + id +
                ", lastName='" + lastName + '\'' +
                ", email='" + email + '\'' +
                ", gender=" + gender +
                ", dId=" + dId +
                '}';
    }

    public Employee(Integer id, String lastName, String email, Integer gender, Integer dId) {
        this.id = id;
        this.lastName = lastName;
        this.email = email;
        this.gender = gender;
        this.dId = dId;
    }

    public Employee() {
    }

    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;
    }
}

public class Department {
    private Integer id;
    private String 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;
    }

    public Department(Integer id, String departmentName) {
        this.id = id;
        this.departmentName = departmentName;
    }

    public Department() {
    }

    @Override
    public String toString() {
        return "Department{" +
                "id=" + id +
                ", departmentName='" + departmentName + '\'' +
                '}';
    }
}

二、整合Mybatis操作数据库

1.配置数据源

  • 在application.properties中进行配置
spring.datasource.url=jdbc:mysql://47.94.229.156:3306/spring_cache
spring.datasource.username=root
spring.datasource.password=123456
# spring.datasource.driver-class-name=com.mysql.jdbc.Driver(写不写都可以,会根据url判断)

2.使用注解版的Mybatis;

  • 在主程序上用@MapperScan指定需要扫描的mapper接口所在的包
@MapperScan("com.example.demo.mapper")
@SpringBootApplication
public class DemoApplication {

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

}
  • 与主程序同目录下创建mapper包

  • 在mapper包下,建立相应的mapper(接口)
    7e45b0e985912a04db0cb6a7a54e163e.png

首先对employee表操作

@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 deleteEmp(Integer id);
    
    @Insert("INSERT INTO employee(lastName,email,gender,d_id VALUES (#{lastName},#{email},#{gender},#{dId}))")
    public void insertEmployee(Employee employee);
}

3.测试

  • 在测试类中完成测试
@SpringBootTest
class DemoApplicationTests {
    @Autowired
    EmployeeMapper employeeMapper;
    @Test
    void contextLoads() {
        Employee empById = employeeMapper.getEmpById(1);
        System.out.println(empById);
    }

}

在控制台能看到如下:
ecce03b1b2ba8ab49015f9e14bcee36b.png
这就说明数据库连接成功了
由于数据库里的字段是d_id,javaBean里的是dId,没有指定使用驼峰命名规则,所以是查不到数据的。

4.业务层

  • 在主程序相同的目录下建立一个service包
  • 建立EmployeeService类

a3861a346b131fda49ae953344e13112.png

@Service
public class EmployeeService {

    @Autowired
    EmployeeMapper employeeMapper;

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

5.表现层

  • 在主程序的同级目录下创建包controller
  • 在controller包下创建EmployeeController类
@RestController
public class EmployeeController {

    @Autowired
    EmployeeService employeeService;

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

6.测试

  • 运行主程序
  • 在浏览器输入
    http://localhost:8080/emp/1

f31a489f6651a8c504e0b74d149c9d27.png

dId没有值的原因是没有开启驼峰命名规则

(1)开启驼峰命名规则
  • 在application.properties里加入
mybatis.configuration.map-underscore-to-camel-case=true

9e5a1ee9edb166c869e76807ee6e28eb.png
这样就可以显示了

TWO、快速体验缓存

一、步骤

1.开启基于注解的缓存

  • 在主程序上加上基于注解的缓存@EnableCaching

8df2eb8b519f6a20d7c4be8518110960.png

2.标注缓存注解即可

  • 在需要缓存的方法上加上下列相应的注解

be97cad8afff1850ebe0710b1a825826.png

  • 在EmployeeService的方法上加上需要的注解
    b0b751e8469c4741c298d12c397117db.png
    将方法的运行结果进行缓存;以后要是相同的数据,直接从缓存中获取,不用调用方法。

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

  • Cacheable的几个属性:

    • cacheNames/value: 指定缓存组件的名字
    • key:缓存数据使用的key;可以用它来指定。默认是使用方法参数的值
    •  编写可以适应SpEl表达式: #id;参数id的值
      
    • keyGenerator: key的生成器;可以自己指定key的生成器的组件id
    • key/keyGenerator 二选一使用
    • cacheManager:指定缓存管理器
    • condition:指定符合条件的情况下才缓存eg:condition = "#id>0"
    • unless:否定缓存:当unless指定的条件为true,方法的返回值就不会被缓存;(可以获取到结果进行判断)
      eg:unless = "#result==null"
    • sync:是否使用异步模式

3.验证缓存生效

  • 为了方便显示效果,可以打印日志

  • 在application.properties里添加相应的代码logging.level.com.example.demo.mapper=debug

  • 重启项目,清空控制器的打印,在浏览器输入
    http://localhost:8080/emp/1
    控制台如下
    942c9d35365fb08037cdfd7d44fb60de.png
    清空控制台,在浏览器刷新请求
    942c9d35365fb08037cdfd7d44fb60de.png

此时可以看到控制台没有任何的输出,但是浏览器得到了数据,说明数据来源于缓存

二、缓存原理

  • 以@Cacheable为例:

    • Cacheable的执行流程:
  •  1.方法执行前,先去查询Cache(缓存组件),按照cacheNames指定的名字获取;
    
  •      (CacheManager先获取相应的缓存),第一次获取缓存,如果没有Cache组件就会自动创建
    
  •  2.去Cache中查找缓存的内容,使用一个key,默认就是方法的参数;
    
  •      key是按照某种策略生成的;默认是使用keyGenerator生成的,默认使用SimpleKeyGenerator生成key
    
  •          SimpleKeyGenerator生成key的默认策略:
    
    •             如果没有参数:key = new SimpleKey();
      
    •             如果有一个参数:key = 参数值
      
    •             如果有多个参数:key = newSimplekey(params);
      
  •  3.没有查到缓存就调用目标方法
    
  •  4.将目标方法返回的结果,放进缓存
    
  •  总结:@Cacheable标注的方法执行之前先来检查缓存中有没有这个数据,默认按照参数的值作为
    
  •  key去查询缓存,如果没有就运行方法,并将结果放进缓存
    
  •  核心:
    
    •  1)、使用CacheManager【ConcurrentMapCacheManager】按照名字得到Cache【ConcurrentMapCache】组件
      
    •  2)、key使用keyGenerator生成的,默认是SimpleKeyGenerator
      

THREE、缓存注解

b24fc7165e849e41425758a92234a56c.png

一、@Cacheable

常用作查询

1.cacheNames/value

指定缓存组件的名字;指定将方法的返回结果放在哪个缓存中,是数组的方式,可以指定多个缓存
a8cba4418bd87f1bba4051219001f7f2.png

2.key:缓存数据使用的key

可以用它来指定。默认是使用方法参数的值

  • 编写可以使用SpEl表达式: #id;参数id的值;
    #id相同的表示还有:#a0; #p0; #root.args[0]
    例子如下:
    f5737d57b82ac91feaec47c2a392cc77.png

    key/keyGenerator 二选一使用

3.keyGenerator

key的生成器;可以自己指定key的生成器的组件id

  • (自定义key的生成策略) 在与主程序相同的目录下创建一个config包,包下建立一个MyCacheConfig类,用于配置缓存
    8ea1faeedba43878de998fcd7edb2bb8.png
@Configuration
public class MyCacheConfig {

    @Bean("myKeyGenerator")
    public KeyGenerator keyGenerator(){
        return new KeyGenerator(){

            @Override
            public Object generate(Object o, Method method, Object... objects) {
                return method.getName()+"["+ Arrays.asList(objects).toString()+"]";
            }
        };
    }
}
  • 在缓存注解中如下操作
    2fe37782b76bec83bc72031d6c2c991c.png

4. cacheManager

指定使用哪个缓存管理器

5.condition

指定符合条件的情况下才缓存eg:condition = "#id>0"
974ff7359f30a954093b12af0847717b.png

6.unless

否定缓存:当unless指定的条件为true,方法的返回值就不会被缓存;(可以获取到结果进行判断)
6f486763e3eabf75cd23612053ed24cf.png

eg:unless = "#result==null"

7.sync

是否使用异步模式
sync="true"
注意:异步模式下unless不支持

二、@CachePut

既调用方法,又更新缓存数据
修改了数据库的某个数据,同时更新缓存
执行逻辑:

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

1.在需要的方法上添加注解

在EmployeeService类中添加如下代码

 @CachePut("emp")
    public Employee UpdateEmp(Employee employee){
        System.out.println("updateEmp:"+employee);
        employeeMapper.updateEmp(employee);
        return employee;
    }

2.测试

修改查询方法,让它按照默认

83cd5c6aeeff7cd9cb22f1aa9138d0c4.png

(1)查询1号员工,并把查询的结果放到缓存中
  • 启动项目,在浏览器中输入http://localhost:8080/emp/1
    浏览器中返回的数据如下:
{"id":1,"lastName":"1","email":"1","gender":1,"dId":1}

控制台打印查询语句

645d678d8a8550f3e5f11b82f386c4d9.png

(2)再次查询1号员工
  • 清除控制台,再次在浏览器中输入http://localhost:8080/emp/1
    浏览器中依然返回相同的数据:
{"id":1,"lastName":"1","email":"1","gender":1,"dId":1}

控制台没有打印任何信息,说明数据来源于缓存

(3)更新1号员工
  • 在浏览器中输入http://localhost:8080/emp?id=1&lastName=zhangsan&gender=0
  • 控制台打印信息如下

bf64aa4e2b48991f3762e6bb255b875d.png

说明【lastName=zhangsan,gender=0】

(4)第三次查询1号员工
  • 清除控制台,在浏览器输入http://localhost:8080/emp/1
  • 查询出来的数据如下:
{"id":1,"lastName":"1","email":"1","gender":1,"dId":1}
  • 控制台没有打印任何信息

查出来的数据是更新前的数据,更新后的数据没有查到,为什么没有查到更新后的数据呢???

3.为什么没有查到更新后的数据???

  • 查询1号员工时,往缓存放的数据是按照key-value放的,@Cacheable默认使用方法参数的值为key,这里的就是id,也就是传入的1,key:1 ; value:[id":1,“lastName”:“1”,“email”:“1”,“gender”:1,“dId”:1]
  • 更新了一个员工后,调用了@CachePut,更新了员工,更新后的员工信息放到了缓存中,为什么没有取到?因为默认指定的key是不相同的,那样就没法在查询的时候查到了。它用的key为传入的employee对象,value为返回的employee对象,即:
    key:传入的employee对象 value:返回的employee对象

可见要想查出更新后的员工,更新的key和查询的key应该一致

4.解决方法

(1)更新的key和查询的key应该一致
  • 因为查询的时候用的是参数id,更新的时候也可以用id作为key

  • 只需要给@CachePut指定key=“#employee.id”
    697961dc51c15c2a38a615e7610cd162.png

(2)也可以用返回值
  • @CachePut是方法执行完之后,在往缓存放东西,可以从返回值中获取相应的值作为key,key=#result.id

8d9be096f68d0e4043ed37f0d9fef11b.png

5.再次测试

(1)查询1号员工
  • 重新启动项目,在浏览器中输入http://localhost:8080/emp/1
    得到结果如下:
{"id":1,"lastName":"zhangsan","email":null,"gender":0,"dId":null}

727fb67e59e9ed4f4c51995d66aa13e8.png

(2)再次查询1号员工
  • 在浏览器中输入http://localhost:8080/emp/1
    得到的结果不变,控制台没有再次输出,可以知道是从缓存中获取的数据
(3)更新1号员工
  • 在浏览器中输入http://localhost:8080/emp?id=1&lastName=张三&gender=1&email=123456@qq.com
  • 浏览器中的返回结果如下:
{"id":1,"lastName":"张三","email":"123456@qq.com","gender":1,"dId":null}
  • 控制台的结果如下:

38a221720f1de7b4fc1902a2fefe38f2.png

(4)第三次查询1号员工
  • 在浏览器中输入http://localhost:8080/emp/1
  • 浏览器中返回值如下:
{"id":1,"lastName":"张三","email":"123456@qq.com","gender":1,"dId":null}

控制台中没有任何打印,说明是从缓存中查询的数据

三、@CacheEvict

常用于缓存清除

1.在 EmployeeService里添加相应的方法

  @CacheEvict(value = "emp",key = "#id")
    public void deleteEmp(Integer id){
        System.out.println("deleteEmp"+id);
        //employeeMapper.deleteEmp(id);//先不执行删除方法
    }

2.在 EmployeeController里添加相应的方法

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

3.测试

  • 重新启动项目,在浏览器中输入http://localhost:8080/emp/1,可以查询到相应的信息,控制台打印查询语句。
  • 再次在浏览器中输入http://localhost:8080/emp/1,可以看到浏览器中查到信息,但是控制台没有打印,说明信息来自缓存
  • 在浏览器中输入http://localhost:8080/delemp?id=1
    浏览器返回success
    控制台返回如下:

1d81b28f24f74bdb73ca318a94794685.png

  • 再次在浏览器输入http://localhost:8080/emp/1,浏览器中返回为空,控制台打印如下:

4e76fd301ce7eed71c36b7b0d99d5734.png

说明删除了数据库里的同时删除了缓存里的数据

4.属性

(1)key
  • 指定清除的数据(默认为参数的值)
(2)allEntries
  • 是不是删除value值下的所有缓存数据
  • 既然全都删除,那就不需要指定key

573c77f39fa21d68a56478601925922d.png

如图,就是删除emp的所有缓存

(3)beforeInvocation
  • 缓存的清除是否在执行方法之前执行(默认是在方法执行之后执行)

如果在方法执行之前清空缓存,那么在方法出错的时候,方法没有执行,但是缓存会清空

四、@Caching

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Caching {

	Cacheable[] cacheable() default {};

	CachePut[] put() default {};

	CacheEvict[] evict() default {};

}

可以看到这是一个组合注解,可以指定多个缓存规则,用于复杂情况的缓存操作

1.在EmployeeService中写如下方法

@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);
    }
  • 可以看到用了这个组合注解,可以一次查询把数据放到很多key里

2.在EmployeeController中添加如下方法

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

3.在EmployeeMapper中写如下方法

@Select("SELECT * FROM employee WHERE lastName = #{lastName}")
    public Employee getEmpByLastName(String lastName);

4.测试

  • 先在数据库添加一些数据用于测试

3ae30c5b9ebf6dfd6d8995de8674c975.png

  • 运行项目,在浏览器中输入http://localhost:8080/emp/lastname/张三
    浏览器中返回数据:
{"id":1,"lastName":"张三","email":"111","gender":1,"dId":1}

控制台打印查询日志

d4be5768d674d64d0f5229c036dd9afa.png

  • 在浏览器中输入http://localhost:8080/emp/1,可以看到浏览器中返回数据相同,但是控制台并没有任何打印,所以数据来源于缓存,这样就实现了查询一次数据库,把数据放到多个key的目的

  • 同样的,如果我们再写一个根据email查询的方法,执行完上面的流程,也是可以在缓存中拿到数据的(这里没写根据email查询的,就不再测试了)

注意:虽然已经根据lastName查询出来数据了,这个时候如果我再次根据lastName查询,还是会查询数据库的,因为有@CachePut,后面的方法一定会执行的,执行方法,就会查询数据库

c291d9de66bc795b5e63cf5f325f849d.png

五、@CacheConfig

1.cacheName

根据上面的学习我们可以发现,每一个注解里面都要指定value=“emp”这样就会增加工作量,我们可以在类上加上@CacheConfig,用属性cacheName指定,格式如下:

@Service
@CacheConfig(cacheNames = "emp")
public class EmployeeService {
  • 这里指定了cacheName,下面的所有缓存注解都不需要指定cacheName或者value了
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值