天天看點

10年開發大牛帶領程式員打造高性能系統緩存:Spring緩存案例分析

作者:程式員進階碼農II

Spring緩存案例分析

介紹完Spring為緩存提供的核心元件,接下來通過一個完整的案例示範這些注解起到的效果,進而更好地為開發人員在日常開發

10年開發大牛帶領程式員打造高性能系統緩存: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内置緩存:打造高性能系統緩存,緩存實作原理