Spring緩存案例分析
介紹完Spring為緩存提供的核心元件,接下來通過一個完整的案例示範這些注解起到的效果,進而更好地為開發人員在日常開發
過程中引入Spring緩存機制來應對高并發場景提供基礎。
在本案例中,我們将使用Redis作為我們的緩存媒介,而資料持久化使用的是MySQL資料庫。
Spring所提供的Spring Data架構可以完成對這兩種資料庫的高效通路。
與使用Spring Data進行關系型資料庫通路相似,使用SpringData Redis的第一步就是連接配接到Redis伺服器。
想要實作連接配接,就需要擷取RedisConnection,而擷取RedisConnection的手段是使用RedisConnectionFactory接口。
Spring Data Redis對Redis操作做了封裝,提供了一個工具類RedisTemplate,通過注入RedisConnectionFactory到RedisTemplate中,該RedisTemplate就能擷取RedisConnection。
建構一個RedisTemplate的方法如代碼清單6-17所示。
代碼清單6-17 建構RedisTemplate示例代碼
@Bean
public RedisTemplate<String, Integer>
redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Integer> redisTemplate = new RedisTemplate<>
();
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(new
JdkSerializationRedisSerializer());
redisTemplate.setExposeConnection(true);
redisTemplate.setConnectionFactory(factory);
redisTemplate.afterPropertiesSet();
return redisTemplate;
}
RedisTemplate為Redis互動提供了一個進階别的抽象。當RedisConnection提供低級别的方法來發送和傳回二進制值時,RedisTemplate能夠實作序列化和連接配接過程的自動化管理,進而将使用者從這些細節中解放出來。
RedisTemplate提供了豐富的接口來操作Redis的特定資料類型,這些接口包括ValueOperations、ListOperations、SetOperations、ZSetOperations和HashOperations等,分别對應了Redis中String、List、Set、ZSet和Hash這五種常見的資料結構。
為了使用RedisTemplate,我們需要在pom檔案中引入spring-boot-starter-data-redis元件。
本案例中完整的pom檔案定義如代碼清單6-18所示。
代碼清單6-18 案例pom檔案
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId> </dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
這裡spring-boot-starter-cache、spring-boot-starter-data-jpa、spring-boot-starter-data-redis依賴包都非常重要。
我們接着來定義一個實體類User,如代碼清單6-19所示。
代碼清單6-19 User實體類定義代碼
@Entity
@Table(name = "user_table")
@CacheConfig(cacheNames = "user")
public class User implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
protected Long id;
@Column(name = "user_name", nullable = false)
protected String userName;
@Temporal(TemporalType.TIMESTAMP)
@CreationTimestamp
@Column(name = "gmt_create", nullable = false, updatable = false)
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
protected Date gmtCreate;
@Temporal(TemporalType.TIMESTAMP)
@UpdateTimestamp
@Column(name = "gmt_update", nullable = true, insertable = false)
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") protected Date gmtUpdate;
@Column(name = "valid", length = 1)
protected boolean valid = true;
}
上述User類中大量使用了Hibernate中的注解類規範字段的名稱和長度等校驗規則。同時,我們還注意到這裡出現了一個@CacheConfig注解。如果我們在所有的@Cacheable注解中都需要設定value為user,那麼就可以使用@CacheConfig來一次性完成對Cache名稱的設定。
有了User對象,我們給出對應的資料通路層元件UserRepository,如代碼清單6-20所示。
代碼清單6-20 UserRepository接口定義代碼
public interface UserRepository extends JpaRepository<User, Long>,
JpaSpecificationExecutor<User>, Serializable {
}
UserRepository接口定義非常簡單,一方面直接擴充了Spring Data中的JpaRepository接口,另一方面也擴充了JpaSpecificationExecutor接口。
JpaSpecificationExecutor接口提供的是一種Specification查詢機制。
考慮這樣一種場景:我們需要查詢某個實體,而給定的查詢條件是不固定的,這時候就需要動态建構相應的查詢語句。在Spring Data JPA中可以通過JpaSpecificationExecutor接口實作這類查詢。相比于JPQL,使用Specification機制的優勢是類型安全。例如,我們可以使用如代碼清單6-21所示的實作方法來擷取那些valid字段值為true的User對象。
代碼清單6-21 Specification機制示例代碼
Specification<User> specification = Specifications.
<User>and().eq("valid", true).build();
上述實作方法位于UserService中,完整的UserService實作如代碼清單6-22所示。
代碼清單6-22 UserService類代碼
@Service
@EnableCaching
@CacheConfig(cacheNames = "user-object")
public class UserService {
@Autowired
protected UserRepository userRepository;
@CachePut(key = "#root.targetClass + '_' + #user.id")
public void save(User user) {
userRepository.save(user);
}
@CacheEvict(key = "#root.targetClass + '_' + #id")
public void delete(Long id) {
userRepository.deleteById(id);
}
@Cacheable(key = "#root.targetClass + '_' + #id")
public Optional<User> findOne(Long id) {
return userRepository.findById(id);
}
public List<User> findAll() {
Specification<User> specification = Specifications.
<User>and().eq("valid", true).build();
return userRepository.findAll(specification);
}
@Cacheable(value = "test", key = "#root.targetClass + '_' + #p0 +'_' + #p1")
public Page<User> findAll(int pageSize, int pageNumber) {
Specification<User> specification = Specifications.
<User>and().eq("valid", true).build();
Pageable pageable = PageRequest.of(pageNumber, pageSize);
return userRepository.findAll(specification, pageable);
}
}
可以看到,這裡大量使用了Root對象來擷取方法調用所涉及的一組中繼資料,并設定對應的key值。
有了UserService,就可以實作UserController并暴露一系列HTTP端點。
這裡我們簡單列舉如代碼清單6-23所示的分頁方法,其他方法的實作也都類似。
代碼清單6-23 UserController HTTP端點代碼
@GetMapping(value = "/list")
public ResultModel getList(@RequestParam int pageSize, @RequestParam
int pageNumber) {
if (pageSize > 0) {
Page<User> page = userService.findAll(pageSize, pageNumber);
return ResultModel.ok(page);
} else {
List<User> entities = userService.findAll();
return ResultModel.ok(entities);
}
}
在案例的最後一部分,需要介紹的内容就是配置。我們在application.yml檔案中添加如代碼清單6-24所示的與緩存相關的配置項。
代碼清單6-24 application.yml檔案中的緩存配置資訊
spring:
cache:
type: redis
redis:
time-to-live: 20000 #緩存逾時時間,機關為ms
cache-null-values: false #是否緩存空值
redis:
port: 6379
host: localhost
lettuce:
pool:
max-active: 8
max-wait: -1
max-idle: 8
min-idle: 0
timeout: 10000 #redis連接配接逾時時間,機關為ms
database: 0
現在,啟動Spring Boot應用程式,并通過日志看緩存是否生效。在成功添加了幾條User對象資料之後,執行一個根據id擷取User對象的查詢操作,可以得到如代碼清單6-25所示的日志資訊。
代碼清單6-25 查詢User時的日志資訊
Hibernate: select user0_.id as id1_0_0_, user0_.gmt_create as
gmt_crea2_0_0_, user0_.gmt_update as gmt_upda3_0_0_, user0_.user_name
as user_nam4_0_0_, user0_.valid as valid5_0_0_ from user_table user0_
where user0_.id=?
再次執行該查詢操作,我們發現控制台中不會再次出現該日志,說明針對查詢操作的緩存已經生效。
其他針對User對象的操作,你可以參考案例代碼并做一些嘗試:https://github.com/tianminzheng/spring-bootexamples/tree/main/SpringCacheExample。
本文給大家講解的内容是springboot内置緩存:打造高性能系統緩存,Spring緩存案例分析
- 下文給大家講解的是springboot内置緩存:打造高性能系統緩存,緩存實作原理