寻寻觅觅找教程,无疑是一个吃力不讨好的差事,有写的不好的,也有写的好的,但终究有一点那就是不系统。而对于一门技术权威介绍的书籍,又不知道等几年之后才能出版,看着密密麻麻的英文官方文档,特么的一口又一口的老血就喷到了屏幕上。再喷,也得学啊。学啊学啊,又陷入到上面的循环之中。老话说的好,要想知道河的深浅,就得亲自下河摸鱼。SO,本文很纯粹的介绍了主题cache的一小部分,理论不多,重在通过项目给大家和我自己一个入门级别的引导,方便日后查看。
为了不浪费大家找答案的时间,突然有所感慨,记录在上面。重点看最后一句。
代码验证环境:idea。
场景
我们经常需要通过id来查询实体,正常不用的缓存的情况是:请求URL,查询数据库,返回结果。使用缓存的情况:请求URL-查询缓存-缓存中有直接返回,缓存中无查询数据库-返回结果。
验证步骤
1.使用idea 新建springboot项目pom文件如下:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example.testcache</groupId>
<artifactId>democache</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>democache</name>
<description>Demo project for Spring Boot</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.3.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
2.我们要模拟查询数据的过程需要DAO 层,service层提供服务。涉及的几个类如下:
实体类book:
public class Book {
private String isbn;
private String title;
public Book(String isbn, String title) {
this.isbn = isbn;
this.title = title;
}
public String getIsbn() {
return isbn;
}
public void setIsbn(String isbn) {
this.isbn = isbn;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
@Override
public String toString() {
return "Book{" +
"isbn='" + isbn + '\'' +
", title='" + title + '\'' +
'}';
}
}
查询接口BookRepository:
public interface BookRepository {
Book getByIsbn(String isbn);
}
接口实现SimpleBookRepository:
@Component
public class SimpleBookRepository implements BookRepository {
@Override
public Book getByIsbn(String isbn) {
simulateSlowService();
return new Book(isbn, "Some book");
}
private void simulateSlowService() {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
throw new IllegalStateException(e);
}
}
}
以上代码在查询的时候加入了3秒的延迟,你可以随时修改它。
准备工作做好之后我们分别验证一下两种情况。
无缓存的情况
众所周知运行springboot的方式很简单,启动类:
@SpringBootApplication
public class DemocacheApplication {
public static void main(String[] args) {
SpringApplication.run(DemocacheApplication.class, args);
}
}
@SpringBootApplication注解巴拉巴拉。就不解释了。
没有建测试类,我们通过实现CommandLineRunner接口的方式注入bookRepository,去掉用查询方法。
@Component
public class AppRunner implements CommandLineRunner {
private static final Logger logger = LoggerFactory.getLogger(AppRunner.class);
private final BookRepository bookRepository;
public AppRunner(BookRepository bookRepository) {
this.bookRepository = bookRepository;
}
@Override
public void run(String... strings) throws Exception {
logger.info(".... Fetching books");
logger.info("isbn-1234 -->" + bookRepository.getByIsbn("isbn-1234"));
logger.info("isbn-4567 -->" + bookRepository.getByIsbn("isbn-4567"));
logger.info("isbn-1234 -->" + bookRepository.getByIsbn("isbn-1234"));
logger.info("isbn-4567 -->" + bookRepository.getByIsbn("isbn-4567"));
logger.info("isbn-1234 -->" + bookRepository.getByIsbn("isbn-1234"));
logger.info("isbn-1234 -->" + bookRepository.getByIsbn("isbn-1234"));
}
}
输入如下:
2017-05-27 10:58:55.800 INFO 8233 — [ main] com.example.testcache.AppRunner : …. Fetching books
2017-05-27 10:58:58.806 INFO 8233 — [ main] com.example.testcache.AppRunner : isbn-1234 –>Book{isbn=’isbn-1234’, title=’Some book’}
2017-05-27 10:59:01.809 INFO 8233 — [ main] com.example.testcache.AppRunner : isbn-4567 –>Book{isbn=’isbn-4567’, title=’Some book’}
2017-05-27 10:59:04.812 INFO 8233 — [ main] com.example.testcache.AppRunner : isbn-1234 –>Book{isbn=’isbn-1234’, title=’Some book’}
2017-05-27 10:59:07.813 INFO 8233 — [ main] com.example.testcache.AppRunner : isbn-4567 –>Book{isbn=’isbn-4567’, title=’Some book’}
从输出的日志来看,大概每3秒检索一次。
启用缓存
修改SimpleBookRepository.java类的检索方法:
@Component
public class SimpleBookRepository implements BookRepository {
@Override
@Cacheable("books")
public Book getByIsbn(String isbn) {
simulateSlowService();
return new Book(isbn, "Some book");
}
private void simulateSlowService() {
try {
Thread.sleep(3000L);
} catch (InterruptedException e) {
throw new IllegalStateException(e);
}
}
}
除此之外,还需要在启动类上加上@EnableCaching注解:
@SpringBootApplication
@EnableCaching
public class DemocacheApplication {
public static void main(String[] args) {
SpringApplication.run(DemocacheApplication.class, args);
}
}
@EnableCaching小讲
@EnableCaching注解会检查spring bean的公共方法上是否存在缓存注解,如果找到这样的注释,则会自动创建代理来拦截方法调用并相应地处理缓存行为。
能被它监测到的注解有我们很熟的 Cacheable, CachePut 和 CacheEvict等。
Spring Boot自动配置一个合适的缓存管理器,我们的例子没有使用指定的缓存库例如EHcache,所以我们使用的是spring自己的基于java.util.concurrent.ConcurrentHashMap实现的缓存管理器(该功能是从Spring3.1开始提供)。
再次启动应用我们看到结果:
2017-05-27 11:28:21.843 INFO 8318 — [ main] com.example.testcache.AppRunner : …. Fetching books
2017-05-27 11:28:24.855 INFO 8318 — [ main] com.example.testcache.AppRunner : isbn-1234 –>Book{isbn=’isbn-1234’, title=’Some book’}
2017-05-27 11:28:27.860 INFO 8318 — [ main] com.example.testcache.AppRunner : isbn-4567 –>Book{isbn=’isbn-4567’, title=’Some book’}
2017-05-27 11:28:27.861 INFO 8318 — [ main] com.example.testcache.AppRunner : isbn-1234 –>Book{isbn=’isbn-1234’, title=’Some book’}
2017-05-27 11:28:27.861 INFO 8318 — [ main] com.example.testcache.AppRunner : isbn-4567 –>Book{isbn=’isbn-4567’, title=’Some book’}
2017-05-27 11:28:27.862 INFO 8318 — [ main] com.example.testcache.AppRunner : isbn-1234 –>Book{isbn=’isbn-1234’, title=’Some book’}
2017-05-27 11:28:27.862 INFO 8318 — [ main] com.example.testcache.AppRunner : isbn-1234 –>Book{isbn=’isbn-1234’, title=’Some book’}
会发现,只要请求已访问过的实体,会立即从缓存中返回结果。