Tiven Wang
Wang Tiven December 07, 2017
425 favorite favorites
bookmark bookmark
share share

参考文章https://spring.io/guides/gs/caching/步骤运行起一个基本的 Spring Boot Cache 程序。

Run on Docker

使用 Maven 打包应用:

mvn package

在 Docker 容器里运行此 Spring Boot 应用程序:

docker run --rm -p 8080:8080 -v <your-project-root>/gs-caching/complete/target:/app java:8 java -D"java.security.egd"=file:/dev/./urandom -jar /app/gs-caching-0.1.0.jar

With Hibernate

Spring Boot Cache 的基本应用我们了解,接下来看看它如何与具体的 ORM 框架结合使用。这里使用 Spring Boot JPA 默认的 Hibernate 框架和常用的数据库 Postgresql

Local Postgres

首先使用 Docker 工具创建一个本地的 Postgres 数据库,如下面这句命令(默认创建用户名为 postgres 或者使用环境变量 POSTGRES_USER 指定用户名)

docker run --name try-spring-boot-psql \
    -p 5432:5432 \
    -e POSTGRES_DB=tsb \
    -e POSTGRES_PASSWORD=mysecretpassword \
    -d postgres

还是使用 Docker 容器来检查我们创建的数据库,可以看到 Database tsb 已被创建

$ docker run -it --rm --link try-spring-boot-psql:postgres postgres psql -h postgres -U postgres
Password for user postgres:
psql (9.6.3)
Type "help" for help.

postgres=# \l
 postgres  | postgres | UTF8     | en_US.utf8 | en_US.utf8 |
 template0 | postgres | UTF8     | en_US.utf8 | en_US.utf8 | =c/postgres          +
           |          |          |            |            | postgres=CTc/postgres
 template1 | postgres | UTF8     | en_US.utf8 | en_US.utf8 | =c/postgres          +
           |          |          |            |            | postgres=CTc/postgres
 tsb       | postgres | UTF8     | en_US.utf8 | en_US.utf8 |

Spring JPA

想要 Spring Boot 具有连接和访问数据库的能力,需要添加 JPA 和 Spring Data。和具体某个数据库的驱动程序。如下

<dependencies>
	...
	<dependency>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-data-jpa</artifactId>
	</dependency>
	<dependency>
		<groupId>org.postgresql</groupId>
		<artifactId>postgresql</artifactId>
	</dependency>
</dependencies>

要访问数据库势必要有些连接信息,如下配置在 application.yml 文件中数据源信息

spring:
  datasource:
    url: jdbc:postgresql://localhost:5432/tsb
    username: postgres
    password: mysecretpassword
  jpa:
    generate-ddl: true

其他的涉及 JPA 的代码修改这里不展开,请参考 Spring Boot 相关文档。其中 BookRepository 如下

public interface BookRepository extends CrudRepository<Book, Long> {

  @Cacheable("books")
  Book getByIsbn(String isbn);
}

CommandLineRunner 需要修改,增加创建数据的代码。为了防止残留数据影响下次运行,代码最后清空表数据。

public void run(String... args) throws Exception {
  logger.info(".... Saving books");
  logger.info("isbn-1234 -->" + bookRepository.save(new Book("isbn-1234", "isbn 1234")));
  logger.info("isbn-4567 -->" + bookRepository.save(new Book("isbn-4567", "isbn 4567")));

  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"));

  logger.info(".... Cleaning books");
  bookRepository.deleteAll();
}

为了显示程序是否访问了数据库还是从缓存里读取的数据,可以打开显示 SQL 的配置 show-sql: true

spring:
  ...
  jpa:
    ...
    show-sql: true

运行程序 mvn spring-boot:run 可以在后台看到打印信息如下

