1、前言,我们为什么要用缓存?
①普通API接口或应用,当访问量较小时,可以不考虑缓存。但大型API接口、应用,当QPS达到2000左右时(QPS一般指每秒查询率,即每秒2000次访问),MySQL数据库即开始报警,如果不使用缓存,系统很可能会出现延迟、拒绝、服务器资源占满,最后崩溃宕机。
②缓存无非就是不想频繁去读数据库、不想频繁去调用接口,主要实现两个用途:高性能、高并发。一个商业级API接口,当加入缓存技术,可以轻松实现几万甚至十几万QPS。
③今天,我们就在上一讲利用Spring Data REST+JPA实现RESTFul API接口的基础上,添加Ehcache分布式缓存,打造商业级高并发、高性能API接口。
2、Ehcache分布式缓存简介
①EhCache是一个广泛使用的纯Java进程内缓存框架,在Java开发领域久负盛名,它快速精干、部署简单、效率高门槛低、操作简便,但同时功能较少,可扩展性和集群应用较弱。
②简单来说,Ehcache是一个轻量级缓存容器,在接口方面支持RESTFul和SOAP API。
3、项目基本框架
-
Spring-boot 2.5+
-
JDK 11+或者JDK13+
-
数据库MySQL 8.0+
-
Ehcache 3+,详细了解可访问
https://www.ehcache.org
-
接口测试工具Postman
4、添加项目依赖
①关于如何利用Spring Data REST+JPA创建RESTFul API接口,可以参考
https://blog.csdn.net/m0_59562547/article/details/119203351
②加入Ehcache缓存依赖
<!--ehcache依赖-->
<dependency>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache</artifactId>
</dependency>
<!--spring cache依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
5、添加缓存配置文件
我们在resources资源目录下面创建ehcache.xml缓存配置文件。
<?xml version="1.0" encoding="UTF-8"?>
<ehcache>
<!-- 磁盘缓存位置 -->
<diskStore path="java.io.tmpdir/cache"/>
<!--默认缓存 600秒强制过期或300秒内无人访问缓存过期-->
<defaultCache
maxElementsInMemory="10000"
eternal="false"
timeToIdleSeconds="300"
timeToLiveSeconds="600"
overflowToDisk="false"
diskPersistent="false"
diskExpiryThreadIntervalSeconds="120"
/>
<!--业务缓存 300秒强制过期或180秒内无人访问缓存过期-->
<cache name ="book_cache"
maxElementsInMemory="10000"
eternal="false"
timeToIdleSeconds="180"
timeToLiveSeconds="300"
overflowToDisk="false"
diskPersistent="false"
diskExpiryThreadIntervalSeconds="600"
/>
</ehcache>
这是一个常规的Ehcache配置文件,提供两种缓存策略,一种是默认缓存,一种是book-cache业务缓存,业务缓存可自定义名字。
Ehcache缓存属性说明

这里着重讲一下timeToIdleSeconds
发呆秒数和timeToLiveSeconds
总存活秒数的理解:
-
timeToIdleSeconds
表示在多少时间内缓存无访问则过期,如果有访问则不会过期。 -
timeToLiveSeconds
表示该缓存总存活的时间,如达到该时间则会过期。 -
timeToLiveSeconds
必须大于timeToIdleSeconds
才有意义。
6、开启缓存
在项目入口添加上@EnableCaching
开启缓存。
package com.example.demohelloworld;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;
@SpringBootApplication
//开启缓存
@EnableCaching
public class DemohelloworldApplication {
public static void main(String[] args) {
SpringApplication.run(DemohelloworldApplication.class, args);
}
}
7、为JPA RESTFul接口类BookRepository添加缓存注解实现缓存功能
Ehcache相关注解说明

