Spring 的緩存帝國,得益於這 5 個註解!

你好,我是猿 java

在微服務,分佈式的大環境下,緩存絕對是提升系統性能的關鍵手段,Spring 作爲 Java 生態中最流行的企業級應用框架,它是如何實現緩存的呢?這篇文章,我們將深入探討 Spring 中 5 個核心的緩存註解。

1. 什麼是緩存?

緩存(Cache)是一種存儲機制,旨在臨時存儲數據副本,以便快速訪問。緩存一般位於應用程序與數據源(如數據庫)之間,能夠顯著降低數據訪問延遲和減輕數據源的壓力。

2. 緩存的類型

緩存一般可以分爲下面 4 種類型:

3. Spring 緩存

Spring 從 4.0 版本起開始引入了 Cache 模塊,並提供了一套統一的緩存 API,隱藏了底層緩存實現的複雜性。開發者只需通過配置和註解即可實現緩存功能,支持多種緩存實現,如 EhCache、Redis、Caffeine 等。

Spring 緩存模塊的核心組件包括:

通過合理配置和使用,Spring 緩存抽象能夠靈活地滿足各種應用場景的需求。

4. Spring 緩存註解詳解

Spring 緩存註解主要有以下 5 個:

下面我們將逐一對這些註解進行分析。

4.1 @Cacheable

@Cacheable註解用於方法級別,表示方法執行的結果可以被緩存。當方法被調用時,Spring 會先檢查緩存中是否存在對應的鍵值對,如果存在,則直接返回緩存中的結果;如果不存在,則執行方法,並將結果存入緩存。

使用示例:

@Service
public class UserService {

    @Cacheable(value = "users", key = "#id")
    public User getUserById(Long id) {
        // 模擬數據庫訪問
        simulateSlowService();
        returnnew User(id, "John Doe");
    }

    private void simulateSlowService() {
        try {
            Thread.sleep(3000L);
        } catch (InterruptedException e) {
            thrownew IllegalStateException(e);
        }
    }
}

在上述示例中,getUserById方法被@Cacheable註解修飾,指定使用users緩存,並以方法參數id作爲緩存鍵。首次調用該方法時,緩存中不存在對應的用戶信息,方法會被執行並將結果存入緩存。後續相同的調用將直接從緩存中獲取結果,避免了重複的業務邏輯執行。

關鍵屬性:

4.2 @CachePut

@CachePut註解同樣用於方法級別,但與@Cacheable不同,它總是執行方法,並將結果存入緩存。@CachePut適用於需要更新緩存但不影響方法執行結果的場景。

使用示例:

@Service
public class UserService {

    @CachePut(value = "users", key = "#user.id")
    public User updateUser(User user) {
        // 模擬更新數據庫
        return user;
    }
}

在上述示例中,updateUser方法被@CachePut註解修飾,每次調用該方法時,都會執行方法邏輯(更新操作),並將返回的User對象更新到users緩存中。這樣可以確保緩存中的數據與數據庫中的數據保持一致。

關鍵屬性:

@Cacheable相同,@CachePut也支持valuecacheNameskey等屬性,用於指定緩存名稱、鍵及其他配置。

4.3 @CacheEvict

@CacheEvict註解用於方法級別,表示在方法執行後,清除指定緩存中的一個或多個條目。它常用於刪除操作,以確保緩存中的數據與數據源保持一致。

使用示例:

@Service
public class UserService {

    @CacheEvict(value = "users", key = "#id")
    public void deleteUser(Long id) {
        // 模擬刪除數據庫
    }
}

在上述示例中,deleteUser方法被@CacheEvict註解修飾,指定從users緩存中移除鍵爲id的條目。這樣,在用戶被刪除後,相應的緩存數據也被清除,防止緩存中的數據不一致。

關鍵屬性:

4.4 @Caching

@Caching註解用於組合多個緩存註解,使得在一個方法上可以執行多個緩存操作。它適用於需要同時執行多個緩存行爲的複雜場景。

