一、概念
缓存是每个系统都应该考虑的功能 用于加速系统的访问和提升系统的性能
例如一个电商网站的商品信息 如果每次都要查询数据库 耗时过大
此时 可以引入一个缓存中间件 将商品数据信息放入缓存中 就不用查询数据库了 直接查询缓存即可
缓存中若有 则直接使用 缓存中若没有 则查询数据库 并将数据信息放入缓存中
因为 应用程序和缓存的交互是非常快的 速度比查询数据库要快的多 尤其是数据库庞大的情况下
或者处理临时数据 比如验证码 三分钟有效 用完即删除
那么这些数据可以放入缓存中 系统从缓存中存取即可 若到时间则从缓存中删除
应用场景非常广泛
1、JSR-107
为了统一缓存的开发规范 并提升系统的扩展性
J2EE发布了JSR-107缓存规范
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设置
JSR-107定义的都是一些接口 这样 类似于JDBC 直接面向接口编程
然鹅 但并不是市面上所有的缓存组件都提供了JSR-107的实现
若无类似的实现 则须自己写相应的实现
因此JSR-107的使用并不是很多
2、SpringCache缓存抽象
Spring从3.1之后引入了基于注解的缓存技术 本质并不是一个具体的缓存实现方案 而是一个对缓存使用的抽象
通过在既有代码中添加其定义的注解 即能够达到缓存的效果
定义了org.springframework.cache.Cache
和org.springframework.cache.CacheManager
这两个接口来统一不同的缓存技术
并支持使用JCache(JSR-107)注解来简化开发
①、类
- Cache:缓存接口 用于定义缓存操作
它的实现有RedisCache EhCacheCache ConcurrentMapCache等
Cache接口的实现不同 则使用的缓存技术也不同 - CacheManager:缓存管理器 用于管理各种缓存组件(Cache)
②、注解
@Cacheable
:对方法添加该注解 能够根据方法的请求参数对其结果进行缓存
被该注解标注的结果将被缓存
例:
@Cacheable
public User getUser(Integer id)
@CacheEvict
:清空缓存
例:
@CacheEvict
public void deleteUser(Integer id)
@CachePut
:保证方法被调用 又希望结果被缓存
用于缓存更新
例:
@CachePut
publit User updateUser(User user)
@EnableCaching
:标注在启动类上 开启基于注解的缓存
二、使用
在用向导创建项目的时候要引入Spring Cache模块
或者自己添加依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
然后 在主程序类上加上@EnableCaching
注解 开启基于注解的缓存:
注:必须开启 否则无法使用缓存
@MapperScan("net.zjitc.springboot.mapper")
@SpringBootApplication
@EnableCaching
public class SpringbootCacheApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootCacheApplication.class, args);
}
}
之后就是在方法上添加相应的缓存注解了:
在案例中 我用的是MyBatis 当然 其它持久层框架也可以
1、@Cacheable
注解:
使用@Cacheable
注解 将方法的运行结果进行缓存
之后再需要获取相同的数据时 直接可从缓存中获取
@Service
public class EmployeeService {
@Autowired
EmployeeMapper employeeMapper;
@Cacheable(cacheNames = "emp",key = "#id")
public Employee findEmployee(Integer id)
{
return employeeMapper.findEmployeeById(id);
}
}
@Cacheable注解中的属性:
- cacheName或value:指定缓存的名称
CacheManager缓存管理器是可以管理多个缓存组件的
对缓存的真正操作是在缓冲组件中 每一个缓存组件有其唯一的名字
可用数组的形式来指定多个名称 逗号分隔 - key:指定缓存数据时使用的key
数据是以key-value键值对的形式进行存储的
默认使用方法参数的值 例如 输入1 返回User1 那么key为1 value为User1
可用spEL表达式
#a0=#p0=#root.args[0]
还能进行拼接 例:"#root.methodName+’[’+#id+’]’" - keyGenerator:指定key生成器 默认是用参数值作为缓存key 可自己用生成器生成
key和keyGenerator两者只能二选一 - cacheManager:指定缓存管理器
- cacheResolver:指定缓存解析器
cacheResolver得到的是缓存的集合 cacheManager得到的是单个缓存
cacheResolver和cacheManager二选一 - condition:指定符合条件的情况下才进行缓存
- unless:指定符合条件的情况下不进行缓存
和condition相反
例:unless=#result==null 结果为空不进行缓存 - sync:是否使用异步模式进行缓存
spEL表达式:
执行流程:
在@Cacheable
注解标注的方法执行之前先检查缓存中是否有该数据 默认按照参数的值作为key去查询缓存
若没有值 则调用查询数据库的方法 并将结果放入缓存中
若有值 则拿到数据 直接返回 不会调用查询数据库的方法
核心:
使用CacheManager根据名字得到Cache组件
CacheManager默认用的是ConcurrentMapCacheManager
Cache默认用的是ConcurrentMapCache
key使用keyGenerator来生成
默认用的是SimpleKeyGenerator
keyGenerator
属性 指定key生成器
自己写一个配置类 对其进行配置:
@Configuration
public class MyCacheConfig {
@Bean("myKeyGenerator")
public KeyGenerator keyGenerator()
{
return new KeyGenerator() {
@Override
public Object generate(Object target, Method method, Object... params) {
return method.getName()+"["+ Arrays.asList(params).toString()+"]";
}
};
}
}
然后在keyGenerator属性里填入生成器的名称即可
@Cacheable(cacheNames = "emp",keyGenerator = "myKeyGenerator")
public Employee findEmployee(Integer id)
{
System.out.println("查询"+id+"号员工");
return employeeMapper.findEmployeeById(id);
}
condition
属性 指定缓存条件
@Cacheable(cacheNames = "emp",keyGenerator = "myKeyGenerator",condition = "#a0==2")
public Employee findEmployee(Integer id)
{
System.out.println("查询"+id+"号员工");
return employeeMapper.findEmployeeById(id);
}
condition = "#a0==2"
的意思是:当第1个参数为2时 才进行缓存
还可添加更多条件 然后进行连接 例如and eq等 实现多重判断
unless
属性 指定不缓存条件
@Cacheable(cacheNames = "emp",keyGenerator = "myKeyGenerator",unless = "#a0==2")
public Employee findEmployee(Integer id)
{
System.out.println("查询"+id+"号员工");
return employeeMapper.findEmployeeById(id);
}
unless = "#a0==2"
的意思是:当第1个参数不为2时 才进行缓存
condition和unless可同时存在
2、@CachePut
注解:
既调用方法 又更新数据
通常用于修改数据的更新方法
@CachePut(cacheNames = "emp")
public Employee updateEmployee(Employee employee)
{
employeeMapper.updateEmployee(employee);
return employee;
}
@CachePut的执行时机和@Cacheable不同
@Cacheable
是在方法调用之前进行判断
而@CachePut
是先调用方法 然后将方法的结果放入缓存中
无论如何 方法都会被执行到
有一个需要注意的点:
默认key的值是参数 因此@CachePut的key默认是传入的对象
而@Cacheable是key默认是查询时传入的参数
所以它们两个数据是都会被存放在缓存中 更新后的数据不会覆盖缓存中的数据
因此 若要覆盖 必须指定统一的key 相当于是门牌号 得相同 才可互相识别
@CachePut(cacheNames = "emp",key = "#employee.id")
public Employee updateEmployee(Employee employee)
{
System.out.println("更新了 最新数据:"+employee);
employeeMapper.updateEmployee(employee);
return employee;
}
或
@CachePut(cacheNames = "emp",key = "#result.id")
public Employee updateEmployee(Employee employee)
{
System.out.println("更新了 最新数据:"+employee);
employeeMapper.updateEmployee(employee);
return employee;
}
这两种写法都可以 #employee.id是从入参中取属性值 而**#result.id**是从结果中取属性值
对于更新操作来说 入参属性值和更新后结果属性值是一样的 且@CachePut
标注的方法必定会被执行 因此一定是有result结果的
而@Cacheable就不能从结果中取值了 因为@Cacheable
标注的方法不一定会被执行 因而 不一定会有结果
3、@CacheEvict
注解
该注解用于清除缓存
通过key属性来指定要清除的Cache
若不设置key 则默认使用传入的参数值
@CacheEvict(cacheNames = "emp",key = "#id")
public void deleteEmployee(Integer id)
{
System.out.println("删除了"+id);
employeeMapper.deleteEmployee(id);
}
allEntries
属性 是否删除指定缓存中的所有数据
@CacheEvict(cacheNames = "emp",allEntries = true)
public Integer deleteEmployee(Integer id)
{
System.out.println("删除了"+id);
employeeMapper.deleteEmployee(id);
return id;
}
beforeInvocation
属性 缓存中数据的清除是否在方法执行之前
默认为false 即 在方法执行之后清除
区别就是 在方法执行之后清除的话 若是在方法执行过程中出错 则缓存不会被清除
而在方法执行之前清除 无论方法执行是否出错 缓存都会被清除
@CacheEvict(cacheNames = "emp",allEntries = true,beforeInvocation = true)
public Integer deleteEmployee(Integer id)
{
System.out.println("删除了"+id);
employeeMapper.deleteEmployee(id);
return id;
}
4、@Caching
注解
@Caching注解是@Cacheable
注解和@CachePut
注解和@CacheEvict
注解的综合版
是个三合一注解 用于定义复杂的缓存规则
内部是这样的:
public @interface Caching {
Cacheable[] cacheable() default {};
CachePut[] put() default {};
CacheEvict[] evict() default {};
}
当缓存规则比较复杂的时候可使用该注解 以同时指定多个规则
@Caching(cacheable = {@Cacheable(cacheNames="emp",key="#lastName")},
put = {@CachePut(cacheNames="emp",key="#result.id"),
@CachePut(cacheNames="emp",key="#result.email")})
public Employee getEmployeeByLastName(String lastName)
{
return employeeMapper.getEmployeeByLastName(lastName);
}
5、@CacheConfig
注解
该注解加在类上 用于指定该类中缓存配置的公共属性
内部是这样的:
public @interface CacheConfig {
String[] cacheNames() default {};
String keyGenerator() default "";
String cacheManager() default "";
String cacheResolver() default "";
}
指定之后 该类里面所有缓存注解的相应属性都可以不用配置了:
@Service
@CacheConfig(cacheNames = "emp")
public class EmployeeService {
@Caching(cacheable = {@Cacheable(key="#lastName")},
put = {@CachePut(key="#result.id"),
@CachePut(key="#result.email")})
public Employee getEmployeeByLastName(String lastName)
{
return employeeMapper.getEmployeeByLastName(lastName);
}
}