1. @Cacheable注解介绍
@Cacheable
可以标记在一个方法上,也可以标记在一个类上。当标记在一个方法上时表示该方法是支持缓存的,当标记在一个类上时则表示该类所有的方法都是支持缓存的。对于一个支持缓存的方法,Spring会在其被调用后将其返回值缓存起来,以保证下次利用同样的参数来执行该方法时可以直接从缓存中获取结果,而不需要再次执行该方法。Spring在缓存方法的返回值时是以键值对进行缓存的,值就是方法的返回结果,至于键的话,Spring又支持两种策略,默认策略和自定义策略。
@Cacheable
可以指定三个属性:value、key和condition
。
参数 | 解释 | example |
---|---|---|
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”) |
1.1 value属性指定Cache名称
value属性是必须指定的,其表示当前方法的返回值是会被缓存在哪个Cache上的,对应Cache的名称。其可以是一个Cache也可以是多个Cache,当需要指定多个Cache时其是一个数组。
@Cacheable("cache1")//Cache是发生在cache1上的
public User find(Integer id) {
returnnull;
}
@Cacheable({"cache1", "cache2"})//Cache是发生在cache1和cache2上的
public User find(Integer id) {
returnnull;
}
1.2 使用key属性自定义key
key属性是用来指定Spring缓存方法的返回结果时对应的key的。该属性支持SpringEL表达式。当我们没有指定该属性时,Spring将使用默认策略生成key。我们这里先来看看自定义策略。
自定义策略是指我们可以通过Spring的EL表达式来指定我们的key。这里的EL表达式可以使用方法参数及它们对应的属性。使用方法参数时我们可以直接使用“#参数名”或者“#p参数index”。下面是几个使用参数作为key的示例。
/**
* key 是指传入时的参数
*
*/
@Cacheable(value="users", key="#id")
public User find(Integer id) {
returnnull;
}
// 表示第一个参数
@Cacheable(value="users", key="#p0")
public User find(Integer id) {
returnnull;
}
// 表示User中的id值
@Cacheable(value="users", key="#user.id")
public User find(User user) {
returnnull;
}
// 表示第一个参数里的id属性值
@Cacheable(value="users", key="#p0.id")
public User find(User user) {
returnnull;
}
1.3 condition属性指定发生的条件
有的时候我们可能并不希望缓存一个方法所有的返回结果。通过condition属性可以实现这一功能。condition属性默认为空,表示将缓存所有的调用情形。其值是通过SpringEL表达式来指定的,当为true时表示进行缓存处理;当为false时表示不进行缓存处理,即每次调用该方法时该方法都会执行一次。如下示例表示只有当user的id为偶数时才会进行缓存。
// 根据条件判断是否缓存
@Cacheable(value={"users"}, key="#user.id", condition="#user.id%2==0")
public User find(User user) {
System.out.println("find user by user " + user);
return user;
}
2. 代码示例
2.1 springboot的启动类加@EnableCaching
@SpringBootApplication
@EnableCaching // 开启缓存
@EnableJpaAuditing //加上注解后,会自动扫描到这个实现了AuditorAware的bean--config目录下的JpaAuditorAware
//@EnableTransactionManagement // 开启事务管理,等同于xml配置方式的 <tx:annotation-driven />
@EnableJpaRepositories(basePackages = "com.pay.payee.repository") // JPA包
//@EnableElasticsearchRepositories(basePackages = "com.pay.payee.repository.es") // 开启ES搜索Repository
public class BankRouterApplication {
public static void main(String[] args) {
SpringApplication.run(BankRouterApplication.class, args);
}
}
2.2 Service接口(最好在service实现类)添加注解Cacheable
由于javax.persistence
还有一个同名注解,我们引入的是org.springframework.cache.annotation
下Cacheable
/*
* @Description 定义缓存名称,使用在:@Cacheable(value = {IPackageIndexService.CACHE_NAME}, key = "#p0")以及清除缓存的地方。
*/
public final String CACHE_NAME = "PACKAGE-INDEX-LIST::BY::STATE";
@Cacheable(value = {IPackageIndexService.CACHE_NAME}, key = "#p0")
public BzPackageIndex findTop1ByPackageId(String packageId);
Service实现类,没有添加@Cacheable
,继承接口的@Cacheable
@Service
@Transactional
@Slf4j
public class PackageIndexServiceImpl implements IPackageIndexService {
@Autowired
IPayerAccountService payerAccountService;
@Autowired
private PackageIndexRepository packageIndexRepository;
public BzPackageIndex findTop1ByPackageId(String packageId) {
return packageIndexRepository.findTop1ByPackageId(packageId);
}
Controller调用service方法
@ApiVersion(5)
@RequestMapping(value = "/findTop1ByPackageId")
// http://localhost:8555/v5/packageIndex/findTop1ByPackageId?packageId=BZ-20200107000005
public BzPackageIndex findTop1ByPackageId(String packageId) {
return packageIndexService.findTop1ByPackageId(packageId);
}
2.3 默认使用Redis进行缓存
在application.yml
配置redis数据源
#####Redis#####
spring:
redis:
host: localhost
port: 6379
# 连接超时时间(毫秒)
timeout: 10000
# Redis默认情况下有16个分片,这里配置具体使用的分片,默认是0
database: 0
# 连接池最大连接数(使用负值表示没有限制) 默认 8
lettuce.pool.max-active: 8
# 连接池最大阻塞等待时间(使用负值表示没有限制) 默认 -1
lettuce.pool.max-wait: -1
# 连接池中的最大空闲连接 默认 8
lettuce.pool.max-idle: 8
# 连接池中的最小空闲连接 默认 0
lettuce.pool.min-idle: 0
3. 测试
访问url:http://localhost:8555/v5/packageIndex/findTop1ByPackageId?packageId=BZ-20200107000005
第一次调用方法时,会看到控制台输出了sql。
Hibernate: select bzpackagei0_.id as id1_2_, bzpackagei0_.check_code as check_co2_2_, bzpackagei0_.close_time as close_ti3_2_, bzpackagei0_.create_by as create_b4_2_, bzpackagei0_.create_date as create_d5_2_, bzpackagei0_.del_flag as del_flag6_2_, bzpackagei0_.money_sum as money_su7_2_, bzpackagei0_.office_id as office_i8_2_, bzpackagei0_.package_id as package_9_2_, bzpackagei0_.payer_account as payer_a10_2_, bzpackagei0_.payer_bank as payer_b11_2_, bzpackagei0_.remarks as remarks12_2_, bzpackagei0_.sort as sort13_2_, bzpackagei0_.state as state14_2_, bzpackagei0_.state2 as state15_2_, bzpackagei0_.sub_sign_code as sub_sig16_2_, bzpackagei0_.submit_time as submit_17_2_, bzpackagei0_.submitter as submitt18_2_, bzpackagei0_.success_ity as success19_2_, bzpackagei0_.success_money as success20_2_, bzpackagei0_.total_ity as total_i21_2_, bzpackagei0_.update_by as update_22_2_, bzpackagei0_.update_date as update_23_2_ from bz_package_index bzpackagei0_ where bzpackagei0_.package_id=? limit ?
第二次调用,控制台没有sql输出,spring会在执行getBookByNamefindTop1ByPackageId
方法前先去缓存里看是否有相应的数据,有,则不会执行方法。没有,才执行去数据库取数据。
4. @CacheEvict
@CacheEvict
是用来标注在需要清除缓存元素的方法或类上的。当标记在一个类上时表示其中所有的方法的执行都会触发缓存的清除操作。@CacheEvict
可以指定的属性有value、key、condition、allEntries和beforeInvocation
。其中value、key和condition
的语义与@Cacheable
对应的属性类似。即value
表示清除操作是发生在哪些Cache
上的(对应Cache
的名称);key
表示需要清除的是哪个key
,如未指定则会使用默认策略生成的key
;condition
表示清除操作发生的条件。下面我们来介绍一下新出现的两个属性allEntries和beforeInvocation
。
4.1 allEntries
属性
allEntries是boolean类型,表示是否需要清除缓存中的所有元素。默认为false
,表示不需要。当指定了allEntries
为true
时,Spring Cache将忽略指定的key
。有的时候我们需要Cache
一下清除所有的元素,这比一个一个清除元素更有效率。
@CacheEvict(value="users", allEntries=true)
public void delete(Integer id) {
System.*out*.println("delete user by id: " + id);
}
4.2 beforeInvocation
属性
清除操作默认是在对应方法成功执行之后触发的,即方法如果因为抛出异常而未能成功返回时也不会触发清除操作。使用beforeInvocation
可以改变触发清除操作的时间,当我们指定该属性值为true
时,Spring
会在调用该方法之前清除缓存中的指定元素。
@CacheEvict(value="users", beforeInvocation=true)
public void delete(Integer id) {
System.*out*.println("delete user by id: " + id);
}
其实除了使用@CacheEvict
清除缓存元素外,当我们使用Redis
作为实现时,我们也可以配置Redis自身的驱除策略。
使用@CacheEvict
- Service接口增加清理缓存的方法
public void clearCacheF1ByPackageId(String packageId);
public void clearAllCache();
- Servie实现类
allEntries = true
代表清除这个名字IPackageIndexService.CACHE_NAME
下的所有key
@Override
@CacheEvict(value = {IPackageIndexService.CACHE_NAME}, key = "#p0")
public void clearCacheF1ByPackageId(String packageId) {
}
@Override
@CacheEvict(value = {IPackageIndexService.CACHE_NAME}, allEntries = true)
public void clearAllCache() {
}
- Controller调用service逻辑
@RequestMapping(value = "/clearCacheF1ByPackageId")
// http://localhost:8555/v5/packageIndex/clearCacheF1ByPackageId?packageId=BZ-20200107000005
public void clearCacheF1ByPackageId(String packageId) {
packageIndexService.clearCacheF1ByPackageId(packageId);
}
@ApiVersion(5)
@RequestMapping(value = "/clearAllCache")
// http://localhost:8555/v5/packageIndex/clearAllCache
public void clearAllCache() {
packageIndexService.clearAllCache();
}
- 测试
4.1 先清除所有缓存,访问:http://localhost:8555/v5/packageIndex/clearAllCache
再访问:http://localhost:8555/v5/packageIndex/findTop1ByPackageId?packageId=BZ-20200107000005
,会有sql输出,证明缓存被清理了。
4.2 某个key清除情况,访问2个url,生成2个缓存
http://localhost:8555/v5/packageIndex/findTop1ByPackageId?packageId=BZ-20200107000005
http://localhost:8555/v5/packageIndex/findTop1ByPackageId?packageId=BZ-20191231000005
清除一个缓存:
http://localhost:8555/v5/packageIndex/clearCacheF1ByPackageId?packageId=BZ-20200107000005
再访问http://localhost:8555/v5/packageIndex/findTop1ByPackageId?packageId=BZ-20200107000005
,输出了sql证明缓存没有了。
再访问http://localhost:8555/v5/packageIndex/findTop1ByPackageId?packageId=BZ-20191231000005
,没有输出sql证明缓存还是存在。