ONE、搭建web环境
使用springboot初始化器创建一个项目
选择抽象的缓存模块
所有需要选择的模块:
- 创建需要的数据库
在数据库里创建一些表
department表
employee表
一、创建javabean封装数据
- 创建一个bean包,创建javabean封装数据库里的数据
里面包括有参构造器、无参构造器、toString方法和getter和setter
1 | public class Employee { |
1 | public class Department { |
二、整合Mybatis操作数据库
1.配置数据源
- 在application.properties中进行配置
1 | jdbc:mysql://47.94.229.156:3306/spring_cache = |
2.使用注解版的Mybatis;
- 在主程序上用@MapperScan指定需要扫描的mapper接口所在的包
1 | "com.example.demo.mapper") ( |
与主程序同目录下创建mapper包
在mapper包下,建立相应的mapper(接口)
首先对employee表操作
1 |
|
3.测试
- 在测试类中完成测试在控制台能看到如下:
1
2
3
4
5
6
7
8
9
10
11
class DemoApplicationTests {
EmployeeMapper employeeMapper;
void contextLoads() {
Employee empById = employeeMapper.getEmpById(1);
System.out.println(empById);
}
} - 这就说明数据库连接成功了*
由于数据库里的字段是d_id,javaBean里的是dId,没有指定使用驼峰命名规则,所以是查不到数据的。
4.业务层
- 在主程序相同的目录下建立一个service包
- 建立EmployeeService类
1 |
|
5.表现层
- 在主程序的同级目录下创建包controller
- 在controller包下创建EmployeeController类
1 |
|
6.测试
- 运行
主程序
- 在浏览器输入
http://localhost:8080/emp/1
dId没有值的原因是没有开启驼峰命名规则
(1)开启驼峰命名规则
- 在application.properties里加入
1 | true = |
这样就可以显示了
TWO、快速体验缓存
一、步骤
1.开启基于注解的缓存
- 在主程序上加上基于注解的缓存
@EnableCaching
2.标注缓存注解即可
- 在需要缓存的方法上加上下列相应的注解
在EmployeeService的方法上加上需要的注解
将方法的运行结果进行缓存;以后要是相同的数据,直接从缓存中获取,不用调用方法。*
- 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
控制台如下
清空控制台,在浏览器刷新请求
此时可以看到控制台没有任何的输出,但是浏览器得到了数据,说明数据来源于缓存
二、缓存原理
- 以@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、缓存注解
一、@Cacheable
常用作查询
1.cacheNames/value
指定缓存组件的名字;指定将方法的返回结果放在哪个缓存中,是数组的方式,可以指定多个缓存
2.key:缓存数据使用的key
可以用它来指定。默认是使用方法参数的值
编写可以使用SpEl表达式: #id;参数id的值;
#id相同的表示还有:#a0; #p0; #root.args[0]
例子如下:key/keyGenerator 二选一使用
3.keyGenerator
key的生成器;可以自己指定key的生成器的组件id
(自定义key的生成策略) 在与主程序相同的目录下创建一个config包,包下建立一个MyCacheConfig类,用于配置缓存
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class MyCacheConfig {
"myKeyGenerator") (
public KeyGenerator keyGenerator(){
return new KeyGenerator(){
public Object generate(Object o, Method method, Object... objects) {
return method.getName()+"["+ Arrays.asList(objects).toString()+"]";
}
};
}
}在缓存注解中如下操作
4. cacheManager
指定使用哪个缓存管理器
5.condition
指定符合条件的情况下才缓存*
eg:condition = "#id>0"
6.unless
否定缓存:当unless指定的条件为true,方法的返回值就不会被缓存;(可以获取到结果进行判断)*
eg:unless = "#result==null"
7.sync
是否使用异步模式*
sync="true"
注意:异步模式下unless不支持*
二、@CachePut
既调用方法,又更新缓存数据
修改了数据库的某个数据,同时更新缓存
执行逻辑:
在EmployeeService类中添加如下代码
1 | "emp") ( |
2.测试
修改查询方法,让它按照默认
(1)查询1号员工,并把查询的结果放到缓存中
- 启动项目,在浏览器中输入
http://localhost:8080/emp/1
浏览器中返回的数据如下:
1 | {"id":1,"lastName":"1","email":"1","gender":1,"dId":1} |
控制台打印查询语句
(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
控制台打印信息如下
说明【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”
(2)也可以用返回值
- @CachePut是方法执行完之后,在往缓存放东西,可以从返回值中获取相应的值作为key,key=
#result.id
5.再次测试
(1)查询1号员工
- 重新启动项目,在浏览器中输入
http://localhost:8080/emp/1
得到结果如下:1
{"id":1,"lastName":"zhangsan","email":null,"gender":0,"dId":null}
(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}
控制台的结果如下:
(4)第三次查询1号员工
- 在浏览器中输入
http://localhost:8080/emp/1
- 浏览器中返回值如下:
1
{"id":1,"lastName":"张三","email":"123456@qq.com","gender":1,"dId":null}
- 控制台中没有任何打印,说明是从缓存中查询的数据*
三、@CacheEvict
常用于缓存清除
1.在 EmployeeService里添加相应的方法
1 | "emp",key = "#id") (value = |
2.在 EmployeeController里添加相应的方法
1 | "/delemp") ( |
3.测试
- 重新启动项目,在浏览器中输入
http://localhost:8080/emp/1
,可以查询到相应的信息,控制台打印查询语句。 - 再次在浏览器中输入
http://localhost:8080/emp/1
,可以看到浏览器中查到信息,但是控制台没有打印,说明信息来自缓存 - 在浏览器中输入
http://localhost:8080/delemp?id=1
浏览器返回success
控制台返回如下:
- 再次在浏览器输入
http://localhost:8080/emp/1
,浏览器中返回为空,控制台打印如下:
说明删除了数据库里的同时删除了缓存里的数据
4.属性
(1)key
- 指定清除的数据(默认为参数的值)
(2)allEntries
- 是不是删除value值下的所有缓存数据
- 既然全都删除,那就不需要指定key
如图,就是删除emp的所有缓存
(3)beforeInvocation
- 缓存的清除是否在执行方法之前执行(默认是在方法执行之后执行)
如果在方法执行之前清空缓存,那么在方法出错的时候,方法没有执行,但是缓存会清空
四、@Caching
1 | ({ElementType.TYPE, ElementType.METHOD}) |
可以看到这是一个组合注解,可以指定多个缓存规则,用于复杂情况的缓存操作
1.在EmployeeService中写如下方法
1 | ( |
- 可以看到用了这个组合注解,可以一次查询把数据放到很多key里
2.在EmployeeController中添加如下方法
1 | "/emp/lastname/{lastName}") ( |
3.在EmployeeMapper中写如下方法
1 | "SELECT * FROM employee WHERE lastName = #{lastName}") ( |
4.测试
- 先在数据库添加一些数据用于测试
- 运行项目,在浏览器中输入
http://localhost:8080/emp/lastname/张三
浏览器中返回数据:控制台打印查询日志1
{"id":1,"lastName":"张三","email":"111","gender":1,"dId":1}
在浏览器中输入
http://localhost:8080/emp/1
,可以看到浏览器中返回数据相同,但是控制台并没有任何打印,所以数据来源于缓存,这样就实现了查询一次数据库,把数据放到多个key的目的同样的,如果我们再写一个根据email查询的方法,执行完上面的流程,也是可以在缓存中拿到数据的(这里没写根据email查询的,就不再测试了)
注意:虽然已经根据lastName查询出来数据了,这个时候如果我再次根据lastName查询,还是会查询数据库的,因为有@CachePut,后面的方法一定会执行的,执行方法,就会查询数据库
五、@CacheConfig
1.cacheName
根据上面的学习我们可以发现,每一个注解里面都要指定value=“emp”
这样就会增加工作量,我们可以在类上加上@CacheConfig,用属性cacheName指定,格式如下:
1 |
|
- 这里指定了cacheName,下面的所有缓存注解都不需要指定cacheName或者value了