Spring Cache的使用方法和原理类似于Spring对事务管理的支持,都是AOP的方式。其核心思想是:当我们在调用一个缓存方法时会把该方法参数和返回结果作为一个键值对存放在缓存中,等到下次利用同样的参数来调用该方法时将不再执行该方法,而是直接从缓存中获取结果进行返回。
Spring Cache提供了@Cacheable、@CachePut、@CacheEvict等注解,在方法上使用。通过注解Cache可以实现类似于事务一样、缓存逻辑透明的应用到我们的业务代码上,且只需要更少的代码就可以完成。
Spring中提供了4个注解来声明缓存规则,如下所示:
❑@Cacheable——主要针对方法配置,根据方法的请求参数对其结果进行缓存。
❑@CachePut——主要针对方法配置,根据方法的请求参数对其结果进行缓存,和@Cacheable不同的是,每次都会触发真实方法的调用。
❑@CacheEvict——主要针对方法配置,能够根据一定的条件对缓存进行清空。
❑@Caching——用来组合使用其他注解,可以同时应用多个Cache注解。
cache是什么呢?cache其实就是缓存,spring cache可以把结果缓存到内存中,而现在大多数用的是缓存的中间件如redis,escache啦,等等,那今天我们先来认识下缓存吧
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、从缓存中读取之前缓存存储的数据
Cache | 缓存接口,定义缓存操作。实现有:**RedisCache、EhCacheCache、ConcurrentMapCache等** |
---|---|
CacheManager | 缓存管理器,管理各种缓存(**Cache)组件** |
@Cacheable | 主要针对方法配置,能够根据方法的请求参数对其结果进行缓存 |
@CacheEvict | 清空缓存 |
@CachePut | 保证方法被调用,又希望结果被缓存。 |
@EnableCaching | 开启基于注解的缓存 |
keyGenerator | 缓存数据时key生成策略** |
serialize | 缓存数据时value序列化策略** |
@Cacheable/@CachePut/ @CacheEvict主要的参数 | ||
---|---|---|
value | 缓存的名称,在 spring 配置文件中定义,必须指定至少一个 | 例如: @Cacheable(value=”mycache”) 或者 @Cacheable(value={”cache1”,”cache2”} |
key | 缓存的 key,可以为空,如果指定要按照 SpEL 表达式编写,如果不指定,则缺省按照方法的所有参数进行组合 | 例如: @Cacheable(value=”testcache”,key=”#userName”) |
condition | 缓存的条件,可以为空,使用 SpEL 编写,返回 true 或者 false,只有为 true 才进行缓存/清除缓存,在调用方法之前之后都能判断 | 例如: @Cacheable(value=”testcache”,condition=”#userName.length()>2”) |
allEntries (@CacheEvict ) | 是否清空所有缓存内容,缺省为 false,如果指定为 true,则方法调用后将立即清空所有缓存 | 例如: @CachEvict(value=”testcache”,allEntries=true) |
unless (@CachePut) (@Cacheable) | 用于否决缓存的,不像condition,该表达式只在方法执行之后判断,此时可以拿到返回值result进行判断。条件为true不会缓存,fasle才缓存 | 例如: @Cacheable(value=”testcache”,unless=”#result == null”) |
beforeInvocation (@CacheEvict) | 是否在方法执行前就清空,缺省为 false,如果指定为 true,则在方法还没有执行的时候就清空缓存,缺省情况下,如果方法执行抛出异常,则不会清空缓存 | 例如: @CachEvict(value=”testcache”,beforeInvocation=true) |
spel表达式所支持的参数
名字 | 位置 | 描述 | 示例 |
---|---|---|---|
methodName | root object | 当前被调用的方法名 | #root.methodName |
method | root object | 当前被调用的方法 | #root.method.name |
target | root object | 当前被调用的目标对象 | #root.target |
targetClass | root object | 当前被调用的目标对象类 | #root.targetClass |
args | root object | 当前被调用的方法的参数列表 | #root.args[0] |
caches | root object | 当前方法调用使用的缓存列表(如@Cacheable(value={"cache1", "cache2"})),则有两个cache | #root.caches[0].name |
argument name | evaluation context | 方法参数的名字. 可以直接 #参数名 ,也可以使用 #p0或#a0 的形式,0代表参数的索引; | #iban 、 #a0 、 #p0 |
result | evaluation context | 方法执行后的返回值(仅当方法执行之后的判断有效,如‘unless’,’cache put’的表达式 ’cache evict’的表达式beforeInvocation=false) | #result |
Spring Cache的使用方法和原理类似于Spring对事务管理的支持,都是AOP的方式。其核心思想是:当我们在调用一个缓存方法时会把该方法参数和返回结果作为一个键值对存放在缓存中,等到下次利用同样的参数来调用该方法时将不再执行该方法,而是直接从缓存中获取结果进行返回。
Spring Cache提供了@Cacheable、@CachePut、@CacheEvict等注解,在方法上使用。通过注解Cache可以实现类似于事务一样、缓存逻辑透明的应用到我们的业务代码上,且只需要更少的代码就可以完成。
Spring中提供了4个注解来声明缓存规则,如下所示:
❑@Cacheable——主要针对方法配置,根据方法的请求参数对其结果进行缓存。
❑@CachePut——主要针对方法配置,根据方法的请求参数对其结果进行缓存,和@Cacheable不同的是,每次都会触发真实方法的调用。
❑@CacheEvict——主要针对方法配置,能够根据一定的条件对缓存进行清空。
❑@Caching——用来组合使用其他注解,可以同时应用多个Cache注解。
下面我们分别来简单介绍。
1. @Cacheable
该注解中的属性值如下:
❑value:缓存名,必填。
❑key:可选属性,可以使用SPEL标签自定义缓存的key。
❑condition:属性指定发生的条件。
代码示例如下:
@Cacheable("userList") //标识读缓存操作
override fun findAll(): List {
return userDao.findAll()
}
@Cacheable(cacheNames = ["user"], key = "#id") //如果缓存存在,直接读取缓存值;如果不存
// 在调用目标方法,并将方法返回结果放入缓存
override fun findOne(id: Long): User {
return userDao.getOne(id)
}
2. @CachePut
使用该注解标识的方法,每次都会执行目标逻辑代码,并将结果存入指定的缓存中。之后另一个方法就可以直接从相应的缓存中取出缓存数据,而不需要再去查询数据库。@CachePut注解的属性说明如下:
❑value:缓存名,必填。
❑key:可选属性,可以使用SPEL标签自定义缓存的key。
代码示例如下:
@Transactional
@CachePut(cacheNames = ["user"], key = "#user.id") //写入缓存,key为user.id;
// 一般可以标注在save方法上面
override fun saveUser(user: User): User {
return userDao.save(user)
}
3. @CacheEvict
标记要清空缓存的方法,当这个方法被调用后,即会清空缓存。@CacheEvict注解属性说明如下:
❑value:必填。
❑key:可选(默认是所有参数的组合)。
❑condition:缓存的条件。
❑allEntries:是否清空所有缓存内容,默认为false。如果指定为true,则方法调用后将立即清空所有缓存。
❑beforeInvocation:是否在方法执行前就清空,默认为false。如果指定为true,则在方法还没有执行的时候就清空缓存。默认情况下,如果方法执行抛出异常,则不会清空缓存。
代码示例如下:
@Transactional
@CacheEvict(cacheNames = ["user"], key = "#id") //根据key (值为id) 来清除缓存;一般标
// 注在delete, update方法上面
override fun updatePassword(id: Long, password: String): Int {
return userDao.updatePassword(id, password)
}
4. @Caching
@Caching注解的源码如下,从中可以看到同时使用了Cacheable、CachePut、Cache-Evict方法:
public @interface Caching {
Cacheable[] cacheable() default {};
CachePut[] put() default {};
CacheEvict[] evict() default {};
}
使用@Caching注解可以实现在同一个方法上同时使用多种注解,例如:
@Caching(evict={@CacheEvict(“u1”), @CacheEvict(“u2”, allEntries=true)})
下面看代码实例
启动类:
package com.kayleoi.springbootcache;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;
/**
* 默认使用时ConcurrentMapCacheManager == ConcurrentMapCache 将数据保存到ConcurrentMap
service层:在这用到的注解在代码中会有重要的讲解,请看代码
package com.kayleoi.springbootcache.service;
import com.kayleoi.springbootcache.bean.Employee;
import com.kayleoi.springbootcache.mapper.EmployeeMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.*;
import org.springframework.stereotype.Service;
/**
* @Author kay三石
* @date:2019/7/22
* @CacheConfig(cacheNames = "emp") 抽取缓存的公共配置
*/
@CacheConfig(cacheNames = "emp")
@Service
public class EmployeeService {
@Autowired
EmployeeMapper employeeMapper;
/**
* 属性:
* cacheNames/value: 执行缓存组件的名字,将方法的返回结果放到哪个缓存中是数组方式,可指定多个缓存
* key: 缓存数据使用的key,可以用来指定默认使用方法参数的值,1-方法的返回值
* 编写spel: # id: 参数id的值, #a0 #p0 #root.args[0]
* 执行使用的key :key = "#root.methodName+'['+#id+']'" ====getEmployee(id)
* keyGenerator:key的生成器,可以自己指定key的生成器的组件的id
* key/keyGenerator二则选一使用
*
* cacheManager: 指定缓存管理器,或者cacheResolver指定获取解析器
* condition: 指定符合条件的情况下才缓存 condion= "#id >0 and #id <10"
* unless: 否定缓存,当unless指定条件为true时,方法的返回值就不会被缓存,可以获取到结果进行判断
* unless = "#result == null"
* unless = "#a0==2" 如果第一个参数为2时则不缓存
* sync: 是否使用异步的方式
*
* @param id
* @return
*/
@Cacheable(value = {"emp"}, key = "#root.methodName+'['+#id+']'")
public Employee getEmployee(Integer id) {
System.out.println("查询数据库");
Employee empById = employeeMapper.getEmpById(id);
return empById;
}
/**
* 指定缓存
*
* @param id
* @return
*/
@Cacheable(value = {"emp2"}, keyGenerator = "myKeyGenerator", condition = "#a0>0", unless = "#a0==2")
public Employee getEmployee2(Integer id) {
Employee empById = employeeMapper.getEmpById(id);
return empById;
}
/**
* @CachePut: 调用方法,又更新缓存 ,修改的同时更新(同步更新缓存)
* 运行实机:
* 1.先调用目标方法
* 2.将目标方法的结果缓存起来
* 方法运行后将结果放入
*
* 调式步骤:
* 1.查询1号员工,查到的结果放入缓存中:
* key :1 value: lastName:
* 2. 以后查询还是之前的结果
* 3. 更新1号员工,
* 将方法的返回值放入了缓存 key:传入的emplyee对象,返回的也是对象
* 4. 查询1号员工
* 应该是更新后的员工
* key = "#employee.id" 使用传入参数的id
* key = "#result.id" 使用返回值的id
* @Caheable的key不能使用#result
* 但为什么是没有更新前呢?【1号员工没有在缓冲中更新,就是缓存中没有这个key】
*/
@CachePut(value = {"emp"}, key = "#employee.id")
public Employee update(Employee employee){
System.out.println("employee = [" + employee + "]");
employeeMapper.updateEmp(employee);
return employee;
}
/**
* 清除缓存
* key:指定清除的数据
* allEntries = true 指定清除这缓存中的所有的数据
* beforeInvocation = false:
* 默认是方法执行之后执行,如果出现异常缓存不会清除
*
* beforeInvocation = true
* 代表清除缓存操作是在方法运行之前执行,无论方法是否出现异常,缓存都清除
*
*
* @param id
*/
@CacheEvict(value = "emp",beforeInvocation = true, key = "#id")
public void deleteEmp(Integer id){
employeeMapper.deleteEmById(id);
}
@Caching(
cacheable = {
@Cacheable(value = "emp", key = "#lastName")
},
put = {
@CachePut(value = "emp", key = "#result.id"),
@CachePut(value = "emp", key = "#result.email")
}
)
public Employee getEmployeeByName(String lastName){
return employeeMapper.getEmpByName(lastName);
}
}
mapper类:
package com.kayleoi.springbootcache.mapper;
import com.kayleoi.springbootcache.bean.Employee;
import org.apache.ibatis.annotations.*;
import org.springframework.stereotype.Repository;
/**
* @Author kay三石
* @date:2019/7/22
* @Mapper 标注为是一个mapper
*/
@Mapper
@Repository
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 deleteEmById(Integer id);
@Insert("insert into employee(lastName,email,gender,d_id) values(#{lastName},#{email},#{gender},#{dId})")
public void insertEmployee(Employee employee);
@Select("select * from employee where lastName= #{lastName}")
public Employee getEmpByName(String lastName);
}
ok,这就是全部的操作了,不过现在都用中间件了,这原生的操作还是应该牢记的,毕竟其他的肯定会依靠他来做实现,或者是模仿一些东西。ok,就这些了