

Spring Boot 2.x基础教程:进程内缓存的使用与Cache注解详解
source link: http://blog.didispace.com/spring-boot-learning-21-5-1/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

随着时间的积累,应用的使用用户不断增加,数据规模也越来越大,往往数据库查询操作会成为影响用户使用体验的瓶颈,此时使用缓存往往是解决这一问题非常好的手段之一。Spring 3开始提供了强大的基于注解的缓存支持,可以通过注解配置方式低侵入的给原有Spring应用增加缓存功能,提高数据访问性能。
在Spring Boot中对于缓存的支持,提供了一系列的自动化配置,使我们可以非常方便的使用缓存。下面我们通过一个简单的例子来展示,我们是如何给一个既有应用增加缓存功能的。
快速入门
下面我们将使用 使用Spring Data JPA访问MySQL 一文的案例为基础。这个案例中包含了使用Spring Data JPA访问User数据的操作,利用这个基础,我们为其添加缓存,来减少对数据库的IO,以达到访问加速的作用。如果您还不熟悉如何实现对MySQL的读写操作,那么建议先阅读前文,完成这个基础案例的编写。
先简单回顾下这个案例的基础内容:
User实体的定义
@Entity @Data @NoArgsConstructor public class User { @Id @GeneratedValue private Long id; private String name; private Integer age; public User(String name, Integer age) { this.name = name; this.age = age; } }
User实体的数据访问实现
public interface UserRepository extends JpaRepository<User, Long> { User findByName(String name); User findByNameAndAge(String name, Integer age); @Query("from User u where u.name=:name") User findUser(@Param("name") String name); }
为了更好的理解缓存,我们先对该工程做一些简单的改造。
-
application.properties
文件中新增spring.jpa.show-sql=true
,开启hibernate对sql语句的打印。如果是1.x版本,使用spring.jpa.properties.hibernate.show_sql=true
参数。 - 修改单元测试类,插入User表一条用户名为AAA,年龄为10的数据。并通过findByName函数完成两次查询,具体代码如下:
@RunWith(SpringRunner.class) @SpringBootTest public class Chapter51ApplicationTests { @Autowired private UserRepository userRepository; @Test public void test() throws Exception { // 创建1条记录 userRepository.save(new User("AAA", 10)); User u1 = userRepository.findByName("AAA"); System.out.println("第一次查询:" + u1.getAge()); User u2 = userRepository.findByName("AAA"); System.out.println("第二次查询:" + u2.getAge()); } }
在没有加入缓存之前,我们可以先执行一下这个案例,可以看到如下的日志:
Hibernate: select user0_.id as id1_0_, user0_.age as age2_0_, user0_.name as name3_0_ from user user0_ where user0_.name=? 第一次查询:10 Hibernate: select user0_.id as id1_0_, user0_.age as age2_0_, user0_.name as name3_0_ from user user0_ where user0_.name=? 第二次查询:10
两次 findByName
查询都执行了两次SQL,都是对MySQL数据库的查询。
引入缓存
第一步:在 pom.xml
中引入cache依赖,添加如下内容:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-cache</artifactId> </dependency>
第二步:在Spring Boot主类中增加 @EnableCaching
注解开启缓存功能,如下:
@EnableCaching @SpringBootApplication public class Chapter51Application { public static void main(String[] args) { SpringApplication.run(Chapter51Application.class, args); } }
第三步:在数据访问接口中,增加缓存配置注解,如:
@CacheConfig(cacheNames = "users") public interface UserRepository extends JpaRepository<User, Long> { @Cacheable User findByName(String name); }
第四步:再来执行以下单元测试,可以在控制台中输出了下面的内容
Hibernate: insert into user (age, name, id) values (?, ?, ?) Hibernate: select user0_.id as id1_0_, user0_.age as age2_0_, user0_.name as name3_0_ from user user0_ where user0_.name=? 第一次查询:10 第二次查询:10
到这里,我们可以看到,在调用第二次 findByName
函数时,没有再执行select语句,也就直接减少了一次数据库的读取操作。
为了可以更好的观察,缓存的存储,我们可以在单元测试中注入 CacheManager
。
@Autowired private CacheManager cacheManager;
使用debug模式运行单元测试,观察 CacheManager
中的缓存集users以及其中的User对象的缓存加深理解。
可以看到,在第一次调用 findByName
函数之后, CacheManager
将这个查询结果保存了下来,所以在第二次访问的时候,就能匹配上而不需要再访问数据库了。
Cache配置注解详解
回过头来我们再来看这里使用到的两个注解分别作了什么事情:
-
@CacheConfig
:主要用于配置该类中会用到的一些共用的缓存配置。在这里@CacheConfig(cacheNames = "users")
:配置了该数据访问对象中返回的内容将存储于名为users的缓存对象中,我们也可以不使用该注解,直接通过@Cacheable
自己配置缓存集的名字来定义。 -
@Cacheable
:配置了findByName函数的返回值将被加入缓存。同时在查询时,会先从缓存中获取,若不存在才再发起对数据库的访问。该注解主要有下面几个参数:-
value
、cacheNames
:两个等同的参数(cacheNames
为Spring 4新增,作为value
的别名),用于指定缓存存储的集合名。由于Spring 4中新增了@CacheConfig
,因此在Spring 3中原本必须有的value
属性,也成为非必需项了 -
key
:缓存对象存储在Map集合中的key值,非必需,缺省按照函数的所有参数组合作为key值,若自己配置需使用SpEL表达式,比如:@Cacheable(key = "#p0")
:使用函数第一个参数作为缓存的key值,更多关于SpEL表达式的详细内容可参考 官方文档 -
condition
:缓存对象的条件,非必需,也需使用SpEL表达式,只有满足表达式条件的内容才会被缓存,比如:@Cacheable(key = "#p0", condition = "#p0.length() < 3")
,表示只有当第一个参数的长度小于3的时候才会被缓存,若做此配置上面的AAA用户就不会被缓存,读者可自行实验尝试。 -
unless
:另外一个缓存条件参数,非必需,需使用SpEL表达式。它不同于condition
参数的地方在于它的判断时机,该条件是在函数被调用之后才做判断的,所以它可以通过对result进行判断。 -
keyGenerator
:用于指定key生成器,非必需。若需要指定一个自定义的key生成器,我们需要去实现org.springframework.cache.interceptor.KeyGenerator
接口,并使用该参数来指定。需要注意的是: 该参数与key
是互斥的 -
cacheManager
:用于指定使用哪个缓存管理器,非必需。只有当有多个时才需要使用 -
cacheResolver
:用于指定使用那个缓存解析器,非必需。需通过org.springframework.cache.interceptor.CacheResolver
接口来实现自己的缓存解析器,并用该参数指定。
-
除了这里用到的两个注解之外,还有下面几个核心注解:
-
@CachePut
:配置于函数上,能够根据参数定义条件来进行缓存,它与@Cacheable
不同的是,它每次都会真是调用函数,所以主要用于数据新增和修改操作上。它的参数与@Cacheable
类似,具体功能可参考上面对@Cacheable
参数的解析 -
@CacheEvict
:配置于函数上,通常用在删除方法上,用来从缓存中移除相应数据。除了同@Cacheable
一样的参数之外,它还有下面两个参数:allEntries beforeInvocation
代码示例
本文的相关例子可以查看下面仓库中的 chapter5-1
目录:
- Github: https://github.com/dyc87112/SpringBoot-Learning/
- Gitee: https://gitee.com/didispace/SpringBoot-Learning/
如果您觉得本文不错,欢迎 Star
支持,您的关注是我坚持的动力!
Recommend
-
14
Spring Boot 2.x基础教程:使用集中式缓存Redis 【福利时刻】关...
-
8
Spring Boot 2.x基础教程:使用EhCache缓存集群 【福利时刻】关...
-
14
在之前的《使用Sentinel实现接口限流》一文中,我们仅依靠引入Spring Cloud Alibaba对Sentinel的整合封装spring-cloud-starter-alibaba-sentinel,就完成了对所有Sprin...
-
8
Spring在3.1版本,就提供了一条基于注解的缓存策略,实际使用起来还是很丝滑的,本文将针对几个常用的注解进行简单的介绍说明,有需要的小伙伴可以尝试一下 本文主要知识点: @Cacheable: 缓存存在,则使用缓存;不存在,则执行方法,并将结...
-
7
【DB系列】SpringBoot缓存注解@Cacheable之自定义key策略及缓存失效时间指定 ...
-
10
上面一篇博文介绍了mybatis + xml配置文件的使用方式,在上文中介绍到,xml...
-
5
Python 注解方式实现缓存数据详解 e31845690mei · 2天之前 · 128 次点击 · 预计阅读时间 5 分...
-
5
有部分小伙伴反馈说前面基于注解的Spring中大量使用注解,由于对Java的注解不熟悉,有点难受。建议总结一篇的Java注解的基础知识,那么,它来了! 什么是注解? 如何定义注解 如何使用注解 如何获取注解信息
-
9
缓存在现代计算机系统中无处不在,各式各样硬件和软件的组合构成和管理着缓存,一个编写良好的计算机程序倾向于展示出良好的局部性。
-
10
作者最近在开发公司项目时使用到 Redis 缓存,并在翻看前人代码时,看到了一种关于 @Cacheable 注解的自定义缓存有效期的解决方案,感觉比较实用,因此作者自己拓展完善了一番后分享给各位。 Spring 缓存常规配置
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK