SpringBoot25-spingboot資料訪問-資料快取Cache
我們知道一個程式的瓶頸在於資料庫,我們也知道記憶體的速度是大大快於硬碟的速度的。當我們需要重複地獲取相同的資料的時候,我們一次又一次的請求資料庫或者遠端服務,導致大量的時間耗費在資料庫查詢或者遠端方法呼叫上,導致程式效能的惡化,這便是資料快取要解決的問題。
一,Spring 快取支援
Spring定義了org.springframework.cache.CacheManager和org.springframework.cache.Cache介面用來統一不同的快取技術。其中CacheManager是Spring提供的各種快取技術抽象介面,Cache介面包含快取的各種操作(增加,刪除,獲得快取,我們一般不會直接和此介面打交道)
1,Spring支援的CacheManager
針對不同的快取技術,需要實現不同的CacheManager,Spring定義瞭如下所示的CacheManager實現:
SimpleCacheManager:使用簡單的Collection來儲存快取,主要用來測試用途。
ConcurrentMapCacheManager:使用ConcurrentMap來儲存快取。
NoOpCacheManager:僅測試用途,不會實際儲存快取。
EhCacheCacheManager:使用EhCache作為快取技術。
GuavaCacheManager:使用Google Guava的GuavaCache作為快取技術。
HazelcastCacheManager:使用Hazelcast作為快取技術。
JCacheCacheManager:支援JCache標準的實現作為快取技術,如Apache Commons JCS
RedisCacheManager:使用Redis作為快取技術。
在我們使用任意一個實現的CacheManager的時候,需要註冊實現CacheManager的Bean,例如:
@Bean public EhCacheCacheManager cacheManager(CacheManager ehCacheCacheManager){ return new EhCacheCacheManager(ehCacheCacheManager); }
當然,每種快取技術都有很多的額外配置,但配置cacheManager是必不可少的
2,聲名式快取註解
Spring提供了4個註解來宣告快取規則(又是使用註解式的AOP的一個生動例子)。這四個註解如下:
@Cacheable:在方法執行前spring先檢視快取中是否有資料,如果有資料,則直接返回快取資料;若沒有資料,呼叫方法並將方法返回值放進快取。
@CachePut:無論怎樣,都會將方法的返回值放到快取中。@CachePut的屬性與@Cacheable保持一致。
@CacheEvict:將一條或多條資料從快取中刪除。
@Caching:可以通過@Caching註解組合多個註解策略在一個方法上。
@Cacheable,@Cacheable,@CacheEvict都有value屬性,指定的是要使用的快取名稱;key屬性指定的是資料在快取中的儲存的鍵。
3,開啟聲名式快取支援
開啟聲名式快取支援十分簡單,只需在配置類上使用@EnableCaching註解即可,如下:
package com.jack.springboot8cache.config;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Configuration;
/**
* create by jack 2017/10/9
*/
@Configuration
@EnableCaching
public class AppConfig {
}
二,Spring Boot的支援
在spring中使用快取技術的關鍵是配置CacheManager,而Spring Boot為我們自動配置了多個CacheManager的實現。
Spring Boot的CacheManager的自動配置放置在org.springframework.boot.autoconfigure.cache包中,如下所示:
通過上圖我們可以看出,Spring Boot為我們自動配置了EhCacheCacheConfiguration(使用EhCache),GenericCacheConfiguration(使用Collection),GuavaCacheConfiguration(使用Guava),HazelcastCacheConfiguration(使用Hazelcast),InfinispanCacheConfiguration(使用Infinispan),JCacheCacheConfiguration(使用JCache),NoOpCacheConfiguration(不使用儲存),RedisCacheConfiguration(使用Redis),SimpleCacheConfiguration(使用ConcurrentMap)。在不做任何額外配置的情況下,預設使用的是SimpleCacheConfiguration,即使用ConcurrentMapCacheManager。Spring boot支援以“spring.cache”為字首的屬性來配置快取:
spring.cache.type=#可選generic,ehcache,hazelcast,infinispan,jcache,redis,guava,simple,none
spring.cache.cache-names=#程式啟動時建立快取名稱
spring.cache.ehcache.config=#ehcache配置檔案地址
spring.cache.hazelcast.config=#hazelcast配置檔案地址
spring.cache.infinispan.config=#infinispan配置檔案地址
spring.cache.jcache.config=#jcache配置檔案地址
spring.cache.jcache.provider=#當多個jcache實現在類路徑中的時候,指定jcache實現
spring.cache.guava.spec=#guava specs
在Spring Boot環境下,使用快取技術只需在專案中匯入相關快取技術的依賴包,並在配置類上使用@EnableCaching開啟快取即可。
三,實戰
下面將以Spring Boot預設的ConcurrentMapCacheManager作為快取技術,演示@Cacheable,@CachePut,@CacheEvit,最後使用EhCache,Guava來替換快取技術。
1,新建Spring Boot專案
新建Spring Boot專案依賴為Cache,JPA,WEB,新增mysql驅動,pom.xml的配置檔案如下:
<?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.jack</groupId>
<artifactId>springboot8cache</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>springboot8cache</name>
<description>Demo project for Spring Boot</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.7.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-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--mysql連線驅動-->
<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/com.alibaba/fastjson -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.39</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-cache -->
<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,實體類
package com.jack.springboot8cache.entity;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
/**
* create by jack 2017/10/3
*/
//@Entity註解指明這是一個和資料庫表對映的實體類
@Entity
public class Person {
/**
* 主鍵id
* @Id註解指明這個屬性對映為資料庫的主鍵
* @GeneratedValue定義主鍵生成的方式,下面採用的是mysql的自增屬性
*/
@Id
@GeneratedValue(strategy= GenerationType.AUTO)
private Integer id;
/**
* 姓名
*/
private String name;
/**
* 年齡
*/
private Integer age;
/**
* 地址
*/
private String address;
public Person() {
super();
}
public Person(Integer id, String name, Integer age, String address) {
super();
this.id = id;
this.name = name;
this.age = age;
this.address = address;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
}
3,實體類Repository
package com.jack.springboot8cache.dao;
import com.jack.springboot8cache.entity.Person;
import org.springframework.data.jpa.repository.JpaRepository;
/**
* create by jack 2017/10/8
* 實體類
*/
public interface PersonRepository extends JpaRepository<Person,Integer> {
}
4,業務服務
1)介面:
package com.jack.springboot8cache.service;
import com.jack.springboot8cache.entity.Person;
/**
* create by jack 2017/10/9
*/
public interface DemoService {
Person save(Person person);
void remove(Integer id);
Person findOne(Person person);
}
2)實現類
package com.jack.springboot8cache.impl;
import com.jack.springboot8cache.dao.PersonRepository;
import com.jack.springboot8cache.entity.Person;
import com.jack.springboot8cache.service.DemoService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
/**
* create by jack 2017/10/9
*/
@Service
public class DemoServiceImpl implements DemoService {
@Autowired
private PersonRepository personRepository;
/**
* 注意:如果沒有指定key,則方法引數作為key儲存到快取中
*/
/**
*@CachePut快取新增的或更新的資料到快取,其中快取的名稱為people,資料的key是person的id
* @param person
* @return
*/
@CachePut(value = "people",key="person.id")
@Override
public Person save(Person person) {
Person p = personRepository.save(person);
System.out.println("為id,key為:"+p.getId()+"資料做了快取");
return p;
}
/**
* @CacheEvict從快取people中刪除key為id的資料
* @param id
*/
@CacheEvict(value = "people")
@Override
public void remove(Integer id) {
System.out.println("刪除了id,key為"+id+"的資料快取");
personRepository.delete(id);
}
/**
* @Cacheable快取key為person的id資料到快取people中
* @param person
* @return
*/
@Cacheable(value = "people",key = "person.id")
@Override
public Person findOne(Person person) {
Person p = personRepository.findOne(person.getId());
System.out.println("為id,key為:"+p.getId()+"資料做了快取");
return p;
}
}
注意:如果沒有指定key,則方法引數作為key儲存到快取中
5,控制器
package com.jack.springboot8cache.controller;
import com.jack.springboot8cache.entity.Person;
import com.jack.springboot8cache.service.DemoService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* create by jack 2017/10/9
*/
@RestController
@RequestMapping("cache")
public class CacheController {
@Autowired
private DemoService demoService;
@RequestMapping("/put")
public Person put(Person person){
return demoService.save(person);
}
@RequestMapping("/evit")
public String evit(Integer id){
demoService.remove(id);
return "ok";
}
@RequestMapping("/cacheable")
public Person cacheable(Person person){
return demoService.findOne(person);
}
}
6,開啟快取支援
package com.jack.springboot8cache;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;
@SpringBootApplication
@EnableCaching
public class Springboot8cacheApplication {
public static void main(String[] args) {
SpringApplication.run(Springboot8cacheApplication.class, args);
}
}
7,執行
當我們對資料庫做了快取以後,資料的獲得將從快取中得到,而不是從資料庫中得到。當前的資料庫的資料情況如下所示:
我們在每次執行測試情況下,都重啟了應用程式。
1)測試@Cacheable
第一次訪問http://localhost:9090/cache/cacheable?id=85,第一次將呼叫方法查詢資料庫,並將資料放到快取people中。
此時控制檯輸出如下:
2017-10-09 23:29:05.877 INFO 2468 --- [nio-9090-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring FrameworkServlet 'dispatcherServlet'
2017-10-09 23:29:05.878 INFO 2468 --- [nio-9090-exec-1] o.s.web.servlet.DispatcherServlet : FrameworkServlet 'dispatcherServlet': initialization started
2017-10-09 23:29:05.939 INFO 2468 --- [nio-9090-exec-1] o.s.web.servlet.DispatcherServlet : FrameworkServlet 'dispatcherServlet': initialization completed in 61 ms
Hibernate: select person0_.id as id1_0_0_, person0_.address as address2_0_0_, person0_.age as age3_0_0_, person0_.name as name4_0_0_ from person person0_ where person0_.id=?
為id,key為:85資料做了快取
頁面輸出如下:
再次訪問http://localhost:9090/cache/cacheable?id=85,此時控制檯沒有再次輸出hibernate的查詢語句,以及“為id,key為:85資料做了快取”字樣,表示沒有呼叫這個方法,頁面直接從資料快取中獲得資料,頁面輸出結果如下:
2)測試@CachePut
訪問http://localhost:9090/cache/put?name=jack8&age=99&address=深圳深圳,此時控制檯輸出如下:
2017-10-09 23:37:10.823 INFO 1212 --- [nio-9090-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring FrameworkServlet 'dispatcherServlet'
2017-10-09 23:37:10.824 INFO 1212 --- [nio-9090-exec-1] o.s.web.servlet.DispatcherServlet : FrameworkServlet 'dispatcherServlet': initialization started
2017-10-09 23:37:10.893 INFO 1212 --- [nio-9090-exec-1] o.s.web.servlet.DispatcherServlet : FrameworkServlet 'dispatcherServlet': initialization completed in 69 ms
Hibernate: insert into person (address, age, name) values (?, ?, ?)
為id,key為:86資料做了快取
頁面輸出如下:
我們再次訪問:http://localhost:9090/cache/cacheable?id=87,控制檯無輸出,從快取直接獲得資料,頁面顯示如上。
3)測試@CacheEvit
訪問:http://localhost:9090/cache/cacheable?id=87,為id為87的資料做快取,再次訪問http://localhost:9090/cache/cacheable?id=87,確認資料已經從快取中獲取。
訪問:http://localhost:9090/cache/evit?id=87,從快取中刪除key為87的快取資料:
刪除了id,key為87的資料快取
Hibernate: select person0_.id as id1_0_0_, person0_.address as address2_0_0_, person0_.age as age3_0_0_, person0_.name as name4_0_0_ from person person0_ where person0_.id=?
Hibernate: delete from person where id=?
再次訪問:http://localhost:9090/cache/cacheable?id=87,觀察控制檯,資料已經從資料庫中刪除了,查詢快取,發現空指標異常了,查詢失敗
四,切換快取技術
切換快取技術除了移入相關依賴包或者配置以外,使用方式和上面的實戰例子儲存一致。下面講解Spring Boot下EhCache和Guava作為快取技術的方式,其餘快取技術也是類似的方式。
1,EhCahce
當我們需要使用EhCache作為快取技術的時候,我們只需要在pom.xml中新增EhCache的依賴即可:
<!-- https://mvnrepository.com/artifact/net.sf.ehcache/ehcache -->
<dependency>
<groupId>net.sf.ehcache</groupId>
<artifactId>ehcache</artifactId>
<version>2.10.4</version>
</dependency>
EhCache所需的配置檔案ehcache.xml只需放在類路徑下,Spring Boot會自動掃描,例如:
<?xml version="1.0" encoding="UTF-8">
<ehcache>
<cache name="people" maxElementsInMemory="1000"/>
</ehcache>
Spring Boot會給我們自動配置EhcacheCacheManager的Bean。
2,Guava
當我們需要使用Guava作為快取技術的時候,我們只需要在pom.xml中增加Guava的依賴即可:
<!-- https://mvnrepository.com/artifact/com.google.guava/guava -->
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>23.0</version>
</dependency>
Spring Boot會給我們自動配置GuavaCacheManager這個Bean。
3,Redis
使用Redis,只需新增下面的依賴即可:
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-data-redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<version>1.5.7.RELEASE</version>
</dependency>
Spring Boot將會為我們自動配置RedisCacheManager以及RedisTemplate的Bean。