代码如下:
package com.example.demohelloworld.restful;
import org.springframework.cache.annotation.CacheConfig;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.data.rest.core.annotation.RepositoryRestResource;
import org.springframework.data.rest.core.annotation.RestResource;
import javax.transaction.Transactional;
import java.util.List;
import java.util.Optional;
//指明使用缓存的名称,这个配置可选,若不使用@CacheConfig,则直接在@Cacheable中指明即可
@CacheConfig(cacheNames = {"book_cache"})
@RepositoryRestResource(path = "bs",collectionResourceRel = "bs",itemResourceRel = "b")
public interface BookRepository extends JpaRepository<Book,Integer> {
//根据作者名字来查询,对作者名字进行缓存
@Cacheable(key = "#author")
@RestResource(path = "author" ,rel="author")
List<Book> findAllByAuthorContains(@Param("author") String author);
//根据书名来查询,对书名进行缓存
@Cacheable(key = "#name")
@RestResource(path = "name" ,rel="name")
Book findByNameEquals(@Param("name") String name);
//对排序进行缓存
@Cacheable(key = "#sort")
List<Book> findAll(Sort sort);
//对页码进行缓存
@Cacheable(key = "#pageable")
Page<Book> findAll(Pageable pageable);
//Optional 类是一个可以为null的容器对象,调用get()方法会返回该对象,它可以保存类型T的值,不用显式进行空值检测,对ID进行缓存
@Cacheable(key = "#id")
Optional<Book> findById(Integer id);
//@CachePut一般用户更新方法,每次执行时不检查缓存,直接执行方法,然后将方法结果缓存,如果该KEY已经被缓存起来了,则会覆盖之前的缓存,对实体类进行缓存
@CachePut(key = "#entity")
<S extends Book> S save(S entity);
//@CacheEvict一般用于删除方法,表示移除一个KEY对应缓存
@CacheEvict(key = "#id")
void deleteById(Integer id);
//新增书Get请求
//@CacheEvict(beforeInvocation = true)表示在方法执行之前清空缓存,默认false表示在方法执行之后清空缓存
@CacheEvict(beforeInvocation = true)
@Transactional
@Modifying(clearAutomatically = true)//表示数据表和实体缓存同步更新
@Query(value = "insert into t_book(name,author) values (?1,?2)",nativeQuery = true)
int addBook(@Param("name") String name,@Param("author") String author);//返回值为添加成功数量
//@CacheEvict(allEntries = true)表示将所有的缓存全部清空,默认false
//根据id更新书Get请求
@CacheEvict(allEntries = true)
@Transactional
@Modifying(clearAutomatically = true)//表示数据表和实体缓存同步更新
@Query(value = "update t_book set name=?1,author=?2 where id=?3",nativeQuery = true)
int updateBook(@Param("name") String name,@Param("author") String author,@Param("id") Integer id);//返回值为更新成功数量
}
8、启动项目,利用Postman进行测试
我们创建一个GET请求,输入http://localhost:8080/bs/2
,点击Send,服务器会返回相应的Book值。
服务器后端会出现Hibernate:的提示,表明我们执行optional<Book> findById(Integer id)
方法。

当我们在180秒内(缓存保留时间)点击Send再次发送这个GET请求的时候,服务器后端则不会出现任何消息,表明optional<Book> findById(Integer id)
方法没有被执行,而是直接从缓存value中取值并返回给前端了。我们的缓存生效了!

同理,大家还可以测试一下PUT更新数据请求、DELETE删除数据请求,了解相应方法的执行情况和缓存的使用情况。
①更新数据:不检查缓存,直接执行方法并将最新的执行结果缓存起来。(更新数据+更新缓存)
②删除数据:直接执行方法并移除相对应的缓存,(删除数据+删除对应缓存)。如果有allEntries = true
属性,则会清空所有缓存。
9、总结
1、缓存使API接口实现了高性能+高并发。
2、对JPA打造的RESTFul接口来说,仅需@EnableCaching
开启缓存,在对应方法加上@Cacheable
等注解就可以实现缓存功能了,可以说非常简单方便。
3、除了Ehcache缓存外,Redis等同样可以实现缓存功能,大家可以根据实际需要使用。
构建高质量的技术交流社群,欢迎从事编程开发、技术招聘HR进群,也欢迎大家分享自己公司的内推信息,相互帮助,一起进步!
文明发言,以
交流技术
、职位内推
、行业探讨
为主
广告人士勿入,切勿轻信私聊,防止被骗

原文始发于微信公众号(Java知音):利用 Ehcache 分布式缓存,轻松打造商业级高并发、高性能API接口
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/148928.html