程序性能的瓶頸之一我們知道是數據庫。而內存的速度是遠遠大于數據庫的速度的。如果我們需要重復的獲取相同的數據的時候,我們就需要一次又一次的請求數據或者遠程服務。導致大量的時間耗費在數據庫查詢或者遠程方法調用上。因此,我們可以理由緩存來提升我們程序的性能。
Spring的緩存支持
Spring 定義了org.springframework.cache.CacheManager和org.springframework.cache.Cache接口來統一不同的緩存技術。其中CacheManager是Spring提供的各種緩存技術抽象接口。Cache接口包含緩存的各種操作(增加、刪除、獲得緩存,我們一般不會直接與這個接口打交道)。
Spring支持的CacheManager
- SimpleCacheManager,使用了簡單的Collection來存儲緩存,主要用于測試
- ConcurrentMapCacheManager,使用ConcurrentMap來存儲緩存
- NoOpCacheManager,僅用于測試,不會實際存儲緩存
- EnCacheCacheManager,使用EnCache作為緩存技術
- GuavaCacheManager,使用Google Guava的GuavaCache作為緩存技術
- HazelcastCacheManager,使用Hazelcast作為緩存技術
- JCacheCacheManager,支持Jcache(JSR-107)標準的實現作為緩存技術,如Appache Commons JCS
- RedisCacheManager,使用Redis作為緩存技術
在我們實現任意一個實現了CacheManager的時候,需要注冊實現CacheManager的Bean。例如
<bean id="cacheManager" class="org.springframework.cache.support.SimpleCacheManager">
<property name="caches">
<set>
<bean class="org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean">
<property name="name" value="default"/>
</bean>
<bean class="org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean">
<property name="name" value="accountCache"/>
</bean>
</set>
</property>
</bean>
當然每一個緩存技術可能都需要額外的配置,但配置cacheManager是必不可少的。
聲明式緩存注解
Spring提供了四個注解來聲明緩存規則:
- @Cacheable,在方法執行前,Spring先去查看緩存中是否有數據。如果有數據,則直接返回遠程數據,若沒有數據,調用方法并將方法返回值放進緩存
- @CachePut,無論怎樣,都會將方法的返回值放進緩存中,@CachePut和@Cacheable的屬性一致
- @CacheEvict,將一條或者多條數據從緩存中刪除
- Caching,可以通過@Caching注解組合多個注解策略在同一個方法上
@Cacheable,@CachePut,@CacheEvict都有Value屬性,指定的是要使用的緩存名稱,key屬性指定的是數據在緩存中存儲的鍵。
開啟聲明式緩存支持
開啟聲明式緩存支持非常簡單,只需在緩存類中加入@EnableCaching注解即可
SpringBoot的緩存支持
在Spring中使用緩存的關鍵是配置CacheManager,而SpringBoot為我們配置了多個CacheManager的實現。即上述提到的EnCacheManager等實現例如EnCacheCacheConfiguration、RedisCacheConfiguration(使用Redis),在不做任何額外配置的情況下。默認使用的是SimpleCacheConfiguration。即使用 ConcurrentMapCacheManager。SpringBoot支持以spring.cache為前綴來在屬性文件中配置屬性。例如:
spring.cache.ehcache.config=#
在SpringBoot環境下使用緩存技術只需在項目中導入相關緩存技術的依賴包,并在配置類中使用EnableCaching開啟緩存即可。
SpringBoot緩存實戰
做好前期的準備
Person類
@Entity
public class Person {
@Id
@GeneratedValue
private Long id;
private String name;
private Integer age;
private String address;
public Person() {
super();
}
public Person(Long id, String name, Integer age, String address) {
super();
this.id = id;
this.name = name;
this.age = age;
this.address = address;
}
public Long getId() {
return id;
}
public void setId(Long 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;
}
}
Dao層的Repository
public interface PersonRepository extends JpaRepository<Person,Long> {
}
Service層以及他的實現類
DemoService
public interface DemoService {
public Person save(Person person);
public void remove(Long id);
public Person findOne(Person person);
}
DemoServiceImpl
@Service
public class DemoServiceImpl implements DemoService {
@Autowired
PersonRepository personRepository;
@Override
@CachePut(value = "people",key = "#person.id")
public Person save(Person person) {
Person p = personRepository.save(person);
System.out.println("為id,Key為"+p.getId()+"的數據做了緩存");
return p;
}
@Override
@CacheEvict(value = "people")
public void remove(Long id) {
System.out.println("刪除了id,Key為"+id+"的數據緩存");
personRepository.delete(id);
}
@Override
@Cacheable(value = "people",key = "#person.id")
public Person findOne(Person person) {
Person p = personRepository.findOne(person.getId());
System.out.println("為id,Key為"+p.getId()+"的數據做了緩存");
return p;
}
}
@CachePut緩存新增的或者更新的數據到緩存,其中緩存名稱為people,數據的key是person 的id
@CacheEvict從緩存中刪除key 為id的數據
@Cacheable緩存key為person的id數據到people中。
這里特別說明下。如果沒有指定key。則方法參數作為key保存到緩存中
Controller層
@RestController
public class CacheController {
@Autowired
DemoService demoService;
@RequestMapping("/put")
public Person put(Person person){
return demoService.save(person);
}
@RequestMapping("/able")
public Person cacheable(Person person){
return demoService.findOne(person);
}
@RequestMapping("/evict")
public String evict(Long id){
demoService.remove(id);
return "OK";
}
}
在SpringBoot的入口文件中開啟緩存支持
@SpringBootApplication
@EnableCaching
public class SpringbootCacheApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootCacheApplication.class, args);
}
}
properties配置文件
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/springboot
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.username=root
spring.datasource.password=123456
#1
spring.jpa.hibernate.ddl-auto=update
#2
spring.jpa.show-sql=true
#3
spring.jackson.serialization.indent_output=true
運行
當我們對數據做緩存之后,數據的獲得將從緩存中得到而不是從數據庫中得到
數據庫初始情況如圖
測試Cacheable
第一次訪問http://localhost:8080/able?id=1的時候,將調用方法 查詢數據庫。并將數據放到緩存中此時控制臺輸出
同時網頁顯示
再次訪問http://localhost:8080/able?id=1。此時控制臺沒有任何輸出。表示沒有調用這個方法。頁面直接從緩存中獲取數據。
測試CachePut
訪問http://localhost:8080/put?name=zzzz&age=23&address=shanghai。此時可見控制臺輸出如圖
頁面輸出
再次訪問http://localhost:8080/put?name=zzzz&age=23&address=shanghai。控制臺無輸出,從緩存直接獲取數據。界面與上圖相同
測試CacheEvict
訪問http://localhost:8080/able?id=1。為id為1的數據做緩存,再次訪問http://localhost:8080/able?id=1。確認數據已經是從緩存中獲取。訪問http://localhost:8080/evict?id=1會看到控制臺打印如下信息。
切換緩存技術
切換緩存技術只需在pom.xml中引入相關的依賴即可。當然如果需要進一步配置則需要進行一定的配置
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-redis</artifactId>
</dependency>