文章目录
ONE、搭建web环境
-
使用springboot初始化器创建一个项目
-
选择抽象的缓存模块
-
所有需要选择的模块:
- 创建需要的数据库
在数据库里创建一些表
-
department表
-
employee表
一、创建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(接口)
首先对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);
}
}
在控制台能看到如下:
这就说明数据库连接成功了
由于数据库里的字段是d_id,javaBean里的是dId,没有指定使用驼峰命名规则,所以是查不到数据的。
4.业务层
- 在主程序相同的目录下建立一个service包
- 建立EmployeeService类
@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
dId没有值的原因是没有开启驼峰命名规则
(1)开启驼峰命名规则
- 在application.properties里加入
mybatis.configuration.map-underscore-to-camel-case=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类,用于配置缓存
@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()+"]";
}
};
}
}
- 在缓存注解中如下操作
4. cacheManager
指定使用哪个缓存管理器
5.condition
指定符合条件的情况下才缓存eg:condition = "#id>0"
6.unless
否定缓存:当unless指定的条件为true,方法的返回值就不会被缓存;(可以获取到结果进行判断)
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.测试
修改查询方法,让它按照默认
(1)查询1号员工,并把查询的结果放到缓存中
- 启动项目,在浏览器中输入
http://localhost:8080/emp/1
浏览器中返回的数据如下:
{"id":1,"lastName":"1","email":"1","gender":1,"dId":1}
控制台打印查询语句
(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
- 控制台打印信息如下
说明【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”
(2)也可以用返回值
- @CachePut是方法执行完之后,在往缓存放东西,可以从返回值中获取相应的值作为key,key=
#result.id
5.再次测试
(1)查询1号员工
- 重新启动项目,在浏览器中输入
http://localhost:8080/emp/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
- 浏览器中的返回结果如下:
{"id":1,"lastName":"张三","email":"123456@qq.com","gender":1,"dId":null}
- 控制台的结果如下:
(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
控制台返回如下:
- 再次在浏览器输入
http://localhost:8080/emp/1
,浏览器中返回为空,控制台打印如下:
说明删除了数据库里的同时删除了缓存里的数据
4.属性
(1)key
- 指定清除的数据(默认为参数的值)
(2)allEntries
- 是不是删除value值下的所有缓存数据
- 既然全都删除,那就不需要指定key
如图,就是删除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.测试
- 先在数据库添加一些数据用于测试
- 运行项目,在浏览器中输入
http://localhost:8080/emp/lastname/张三
浏览器中返回数据:
{"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指定,格式如下:
@Service
@CacheConfig(cacheNames = "emp")
public class EmployeeService {
- 这里指定了cacheName,下面的所有缓存注解都不需要指定cacheName或者value了