一、前言
Spring Cache 對 Cahce 進行了抽象,提供了 @Cacheable、@CachePut、@CacheEvict 等注解。Spring Boot 應用基於 Spring Cache,既提供了基於內存實現的緩存管理器,可以用於單體應用系統,也集成了 EhCache、Redis 等緩存服務器,可以用於大型系統或者分布式系統。
二、關於 Cache
應用系統需要通過 Cache 來緩存不經常改變的數據以提高系統性能和增加系統吞吐量,避免直接訪問數據庫等低速的存儲系統。緩存的數據通常存放在訪問速度更快的內存中或者是低延遲存取的存儲器、服務器上。應用系統緩存通常有以下作用:
緩存 Web 系統的輸出,如偽靜態頁面;
緩存系統中不經常改變的業務數據,如用戶權限、字典數據、配置信息等。
三、使用 Spring Cache
Spring Boot 自帶了基於 ConcurrentHashMap 的 Simple 緩存管理器,也集成了 EhCache、Redis 等緩存管理器。Spring Boot 應用通過注解的方式使用統一的緩存,只需要在方法上使用緩存注解即可,其緩存的具體實現依賴於選擇的目標緩存管理器。本節先介紹 Spring Boot 自帶的 Simple 緩存,然后再介紹 EhCahce 和 Redis 緩存。需要注意的是,Simple 只適合單體應用或者開發環境使用,再或者是一個小微系統,通常應用為分布式應用時,則需要集成 EhCache、Redis 等分布式緩存管理器。
3.1 添加 pom 依賴
集成 Spring Cache,只需要在 pom 中添加以下依賴:
<!-- Spring Cache --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-cache</artifactId> </dependency>
3.2 配置緩存管理器
在 application.properties 中配置目標緩存管理器:
spring.cache.type=SIMPLE
3.3 開啟緩存功能
在啟動類上添加 @EnableCaching 注解,開啟緩存功能:
package com.light.springboot; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cache.annotation.EnableCaching; /** * 該注解指定項目為springboot,由此類當作程序入口,自動裝配web依賴的環境 * @author Administrator * */ @SpringBootApplication @EnableCaching public class SpringbootApplication { public static void main(String[] args) { SpringApplication.run(SpringbootApplication.class, args); } }
3.4 常用 Spring Cache 緩存注解
一旦配置好 Spring Boot 緩存,就可以在 Spring 管理的 Bean 中使用緩存注解,通常可以直接放在 Service 類上。
@CacheConfig,在類上設置當前緩存的一些公共設置,比如緩存名稱;
@Cacheable,作用在方法上,觸發緩存讀取操作。表明該方法的結果是可以緩存的,如果緩存存在,則目標方法不會被調用,直接取出緩存。可以為方法聲明多個緩存,如果至少有一個緩存有緩存項,則其緩存項將被返回;
@CacheEvice,作用在方法上,觸發緩存失效操作,刪除緩存項或者清空緩存;
@CachePut,作用在方法上,觸發緩存更新操作,添加該注解后總是會執行方法體,並且使用返回的結果更新緩存,同 Cacheable 一樣,支持 condition、unless、key 選項,也支持 KeyGenerator;
@Caching,作用在方法上,綜合上面的各種操作,在有些場景上,調用業務會觸發多種緩存操作。
通常清空下,直接使用 SpEL 表達式來指定緩存項的 Key 比自定義一個 KeyGenerator 更為簡單。
3.5 定義 Service 接口
package com.light.springboot.service; import com.light.springboot.entity.User; public interface UserService { /** * 新增用戶 */ public User insertUser(User user); /** * 通過id查找單個用戶 */ public User getUserById(Integer id); /** * 通過id修改單個用戶 */ public User updateUserById(User user); /** * 通過id刪除單個用戶 */ public void deleteUserById(Integer id); }
3.6 編寫上面接口的實現類 UserServiceImpl
此處持久層技術使用 Spring Data JPA 實現,調用 UserRepository 接口。
package com.light.springboot.service.impl;import org.springframework.beans.factory.annotation.Autowired; 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.stereotype.Service; import com.light.springboot.dao.UserRepository; import com.light.springboot.entity.User; import com.light.springboot.service.UserService; @CacheConfig(cacheNames = "user") @Service public class UserServiceImpl implements UserService { @Autowired private UserRepository userRepository; /** * 新增用戶 */ @CachePut(key = "#user.id") public User insertUser(User user) { user = this.userRepository.save(user); return user; } /** * 通過id查找單個用戶 */ @Cacheable(key = "#id") public User getUserById(Integer id) { User user = this.userRepository.findOne(id); return user; } /** * 通過id修改單個用戶 */ @CachePut(key = "#user.id") public User updateUserById(User user) { return this.userRepository.save(user); } /** * 通過id刪除單個用戶 */ @CacheEvict(key = "#id") public void deleteUserById(Integer id) { this.userRepository.delete(id); } }
3.7 編寫 UserController 進行驗證
package com.light.springboot.controller; import java.util.Date; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import com.light.springboot.entity.User; import com.light.springboot.service.UserService; @RestController @RequestMapping(value="/userController") public class UserController { @Autowired private UserService userService; @RequestMapping("/save") public User saveUser() { User user = new User(); user.setName("Adam"); user.setDepartmentId(2); user.setCreateDate(new Date()); userService.insertUser(user); return user; } @RequestMapping("/get/{id}") public User getUser(@PathVariable(required = false) Integer id) { User user = userService.getUserById(id); return user; } @RequestMapping(value="/update/{id}") public User updateUser(@PathVariable(required = false) Integer id) { User user = userService.getUserById(id); if(user == null) { return null; } user.setName("Deft"); user.setDepartmentId(2); userService.updateUserById(user); return user; } @RequestMapping("/delete/{id}") public Integer deleteUser(@PathVariable(required = false) Integer id) { userService.deleteUserById(id); return id; } }
3.8 測試日志說明
由於在系統中 整合了 log4jdbc,所以控制台可以直接顯示運行時的 SQL 及其參數。
3.8.1 發起新增請求
http://localhost:8080/userController/save
頁面顯示:
{"id":14,"name":"Adam","departmentId":2,"createDate":"2018-02-10 18:21:45"}
控制台輸出:
Hibernate: insert into user (create_time, department_id, name) values (?, ?, ?) 2018-02-10 18:21:45.187 INFO 3920 --- [nio-8080-exec-9] jdbc.sqlonly : insert into user (create_time, department_id, name) values ('02/10/2018 18:21:45.184', 2, 'Adam') UserEntity [id=14, name=Adam, departmentId=2, createDate=Sat Feb 10 18:21:45 CST 2018]
3.8.2 新增成功之后,發起按照 id 查詢請求
http://localhost:8080/userController/get/14
頁面顯示:
{"id":14,"name":"Adam","departmentId":2,"createDate":"2018-02-10 18:21:45"}
控制台沒有日志輸出,說明數據是從緩存中獲取的。
3.8.3 發起修改請求
http://localhost:8080/userController/update/14
頁面顯示:
{"id":14,"name":"Deft","departmentId":2,"createDate":"2018-02-10 18:21:45"}
控制台輸出:
Hibernate: update user set create_time=?, department_id=?, name=? where id=? 2018-02-10 18:27:36.275 INFO 3920 --- [nio-8080-exec-3] jdbc.sqlonly : update user set create_time='02/10/2018 18:21:45.184', department_id=2, name='Deft' where id=14
3.8.4 修改成功之后,再次發起按照 id 查詢請求
http://localhost:8080/userController/get/14
頁面顯示:
{"id":14,"name":"Deft","departmentId":2,"createDate":"2018-02-10 18:21:45"}
控制台沒有日志輸出,但是數據已經發生了更新,說明數據是從緩存中獲取的。
3.8.5 發起刪除請求
http://localhost:8080/userController/delete/14
頁面顯示:
14
控制台輸出:
Hibernate: delete from user where id=? 2018-02-10 18:32:32.541 INFO 3920 --- [nio-8080-exec-7] jdbc.sqlonly : delete from user where id=14
3.8.6 刪除成功之后,最后發起按照 id 查詢請求
http://localhost:8080/userController/get/14
頁面沒有顯示。
控制台輸出:
Hibernate: select user0_.id as id1_0_0_, user0_.create_time as create_t2_0_0_, user0_.department_id as departme3_0_0_, user0_.name as name4_0_0_ from user user0_ where user0_.id=? 2018-02-10 18:36:30.372 INFO 3920 --- [nio-8080-exec-9] jdbc.sqlonly : select user0_.id as id1_0_0_, user0_.create_time as create_t2_0_0_, user0_.department_id as departme3_0_0_, user0_.name as name4_0_0_ from user user0_ where user0_.id=14
可以看到日志打印出了查詢語句,說明該緩存項已經被清除,需要查詢數據庫。
四、集成 EhCache
4.1 添加 EhCache 依賴
<!-- Spring Cache --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-cache</artifactId> </dependency> <!-- EhCache --> <dependency> <groupId>net.sf.ehcache</groupId> <artifactId>ehcache</artifactId> </dependency>
4.2 添加 ehcache.xml 配置文件
在 src/main/resources 目錄下創建 ehcache.xml 文件,內容如下:
<?xml version="1.0" encoding="UTF-8"?> <ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="ehcache.xsd"> <cache name="user" eternal="false" maxEntriesLocalHeap="0" timeToIdleSeconds="50"> </cache> </ehcache>
4.3 在 application.properties 中配置目標緩存管理器
spring.cache.type=ehcache
spring.cache.ehcache.config=classpath:ehcache.xml
五、集成 Redis
5.1 添加 Redis 依賴
<!-- Redis --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency>
5.2 在 application.properties 中配置目標緩存管理器
spring.redis.host=192.168.1.102 spring.redis.port=6379 spring.redis.password= spring.redis.database=0 spring.redis.pool.max-active=8 spring.redis.pool.max-idle=8 spring.redis.pool.max-wait=-1 spring.redis.pool.min-idle=0 spring.redis.timeout=0 spring.cache.type=redis
其他步驟與使用 Simple 和 Ehcache 時的步驟一致。