缓存在开发中是一个必不可少的优化点,近期在项目重构中,有关缓存问题,花费大量的时间去做优化,比如在加载数据比较所的场景中,使用缓存机制来提高接口的响应速度,间接的提高用户体验。对于缓存,很多开发者对它都是既爱又恨,爱它的是:能够大幅度提升响应效率,恨的是:如果缓存处理不好、没有用好缓存策略,没有及时更新数据库的数据就会导致数据产生滞后,从而导致用户体验较差。这是一个很严重的老大难的问题,例如我在开发某项功能需要和其他项目进行对接的时候,线上总是出现我推送接口数据成功但是网站数据滞后的现象,询问对方的技术人员,得到的回复是缓存问题,只要删除缓存就没事了,我只能无奈了……所有说如何处理好缓存,对于我们开发人员来说,是一个极其棘手的问题。值得庆幸的是,SpringBoot已经提供给开发者很便捷的开发工作,废话不多了,接下来开始探索SpringBoot的缓存如何使用吧!

  Spring 从3.1 版本开始就定义了 org.springframework.cache.Cacheorg.springframework.cache.CacheManager 接口来统一不同的缓存技术,Cache 接口下 Spring 提供了各种的xxxCache的实现,例如RedisCache、EhCacheCache、ConcurrentMapCache等。每次调用需要缓存功能的方法时,Spring会检查检查指定参数的指定的目标方法是否已经被调用过。如果有就直接从缓存中获取方法调用后的结果,如果没有就调用方法并缓存结果后返回给用户,下次调用直接从缓存中获取。

重要概念以及缓存注解

缓存注解

名称

解释

Cache

缓存接口,定义缓存操作。实现有:RedisCache、EhCacheCache、ConcurrentMapCache等

CacheManager

缓存管理器,管理各种缓存(cache)组件

@Cacheable

主要针对方法配置,能够根据方法的请求参数对其进行缓存。主要实现的功能在进行一个读操作的时候,先从缓存中查询,如果查找不到,在读取数据库。这是缓存的注解最重要的一个方法,基本上我们的所有缓存实现都要依赖于它

@CacheEvict

主要是配合@Cacheable一起使用的,它的主要作用就是清除缓存。当方法进行一些更新、删除操作的时候,就需要删除缓存

@CachePut

这个注解它总是会把数据缓存,而不会去每次做检查它是否存在 。与@Cacheable区别在于是否每次都调用方法,常用于更新

@EnableCaching

开启基于注解的缓存,主要作用就是全局配置缓存

keyGenerator

缓存数据时key生成策略

serialize

缓存数据时value序列化策略

@CacheConfig

统一配置本类的缓存注解的属性

@Cacheable/@CachePut/@CacheEvict 主要的参数

名称

解释

value