使用示例:

@Service
public class UserService {

    @Caching(
        put = { @CachePut(value = "users", key = "#user.id"),
                @CachePut(value = "username", key = "#user.username") },
        evict = { @CacheEvict(value = "userCache", allEntries = true) }
    )
    public User addUser(User user) {
        // 模擬添加用戶到數據庫
        return user;
    }
}

在上述示例中,addUser方法通過@Caching註解同時執行了兩個@CachePut操作,將用戶信息存入不同的緩存中,並且執行了一個@CacheEvict操作,清除userCache中的所有條目。

關鍵屬性:

@Caching主要包含以下屬性:

通過組合不同類型的緩存註解,@Caching提供了更靈活的緩存操作能力。

4.5 @CacheConfig

@CacheConfig註解用於類級別,爲該類中的所有緩存註解提供公共配置。例如,可以指定統一的緩存名稱、緩存管理器等,減少重複配置的工作量。

使用示例:

@Service
@CacheConfig(cacheNames = "users", cacheManager = "cacheManager")
publicclass UserService {

    @Cacheable(key = "#id")
    public User getUserById(Long id) {
        // 模擬數據庫訪問
        returnnew User(id, "John Doe");
    }

    @CachePut(key = "#user.id")
    public User updateUser(User user) {
        // 模擬更新數據庫
        return user;
    }

    @CacheEvict(key = "#id")
    public void deleteUser(Long id) {
        // 模擬刪除數據庫
    }
}

在上述示例中,@CacheConfig註解指定了默認的緩存名稱和緩存管理器,使得類中的所有緩存註解無需重複指定這些屬性,只需關注特定的鍵或其他配置。

關鍵屬性:

@CacheConfig通過提供類級別的緩存配置,簡化了屬性的配置和維護,提高了代碼的可讀性和可維護性。

5. 緩存框架

要使 Spring 的緩存註解生效,必須配置一個緩存管理器(CacheManager)和相應的緩存提供者。Spring 支持多種緩存實現,常見的包括 EhCache、Redis、Caffeine 等。下面,我們介紹這 3 種常用緩存提供者的配置方法。

5.1 EhCache

EhCache 是一款常用的開源緩存庫,支持本地內存和磁盤存儲,配置靈活,適用於單機應用。

依賴配置(Maven)

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
    <groupId>net.sf.ehcache</groupId>
    <artifactId>ehcache</artifactId>
    <version>2.10.6</version>
</dependency>

配置示例

創建一個 EhCache 配置文件ehcache.xml

<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd">
    <diskStore path="java.io.tmpdir"/>
    <defaultCache 
        maxEntriesLocalHeap="1000" 
        eternal="false" 
        timeToIdleSeconds="300" 
        timeToLiveSeconds="600" 
        overflowToDisk="false"/>
    <cache 
           maxEntriesLocalHeap="500"
           timeToLiveSeconds="3600"
           eternal="false"
           overflowToDisk="false"/>
</ehcache>

Spring 配置

@Configuration
@EnableCaching
public class CacheConfig {

    @Bean
    public EhCacheManagerFactoryBean ehCacheManagerFactoryBean() {
        EhCacheManagerFactoryBean factoryBean = new EhCacheManagerFactoryBean();
        factoryBean.setConfigLocation(new ClassPathResource("ehcache.xml"));
        factoryBean.setShared(true);
        return factoryBean;
    }

    @Bean
    public CacheManager cacheManager(EhCacheManagerFactoryBean factoryBean) {
        returnnew EhCacheCacheManager(factoryBean.getObject());
    }
}

5.2 Redis

Redis 是一種高性能的 NoSQL 緩存數據庫,支持分佈式部署,適用於大規模應用場景。

依賴配置(Maven)

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

配置示例(application.properties)

spring.cache.type=redis
spring.redis.host=localhost
spring.redis.port=6379
spring.redis.password=yourpassword

Spring 配置

Spring Boot 會自動配置RedisCacheManager,無需額外配置。如果需要自定義配置,可以如下:

