JSR107
Java Caching定义了5个核心接口,分别是CachingProvider, CacheManager, Cache, Entry和 Expiry。
• CachingProvider定义了创建、配置、获取、管理和控制多个CacheManager。一个应用可以在运行期访问多个CachingProvider。
• CacheManager定义了创建、配置、获取、管理和控制多个唯一命名的Cache,这些Cache存在于CacheManager的上下文中。一个CacheManager仅被一个CachingProvider所拥有。
• Cache是一个类似Map的数据结构并临时存储以Key为索引的值。一个Cache仅被一个CacheManager所拥有。
• Entry是一个存储在Cache中的key-value对。
• Expiry 每一个存储在Cache中的条目有一个定义的有效期。一旦超过这个时间,条目为过期的状态。一旦过期,条目将不可访问、更新和删除。缓存有效期可以通过ExpiryPolicy设置。

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、从缓存中读取之前缓存存储的数据

缓存的使用
搭建环境
1. 导入依赖
<dependency>
<!--缓存-->
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<!--web-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--mybatis-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.2</version>
</dependency>
<!--mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
2. 创建数据库
database : spring_cache
table_1 : employee
table_2 : department
3. 创建jave bean 封装数据库数据
4. 整合mybatis
1. 配置数据源和mybatis基本配置
application.yml配置文件
#数据源
spring:
datasource:
username: root
password: 001129
url: jdbc:mysql://localhost:3306/spring_cache?serverTimezone=UTC&characterEncoding=utf-8
driver-class-name: com.mysql.jdbc.Driver
#mybatis 配置
mybatis:
type-aliases-package: com.it.bean
configuration:
map-underscore-to-camel-case: true
debug: true
2. 编写mapper接口
EmployeeMapper.java
@Mapper
@Repository
public interface EmployeeMapper {
@Select("SELECT * FROM employee WHERE `id` = #{id}")
public Employee getEmployeeById(@Param("id") Integer id);
@Update("UPDATE employee SET `lastName` = #{lastName},`email`=#{email},`gender`=#{gender},`d_id`=#{dId} WHERE `id`=#{id}")
public void updateEmployee(Employee employee);
@Delete("DELETE FROM employee WHERE `id`=#{id}")
public void deleteEmployee(@Param("id") Integer id);
@Insert("INSERT INTO employee (`lastName`,`email`,`gender`,`d_id`) VALUES(#{lastName},#{email},#{gender},#{dId})")
public void insertEmployee(Employee employee);
}
DepartmentMapper.java 略
3. 编写Service层
@Service
public class EmployeeService {
@Autowired
EmployeeMapper employeeMapper;
public Employee getEmployeeById(Integer id){
System.out.println("查询" + id + "号员工");
Employee employee = employeeMapper.getEmployeeById(id);
return employee;
}
}
4. 编写Controller层测试
@RestController
public class EmployeeController {
@Autowired
EmployeeService employeeService;
@GetMapping("getEmployee/{id}")
public Employee getEmployee(@PathVariable("id") Integer id){
return employeeService.getEmployeeById(id);
}
}
5. 开启缓存
在主程序上标注注解 @EnableCaching 开启缓存
@SpringBootApplication
@EnableCaching
public class Springboot09CacheApplication {
public static void main(String[] args) {
SpringApplication.run(Springboot09CacheApplication.class, args);
}
}
6. 使用缓存注解@Cacheable
在方法上标注缓存注解
service层下的getEmployeeById方法下标注 @Cacheable使用缓存
@Cacheable 将方法的返回结果进行缓存 ,
下次需要可以直接从缓存中获取,不用调用方法
@Cacheable(cacheNames = "emp" )
public Employee getEmployeeById(Integer id){
System.out.println("查询" + id + "号员工");
Employee employee = employeeMapper.getEmployeeById(id);
return employee;
}
编写Controller 来测试
@GetMapping("/getEmployee/{id}")
public Employee getEmployee(@PathVariable("id") Integer id){
return employeeService.getEmployeeById(id);
}
测试访问页面 查询一号员工第一次需要调用方法,第二次则不需要调用方法,直接从缓存中获取到数据
缓存的主要参数