缓存的名称,在 spring 配置文件中定义,必须指定至少一个。例如:  @Cacheable(value=”mycache”) 或者@Cacheable(value={”cache1”,”cache2”}

key

缓存的 key,可以为空,如果指定要按照 SpEL 表达式编写,如果不指定,则缺省按照方法的所有参数进行组合。例如:@Cacheable(value=”testcache”,key=”#id”)

condition

缓存的条件,可以为空,使用 SpEL 编写,返回 true 或者 false,只有为 true 才进行缓存/清除缓存。@Cacheable(value=”testcache”,condition=”#userName.length()>2”)

unless

否定缓存。当条件结果为TRUE时,就不会缓存。@Cacheable(value=”testcache”,unless=”#userName.length()>2”)

allEntries(@CacheEvict )

是否清空所有缓存内容,缺省为 false,如果指定为 true,则方法调用后将立即清空所有缓存。  @CachEvict(value=”testcache”,allEntries=true)

beforeInvocation(@CacheEvict)

是否在方法执行前就清空,缺省为 false,如果指定为 true,则在方法还没有执行的时候就清空缓存,缺省情况下,如果方法执行抛出异常,则不会清空缓存@CachEvict(value=”testcache”,beforeInvocation=true)

springBoot开启注解

开始使用前需要导入依赖

<!-- Springboot devtools热部署配置 -->
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-devtools</artifactId>
	<optional>true</optional>
</dependency>

<!-- 引入lombok 依赖 -->
<dependency>
	<groupId>org.projectlombok</groupId>
	<artifactId>lombok</artifactId>
	<scope>provided</scope>
</dependency>

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-web</artifactId>
	<scope>provided</scope>
</dependency>

<!-- 引入cache 缓存依赖 -->
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-cache</artifactId>
</dependency>

<!--MySQL依赖 -->
<dependency>
	<groupId>mysql</groupId>
	<artifactId>mysql-connector-java</artifactId>
</dependency>

<!-- 引入 MyBatis依赖 -->
<dependency>
	<groupId>org.mybatis.spring.boot</groupId>
	<artifactId>mybatis-spring-boot-starter</artifactId>
	<version>1.3.2</version>
</dependency>

在启动类注解@EnableCaching开启缓存

@SpringBootApplication
@EnableCaching  //开启缓存
public class CacheApplication{

    public static void main(String[] args) {
        SpringApplication.run(CacheApplication.class, args);
    }

}

缓存@Cacheable

@Cacheable注解会先查询是否已经有缓存,有会使用缓存,没有则会执行方法并缓存

@Cacheable(key = "#id", unless = "#result.state==0")
public list<SysUser> getLists(Integer uid){
    return cacheDao.getListsByUid(uid);
}

到这里,可以运行程序检验缓存功能是否实现了。如有兴趣的话,也可以深入源码,查看其他的属性,这里简单的说下。

String[] cacheNames() default {}; //和value注解差不多,二选一

String keyGenerator() default ""; //key的生成器。key/keyGenerator二选一使用

String cacheManager() default ""; //指定缓存管理器

String cacheResolver() default ""; //或者指定获取解析器

String condition() default ""; //条件符合则缓存

String unless() default ""; //条件符合则不缓存

boolean sync() default false; //是否使用异步模式

配置@CacheConfig

当我们需要缓存的地方越来越多,你可以使用@CacheConfig(cacheNames = {"myCache"})注解来统一指定value的值,这时可省略value,如果你在你的方法依旧写上了value,那么依然以方法的value值为准。

@Service
@CacheConfig(cacheNames = "articleCache")
public class ArticleService {
    ……
}

CacheConfig 的属性

String keyGenerator() default "";  //key的生成器。key/keyGenerator二选一使用

String cacheManager() default "";  //指定缓存管理器

String cacheResolver() default ""; //或者指定获取解析器

更新@CachePut

@CachePut注解的作用 主要针对方法配置,能够根据方法的请求参数对其结果进行缓存,和 @Cacheable 不同的是,它每次都会触发真实方法的调用 。简单来说就是用户更新缓存数据。但需要注意的是该注解的value 和 key 必须与要更新的缓存相同,也就是与@Cacheable 相同。示例:

@CachePut(value = "cacheTemp", key = "targetClass + #p0")
public SysUser updata(SysUser sysUser) {
    SysUser sysUser = sysUserDao.findAllById(sysUser.getId());
    sysUserDao.updata(sysUser);
    return sysUser;
}

@Cacheable(value = "cacheTemp", key = "targetClass +#p0")//清空缓存
public SysUser save(SysUser sysUser) {
    sysUserDao.save(sysUser);
    return sysUser;
}

CachePut 其他的属性

String[] cacheNames() default {}; //与value二选一

String keyGenerator() default "";  //key的生成器。key/keyGenerator二选一使用

String cacheManager() default "";  //指定缓存管理器

String cacheResolver() default ""; //或者指定获取解析器

String condition() default ""; //条件符合则缓存

String unless() default ""; //条件符合则不缓存

清除@CacheEvict

@CachEvict 的作用 主要针对方法配置,能够根据一定的条件对缓存进行清空 。

@Cacheable(value = "cacheTemp",key = "#id")
public SysUser save(SysUser job) {
    ……
}

//清除一条缓存,key为要清空的数据
@CacheEvict(value="cacheTemp",key="#id")
public void delect(int id) {
    ……
}

//方法调用后清空所有缓存
@CacheEvict(value="accountCache",allEntries=true)
public void delectAll() {
    ……
}

//方法调用前清空所有缓存
@CacheEvict(value="accountCache",beforeInvocation=true)
public void delectAll() {
    ……
}

其他属性

String[] cacheNames() default {}; //与value二选一

String keyGenerator() default "";  //key的生成器。key/keyGenerator二选一使用

String cacheManager() default "";  //指定缓存管理器

String cacheResolver() default ""; //或者指定获取解析器

String condition() default ""; //条件符合则清空

测试

这里使用postman模拟接口请求

执行插入操作

首先我们来增加一篇文章:请求add接口

spring禁用TransactionInterceptor springboot禁用缓存_缓存

后台返回表示成功:

spring禁用TransactionInterceptor springboot禁用缓存_java_02

从后台数据库看到已经插入数据,它的id是1

spring禁用TransactionInterceptor springboot禁用缓存_数据库_03

执行查询操作

在查询操作中,getArticle,我使用线程睡眠的方式,模拟了5秒的时间来处理耗时性业务,第一次请求肯定会查询数据库,理论上第二次请求,将会走缓存,我们来测试一下:首先执行查询操作

spring禁用TransactionInterceptor springboot禁用缓存_缓存_04

接口响应成功,再看一下后台打印:表示执行了一次查询操作,耗时5212秒

spring禁用TransactionInterceptor springboot禁用缓存_postman_05

好,重点来了,我们再次请求接口看看会返回什么?

spring禁用TransactionInterceptor springboot禁用缓存_缓存_06

与上面的比对,这次没有打印执行数据库查询操作,证明没有走数据库,并且耗时只有3ms,成功了!缓存发挥作用,从5212秒减小到3秒!大大提升了响应速度,

执行更新操作

当我们进行修改操作的时候,我们希望缓存的数据被清空:

spring禁用TransactionInterceptor springboot禁用缓存_spring_07

后台控制台打印:

spring禁用TransactionInterceptor springboot禁用缓存_缓存_08

接下来,连续请求三次请求查询接口,看看会返回什么?

第一次请求以及结果

spring禁用TransactionInterceptor springboot禁用缓存_postman_09

spring禁用TransactionInterceptor springboot禁用缓存_数据库_10

连续三次调用查询接口,虽然每次返回的都是相同的结果,看上去没有什么不同,但是我却有很明显的感受,就是第一次查询最慢,第二次、第三次查询特别快。第一次查询耗时是5063毫秒,第二次、第三次耗时确实0,这是因为当我们第一次执行id=2的数据库查询操作,那个时候缓存已被清空,是从数据库中获取的最新数据并放进缓存,然后后面的几次查询都是直接从缓存中获取数据,所以查询速度很快。

执行删除操作

在删除操作中,执行了一次删除,那么缓存也会被清空,查询的时候会再次走数据库,这里就不在具体展示了。

总结

如何在开发中使用缓存?怎样合理的使用都是值得我们学习的地方,缓存能大大提升程序的响应速度,提升用户体验,不过它适用的场景也是读多写少的业务场景,如果数据频繁修改,缓存将会失去意义,每次还是执行的数据库操作!这里只是探讨了Spring的注解缓存,相对来说还是比较简单的,希望起到抛砖引玉的作用.