2017-12-11 17:56:28.236  INFO 22868 --- [           main] w.t.springbootguides.caching.AppRunner   : .... Saving books
Hibernate: select nextval ('hibernate_sequence')
Hibernate: insert into book (isbn, title, id) values (?, ?, ?)
2017-12-11 17:56:28.331  INFO 22868 --- [           main] w.t.springbootguides.caching.AppRunner   : isbn-1234 -->Book{isbn='isbn-1234', title='isbn 1234'}
Hibernate: select nextval ('hibernate_sequence')
Hibernate: insert into book (isbn, title, id) values (?, ?, ?)
2017-12-11 17:56:28.431  INFO 22868 --- [           main] w.t.springbootguides.caching.AppRunner   : isbn-4567 -->Book{isbn='isbn-4567', title='isbn 4567'}
2017-12-11 17:56:28.491  INFO 22868 --- [           main] w.t.springbootguides.caching.AppRunner   : .... Fetching books
Hibernate: select book0_.id as id1_0_, book0_.isbn as isbn2_0_, book0_.title as title3_0_ from book book0_ where book0_.isbn=?
2017-12-11 17:56:28.799  INFO 22868 --- [           main] w.t.springbootguides.caching.AppRunner   : isbn-1234 -->Book{isbn='isbn-1234', title='isbn 1234'}
Hibernate: select book0_.id as id1_0_, book0_.isbn as isbn2_0_, book0_.title as title3_0_ from book book0_ where book0_.isbn=?
2017-12-11 17:56:28.807  INFO 22868 --- [           main] w.t.springbootguides.caching.AppRunner   : isbn-4567 -->Book{isbn='isbn-4567', title='isbn 4567'}
2017-12-11 17:56:28.826  INFO 22868 --- [           main] w.t.springbootguides.caching.AppRunner   : isbn-1234 -->Book{isbn='isbn-1234', title='isbn 1234'}
2017-12-11 17:56:28.828  INFO 22868 --- [           main] w.t.springbootguides.caching.AppRunner   : isbn-4567 -->Book{isbn='isbn-4567', title='isbn 4567'}
2017-12-11 17:56:28.829  INFO 22868 --- [           main] w.t.springbootguides.caching.AppRunner   : isbn-1234 -->Book{isbn='isbn-1234', title='isbn 1234'}
2017-12-11 17:56:28.830  INFO 22868 --- [           main] w.t.springbootguides.caching.AppRunner   : isbn-1234 -->Book{isbn='isbn-1234', title='isbn 1234'}
2017-12-11 17:56:28.833  INFO 22868 --- [           main] w.t.springbootguides.caching.AppRunner   : .... Cleaning books
Hibernate: select book0_.id as id1_0_, book0_.isbn as isbn2_0_, book0_.title as title3_0_ from book book0_
Hibernate: delete from book where id=?
Hibernate: delete from book where id=?

可以看到在第一次读取数据的时候产生了 SQL 语句,也就是进行了数据库访问,但在之后就没有产生,也即它是从缓存里读取的数据。

Spring Cache

不难发现这里有个可以改进的地方,在创建数据后可以不需要再次去数据库里面查询,而是直接从缓存里面。 这就需要另外一个 Spring Cache 注解 @CachePut(cacheNames = "books", key = "#p0.isbn") ,其中指定缓存的名称和 Key 关键字段。

public interface BookRepository extends CrudRepository<Book, Long> {

  @Cacheable("books")
  Book getByIsbn(String isbn);

  @CachePut(cacheNames = "books", key = "#p0.isbn")
  Book save(Book isbn);
}

然后从下面运行日志可以看出,当查询数据时并没有产生 SQL ,而是从缓存中读取的:

2017-12-13 15:08:27.112  INFO 25900 --- [           main] w.t.springbootguides.caching.AppRunner   : .... Saving books
Hibernate: select nextval ('hibernate_sequence')
Hibernate: insert into book (isbn, title, id) values (?, ?, ?)
2017-12-13 15:08:27.250  INFO 25900 --- [           main] w.t.springbootguides.caching.AppRunner   : isbn-1234 -->Book{isbn='isbn-1234', title='isbn 1234'}
Hibernate: select nextval ('hibernate_sequence')
Hibernate: insert into book (isbn, title, id) values (?, ?, ?)
2017-12-13 15:08:27.261  INFO 25900 --- [           main] w.t.springbootguides.caching.AppRunner   : isbn-4567 -->Book{isbn='isbn-4567', title='isbn 4567'}
2017-12-13 15:08:27.262  INFO 25900 --- [           main] w.t.springbootguides.caching.AppRunner   : .... Fetching books
2017-12-13 15:08:27.267  INFO 25900 --- [           main] w.t.springbootguides.caching.AppRunner   : isbn-1234 -->Book{isbn='isbn-1234', title='isbn 1234'}
2017-12-13 15:08:27.271  INFO 25900 --- [           main] w.t.springbootguides.caching.AppRunner   : isbn-4567 -->Book{isbn='isbn-4567', title='isbn 4567'}
2017-12-13 15:08:27.278  INFO 25900 --- [           main] w.t.springbootguides.caching.AppRunner   : isbn-1234 -->Book{isbn='isbn-1234', title='isbn 1234'}
2017-12-13 15:08:27.280  INFO 25900 --- [           main] w.t.springbootguides.caching.AppRunner   : isbn-4567 -->Book{isbn='isbn-4567', title='isbn 4567'}
2017-12-13 15:08:27.285  INFO 25900 --- [           main] w.t.springbootguides.caching.AppRunner   : isbn-1234 -->Book{isbn='isbn-1234', title='isbn 1234'}
2017-12-13 15:08:27.286  INFO 25900 --- [           main] w.t.springbootguides.caching.AppRunner   : isbn-1234 -->Book{isbn='isbn-1234', title='isbn 1234'}
2017-12-13 15:08:27.288  INFO 25900 --- [           main] w.t.springbootguides.caching.AppRunner   : .... Cleaning books
Hibernate: select book0_.id as id1_0_, book0_.isbn as isbn2_0_, book0_.title as title3_0_ from book book0_
Hibernate: delete from book where id=?
Hibernate: delete from book where id=?