/**
* @Cacheable 将方法的返回结果进行缓存 ,下次需要可以直接从缓存中获取,不用调用方法
*
* CacheManager 管理多个Cache组件,对缓存的CRUD操作在Cache组件中,每个组件有自己的名字
*
* @Cacheable 中属性
* cacheName / value : 指定缓存组件的名字
* key : 缓存数据使用的key 可以用它来指定,默认是方法的参数值
* keyGenerator : key生成器,可以自己指定 key 和 keyGenerator 二选一
* cacheManager : 指定缓存管理器
* cacheResolver : 指定缓存解析器
* condition : 指定符合条件情况下才缓存 condition = "#id>0"
* unless : 否定缓存 当unless指定的条件为true 这个方法的返回值就不会被缓存
* unless = "#result == null"
* sync : 缓存是否使用异步模式
缓存的工作原理
缓存的工作原理
* 1. 自动配置类 CacheAutoConfiguration
* 2. 缓存配置类
* org.springframework.boot.autoconfigure.cache.GenericCacheConfiguration
* org.springframework.boot.autoconfigure.cache.JCacheCacheConfiguration
* org.springframework.boot.autoconfigure.cache.EhCacheCacheConfiguration
* org.springframework.boot.autoconfigure.cache.HazelcastCacheConfiguration
* org.springframework.boot.autoconfigure.cache.InfinispanCacheConfiguration
* org.springframework.boot.autoconfigure.cache.CouchbaseCacheConfiguration
* org.springframework.boot.autoconfigure.cache.RedisCacheConfiguration
* org.springframework.boot.autoconfigure.cache.CaffeineCacheConfiguration
* org.springframework.boot.autoconfigure.cache.SimpleCacheConfiguration
* org.springframework.boot.autoconfigure.cache.NoOpCacheConfiguration
* 3. 默认生效的缓存配置类 SimpleCacheConfiguration
* 4. 给容器中注册 CacheManager : ConcurrentMapCacheManager
* 5. 获取和创建 ConcurrentMapCache类型的缓存组件,将数据保存在ConcurrentMap中

缓存的运行流程
* 运行流程
* @Cacheable:
* 1. 方法执行前,先去查询Cache(缓存组件),按照cacheNames 指定名字查找
* CacheManager先获取相应的缓存,第一次获取如果没有cache组件会自动创建
* 2. 去cacheMap中查找缓存内容,使用一个key ,默认是方法的参数
* key 是按照某种策略生成,默认使用keyGenerator,默认使用simpleKeyGenerator生成
* 3. 没有查到缓存就调用目标方法
* 4. 将目标方法返回的结果,放进缓存中
*
* @cacheable标注的方法执行前先检查缓存中有无这个数据,默认按照参数值作为key去查询缓存
* 如果没有运行方法并将返回值存入缓存,以后调用就直接可以使用缓存中的数据
7. 使用缓存注解@CachePut
@CachePut : 即调用方法,又更新数据,同步更新缓存
修改了数据库中的信息,同时更新缓存
//@CachePut : 即调用方法,又更新数据
// 修改了数据库中的信息,同时更新缓存
@CachePut(value = "emp",key = "#result.id")
public Employee updateEmployee(Employee employee){
employeeMapper.updateEmployee(employee);
System.out.println("更新了一条数据");
return employee;
}
}
编写Controller 来测试
@GetMapping("/updateEmployee")
public Employee updateEmployee(Employee employee){
Employee employee1 = employeeService.updateEmployee(employee);
return employee1;
}
运行:
1. 先调用目标方法
2. 将结果缓存起来
(@Cacheable是方法执行前去缓存中查询,没有就在方法执行完存入
有就直接取
@CachePut是方法执行后将结果存入缓存
不查询
)
测试步骤:
1. 查询1号员工
2. 更新1号员工
3. 再次查询1号员工
上述步骤测试完发现,(Key值不统一的情况下)会发现更新完
1号员工 再次查询1号员工 结果还是第一次查询的1号员工信息,
我们需要的是员工信息更改后,查询出来的是更改后的信息
因为 缓存是 key value 形式
这时候我们就需要将查询员工的缓存key值 和 更新员工的缓存key值 保持统一
因为如果@CachePut不指定key值 默认的是 方法参数作为key
我们查询员工的key 是参数 id 而更新员工则是参数 employee对象
这样就形成了两个key
key 为 id 的缓存中保存的是 第一次查询后的员工信息值
key 为 employee对象的缓存中保存的是更新后的员工信息
我们调用查询员工 就会到缓存key为id中将数据拿出来
这时我们将两个方法设置为同一key
这样我们更新后的数据也会在这个key中更新
再次调用查询就会从缓存中拿出我们更新后的值
8. 使用缓存注解 @CacheEvict
@CacheEvict :缓存清除
allEntries = true 删除缓存中所有数据
beforeInvocation = false 缓存清除是否在方法执行前执行
默认 false 代表方法执行后执行,如果出错,缓存不会被清除
设置为 true 代表方法执行前执行,无论方法是否异常,都会清除缓存
@CacheEvict(value = "emp",key = "#id")
public void deleteEmployee(Integer id){
System.out.println("删除了一名员工");
employeeMapper.deleteEmployee(id);
}
编写Controller 来测试
@GetMapping("/deleteEmployee/{id}")
public String deleteEmployee(@PathVariable("id") Integer id){
employeeService.deleteEmployee(id);
return "删除了" + id + "员工 ";
}
测试发现,我们查询1号员工,第二次查询时不需要走数据库,直接从缓存中获取,
但是我们调用了@CacheEvict修饰的方法 执行后,再次查询我们发现需要走数据库
也就证明,@CacheEvict将我们的缓存删除了
key = xxx : 删除 指定key的缓存
allEntries = true : 删除缓存中所有数据
beforeInvocation = false 缓存清除是否在方法执行前执行
默认 false 代表方法执行后执行,如果出错,缓存不会被清除
设置为 true 代表方法执行前执行,无论方法是否异常,都会清除缓存
9. 使用缓存注解@Caching
@Caching 定义复杂缓存规则
//@Caching 定义复杂缓存规则
@Caching(
cacheable = {
@Cacheable(value="emp",key = "#lastName")
},
put = {
@CachePut(value = "emp",key = "#result.id")
}
)
public Employee getEmployeeByLastName(String lastName){
System.out.println("查询到了");
return employeeMapper.getEmployeeByLastName(lastName);
}
编写Controller 来测试
我们测试发现, 我们根据lastName查询员工信息,由于我们定义了
复杂缓存规则,@CachePut 会将查询后的数据添加到key为id的缓存中
所以下次我们根据id来查询就不需要走数据库了
就会从缓存中拿
可以在类上添加 @CacheConfig(cacheNames = “emp”)
这样我们就统一了缓存组件的名字
下面的缓存就不需要写 cacheName 或 value了