@Configuration
@EnableCaching
public class RedisCacheConfig {

    @Bean
    public RedisCacheManager cacheManager(RedisConnectionFactory connectionFactory) {
        // 配置默認緩存過期時間等
        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
            .entryTtl(Duration.ofMinutes(60))
            .disableCachingNullValues();
        return RedisCacheManager.builder(connectionFactory)
            .cacheDefaults(config)
            .build();
    }
}

5.3 Caffeine

Caffeine 是一個高性能的本地緩存庫,具有豐富的緩存策略和高併發性能。

依賴配置(Maven)

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
    <groupId>com.github.ben-manes.caffeine</groupId>
    <artifactId>caffeine</artifactId>
    <version>3.1.6</version>
</dependency>

Spring 配置

@Configuration
@EnableCaching
public class CaffeineCacheConfig {

    @Bean
    public Caffeine<Object, Object> caffeineConfig() {
        return Caffeine.newBuilder()
                .expireAfterWrite(60, TimeUnit.MINUTES)
                .maximumSize(1000);
    }

    @Bean
    public CacheManager cacheManager(Caffeine<Object, Object> caffeine) {
        CaffeineCacheManager manager = new CaffeineCacheManager("users");
        manager.setCaffeine(caffeine);
        return manager;
    }
}

6. 案例分析

下面我們通過一個簡單的 CRUD 應用,演示如何在 Spring Boot 項目中集成和使用緩存註解。

6.1 項目介紹

構建一個用戶管理系統,包含用戶的增刪改查功能。通過緩存優化其中的讀取操作,以提升系統性能。

6.2 環境搭建

技術棧

依賴配置(Maven)

<dependencies>
    <!-- Spring Boot Web Starter -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <!-- Spring Data JPA Starter -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>

    <!-- H2 Database -->
    <dependency>
        <groupId>com.h2database</groupId>
        <artifactId>h2</artifactId>
        <scope>runtime</scope>
    </dependency>

    <!-- Spring Boot Cache Starter -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-cache</artifactId>
    </dependency>

    <!-- EhCache -->
    <dependency>
        <groupId>net.sf.ehcache</groupId>
        <artifactId>ehcache</artifactId>
        <version>2.10.6</version>
    </dependency>

    <!-- Lombok(可選,用於簡化代碼) -->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <scope>provided</scope>
    </dependency>
</dependencies>

6.3 緩存配置

創建ehcache.xml文件

<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd">
    <diskStore path="java.io.tmpdir"/>
    <defaultCache
        maxEntriesLocalHeap="1000"
        eternal="false"
        timeToIdleSeconds="300"
        timeToLiveSeconds="600"
        overflowToDisk="false"/>
    <cache 
           maxEntriesLocalHeap="500"
           timeToLiveSeconds="3600"
           eternal="false"
           overflowToDisk="false"/>
</ehcache>

配置類

@Configuration
@EnableCaching
public class CacheConfig {

    @Bean
    public EhCacheManagerFactoryBean ehCacheManagerFactoryBean() {
        EhCacheManagerFactoryBean factoryBean = new EhCacheManagerFactoryBean();
        factoryBean.setConfigLocation(new ClassPathResource("ehcache.xml"));
        factoryBean.setShared(true);
        return factoryBean;
    }

    @Bean
    public CacheManager cacheManager(net.sf.ehcache.CacheManager cm) {
        returnnew EhCacheCacheManager(cm);
    }
}

6.4 實體和倉庫

用戶實體類

@Entity
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
    @Id
    private Long id;
    private String username;
}

用戶倉庫接口

public interface UserRepository extends JpaRepository<User, Long> {
    User findByUsername(String username);
}

6.5 服務層與緩存註解應用

@Service
@CacheConfig(cacheNames = "users")
public class UserService {

    @Autowired
    private UserRepository userRepository;

    @Cacheable(key = "#id")
    public User getUserById(Long id) {
        simulateSlowService();
        return userRepository.findById(id).orElse(null);
    }