到此步骤的完整代码 Github

Cache Coherence in Distributed Systems

缓存一致性(Cache Coherence)问题存在于分布式应用程序系统中。 基于本机内存的缓存机制在分布式系统中都存在数据一致性问题,同时也有很多成熟的理论和解决方案,这里对此不展开讨论。

File:Cache_Coherency_Generic

分布式服务化系统一致性的“最佳实干”

Caching Frameworks

Memcached vs. Redis?

Redis

添加依赖包

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

运行一个 Docker Redis 实例: docker run --name try-spring-boot-redis -p 6379:6379 -d redis

配置 Redis 连接信息

spring:
  ...
  redis:
    host: localhost
    port: 6379
    pool:
      max-idle: 8
      min-idle: 0
      max-active: 8
      max-wait: 1

稍微修改一下代码,创建之前查询一下,当缓存中存在时不创建数据。

logger.info(".... Saving books");
if(null == bookRepository.getByIsbn("isbn-1234")) {
  logger.info("isbn-1234 -->" + bookRepository.save(new Book("isbn-1234", "isbn 1234")));
}
if(null == bookRepository.getByIsbn("isbn-4567")) {
  logger.info("isbn-4567 -->" + bookRepository.save(new Book("isbn-4567", "isbn 4567")));
}

重新运行 mvn spring-boot:run 输出日志跟之前的并没有什么不同,缓存和数据库中不存在,然后就创建了两条数据。 再次运行 mvn spring-boot:run 应该有再次创建数据的 SQL,因为之前运行后都会清空数据。 但由于现在使用了 Redis 作为独立于应用程序进程的后端服务,缓存并没有并清空,所以再次运行仍然能查询到缓存数据。

2017-12-14 10:43:57.087  INFO 23852 --- [           main] w.t.springbootguides.caching.AppRunner   : .... Saving books
2017-12-14 10:43:57.237  INFO 23852 --- [           main] w.t.springbootguides.caching.AppRunner   : .... Fetching books
2017-12-14 10:43:57.241  INFO 23852 --- [           main] w.t.springbootguides.caching.AppRunner   : isbn-1234 -->Book{isbn='isbn-1234', title='isbn 1234'}
2017-12-14 10:43:57.246  INFO 23852 --- [           main] w.t.springbootguides.caching.AppRunner   : isbn-4567 -->Book{isbn='isbn-4567', title='isbn 4567'}
2017-12-14 10:43:57.249  INFO 23852 --- [           main] w.t.springbootguides.caching.AppRunner   : isbn-1234 -->Book{isbn='isbn-1234', title='isbn 1234'}
2017-12-14 10:43:57.279  INFO 23852 --- [           main] w.t.springbootguides.caching.AppRunner   : isbn-4567 -->Book{isbn='isbn-4567', title='isbn 4567'}
2017-12-14 10:43:57.288  INFO 23852 --- [           main] w.t.springbootguides.caching.AppRunner   : isbn-1234 -->Book{isbn='isbn-1234', title='isbn 1234'}
2017-12-14 10:43:57.294  INFO 23852 --- [           main] w.t.springbootguides.caching.AppRunner   : isbn-1234 -->Book{isbn='isbn-1234', title='isbn 1234'}
2017-12-14 10:43:57.297  INFO 23852 --- [           main] w.t.springbootguides.caching.AppRunner   : .... Cleaning books
2017-12-14 10:43:57.353  INFO 23852 --- [           main] o.h.h.i.QueryTranslatorFactoryInitiator  : HHH000397: Using ASTQueryTranslatorFactory
Hibernate: select book0_.id as id1_0_, book0_.isbn as isbn2_0_, book0_.title as title3_0_ from book book0_

解决这个问题的办法,在 deleteAll 方法上加上清空缓存的操作

@CacheEvict(cacheNames = "books", allEntries = true)
void deleteAll();

重新运行两次程序,便可以看到创建了数据,证明缓存已被清空。

完整代码 Github

Memcached

EhCache

JSR-107 Cache

JCache is the Java caching API. It was defined by JSR107. It defines a standard Java Caching API for use by developers and a standard SPI (“Service Provider Interface”) for use by implementers.

Similar Posts

Comments

Back to Top