目录
  1. 1. ONE、搭建web环境
    1. 1.1. 一、创建javabean封装数据
    2. 1.2. 二、整合Mybatis操作数据库
      1. 1.2.1. 1.配置数据源
      2. 1.2.2. 2.使用注解版的Mybatis;
      3. 1.2.3. 3.测试
      4. 1.2.4. 4.业务层
      5. 1.2.5. 5.表现层
      6. 1.2.6. 6.测试
        1. 1.2.6.1. (1)开启驼峰命名规则
  2. 2. TWO、快速体验缓存
    1. 2.1. 一、步骤
      1. 2.1.1. 1.开启基于注解的缓存
      2. 2.1.2. 2.标注缓存注解即可
      3. 2.1.3. 3.验证缓存生效
    2. 2.2. 二、缓存原理
  3. 3. THREE、缓存注解
    1. 3.1. 一、@Cacheable
      1. 3.1.1. 1.cacheNames/value
      2. 3.1.2. 2.key:缓存数据使用的key
      3. 3.1.3. 3.keyGenerator
      4. 3.1.4. 4. cacheManager
      5. 3.1.5. 5.condition
      6. 3.1.6. 6.unless
      7. 3.1.7. 7.sync
    2. 3.2. 二、@CachePut
      1. 3.2.1. 1.在需要的方法上添加注解
      2. 3.2.2. 2.测试
        1. 3.2.2.1. (1)查询1号员工,并把查询的结果放到缓存中
        2. 3.2.2.2. (2)再次查询1号员工
        3. 3.2.2.3. (3)更新1号员工
        4. 3.2.2.4. (4)第三次查询1号员工
      3. 3.2.3. 3.为什么没有查到更新后的数据???
      4. 3.2.4. 4.解决方法
        1. 3.2.4.1. (1)更新的key和查询的key应该一致
        2. 3.2.4.2. (2)也可以用返回值
      5. 3.2.5. 5.再次测试
        1. 3.2.5.1. (1)查询1号员工
        2. 3.2.5.2. (2)再次查询1号员工
        3. 3.2.5.3. (3)更新1号员工
        4. 3.2.5.4. (4)第三次查询1号员工
    3. 3.3. 三、@CacheEvict
      1. 3.3.1. 1.在 EmployeeService里添加相应的方法
      2. 3.3.2. 2.在 EmployeeController里添加相应的方法
      3. 3.3.3. 3.测试
      4. 3.3.4. 4.属性
        1. 3.3.4.1. (1)key
        2. 3.3.4.2. (2)allEntries
        3. 3.3.4.3. (3)beforeInvocation
    4. 3.4. 四、@Caching
      1. 3.4.1. 1.在EmployeeService中写如下方法
      2. 3.4.2. 2.在EmployeeController中添加如下方法
      3. 3.4.3. 3.在EmployeeMapper中写如下方法
      4. 3.4.4. 4.测试
    5. 3.5. 五、@CacheConfig
      1. 3.5.1. 1.cacheName
SpringBoot缓存

ONE、搭建web环境

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

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

  • 所有需要选择的模块:

98a7d018149e51ac2e4dcfa929289b02.png

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

在数据库里创建一些表

  • department表
    a40708612755cf8514d56c355cd1edde.png

  • employee表
    96febadafe58de643199808b3fee4c9f.png

一、创建javabean封装数据

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
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;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
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中进行配置
1
2
3
4
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接口所在的包
1
2
3
4
5
6
7
8
9
@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表操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@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.测试

  • 在测试类中完成测试
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    @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

1
2
3
4
5
6
7
8
9
10
11
12
@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类
1
2
3
4
5
6
7
8
9
10
11
12
@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里加入
1
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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    @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类中添加如下代码

1
2
3
4
5
6
@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
    浏览器中返回的数据如下:
1
{"id":1,"lastName":"1","email":"1","gender":1,"dId":1}

控制台打印查询语句

645d678d8a8550f3e5f11b82f386c4d9.png

(2)再次查询1号员工

  • 清除控制台,再次在浏览器中输入http://localhost:8080/emp/1
    浏览器中依然返回相同的数据:

    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
  • 查询出来的数据如下:
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
    得到结果如下:
    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

  • 浏览器中的返回结果如下:

    1
    {"id":1,"lastName":"张三","email":"123456@qq.com","gender":1,"dId":null}
  • 控制台的结果如下:

38a221720f1de7b4fc1902a2fefe38f2.png

(4)第三次查询1号员工

  • 在浏览器中输入http://localhost:8080/emp/1
  • 浏览器中返回值如下:
    1
    {"id":1,"lastName":"张三","email":"123456@qq.com","gender":1,"dId":null}
  • 控制台中没有任何打印,说明是从缓存中查询的数据*

三、@CacheEvict

常用于缓存清除

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

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

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

1
2
3
4
5
@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

1
2
3
4
5
6
7
8
9
10
11
12
13
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Caching {

Cacheable[] cacheable() default {};

CachePut[] put() default {};

CacheEvict[] evict() default {};

}

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

1.在EmployeeService中写如下方法

1
2
3
4
5
6
7
8
9
10
11
12
@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中添加如下方法

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

3.在EmployeeMapper中写如下方法

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

4.测试

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

3ae30c5b9ebf6dfd6d8995de8674c975.png

  • 运行项目,在浏览器中输入http://localhost:8080/emp/lastname/张三
    浏览器中返回数据:
    1
    {"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指定,格式如下:

1
2
3
@Service
@CacheConfig(cacheNames = "emp")
public class EmployeeService {
  • 这里指定了cacheName,下面的所有缓存注解都不需要指定cacheName或者value了
文章作者: Danqing
文章链接: http://yoursite.com/2020/04/13/SpringBoot%E7%BC%93%E5%AD%98/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 DanqingBlog
打赏
  • 微信
  • 支付宝

评论