    @Cacheable(key = "#username")
    public User getUserByUsername(String username) {
        simulateSlowService();
        return userRepository.findByUsername(username);
    }

    @CachePut(key = "#user.id")
    public User updateUser(User user) {
        return userRepository.save(user);
    }

    @CacheEvict(key = "#id")
    public void deleteUser(Long id) {
        userRepository.deleteById(id);
    }

    private void simulateSlowService() {
        try {
            Thread.sleep(2000L); // 模擬耗時操作
        } catch (InterruptedException e) {
            thrownew IllegalStateException(e);
        }
    }
}

在上述示例中:

6.6 控制層

@RestController
@RequestMapping("/api/users")
public class UserController {

    @Autowired
    private UserService userService;

    @GetMapping("/{id}")
    public ResponseEntity<User> getUserById(@PathVariable Long id) {
        User user = userService.getUserById(id);
        return ResponseEntity.ok(user);
    }

    @GetMapping("/username/{username}")
    public ResponseEntity<User> getUserByUsername(@PathVariable String username) {
        User user = userService.getUserByUsername(username);
        return ResponseEntity.ok(user);
    }

    @PostMapping
    public ResponseEntity<User> addUser(@RequestBody User user) {
        User savedUser = userService.updateUser(user);
        return ResponseEntity.status(HttpStatus.CREATED).body(savedUser);
    }

    @DeleteMapping("/{id}")
    public ResponseEntity<Void> deleteUser(@PathVariable Long id) {
        userService.deleteUser(id);
        return ResponseEntity.noContent().build();
    }
}

6.7 測試緩存效果

  1. 啓動應用程序

  2. 調用GET /api/users/{id}接口

  1. 調用POST /api/users接口更新用戶
  1. 調用DELETE /api/users/{id}接口刪除用戶

通過上述步驟,可以驗證緩存的實際效果,發現讀取操作的響應時間明顯降低。

7. 增強功能

7.1 自定義緩存鍵生成策略

默認情況下,Spring 根據方法的參數生成緩存鍵。對於複雜的業務場景,可能需要自定義緩存鍵生成策略。

自定義 KeyGenerator

@Component("customKeyGenerator")
public class CustomKeyGenerator implements KeyGenerator {
    @Override
    public Object generate(Object target, Method method, Object... params) {
        return method.getName() + "_" + Arrays.stream(params)
                .map(Object::toString)
                .collect(Collectors.joining("_"));
    }
}

使用自定義 KeyGenerator

@Cacheable(cacheNames = "users", keyGenerator = "customKeyGenerator")
public User getUser(Long id, String type) {
    // 方法實現
}

7.2 緩存條件與排除

通過conditionunless屬性,可以控制是否進行緩存操作。

示例

@Cacheable(value = "users", key = "#id", condition = "#id > 10", unless = "#result.username == 'admin'")
public User getUserById(Long id) {
    // 方法實現
}

在上述示例中:

7.3 緩存同步與異步

在分佈式系統中,緩存的一致性和同步性是至關重要的。Spring Cache 本身不直接提供同步機制,但可以通過結合其他工具實現。

方案

7.4 緩存與事務的結合

在涉及事務的操作中,緩存的更新需要與事務保持一致性。

方案

示例

@CacheEvict(value = "users", key = "#id", beforeInvocation = false)
@Transactional
public void deleteUser(Long id) {
    userRepository.deleteById(id);
}

在上述示例中,緩存清除操作將在事務提交後執行,確保數據成功刪除後再清除緩存。

8. 總結

本文,我們分析了緩存技術,它在提升應用性能、降低數據庫壓力、改善用戶體驗方面發揮着重要作用。

另外,我們重點分析了 Spring 中 5 個核心的緩存註解以及示例分析,Spring 通過提供全面的緩存抽象和簡潔的緩存註解,使得開發者能夠輕鬆地集成和管理緩存機制。

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/19u0Las7ego4G5c4uMrP2A