最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Spring Cache用法及常見問題解決方案

 更新時間:2025年09月11日 10:36:23   作者:何蘇三月  
Spring Cache用法很簡單,但你知道這中間的坑嗎?今天我以一個親身經(jīng)歷者的角度,系統(tǒng)的講解SpringCache的用法,并且舉例介紹使用SpringCache遇到的常見問題,感興趣的朋友跟隨小編一起看看吧

Spring Cache 作為 Spring 框架提供的緩存抽象層,確實能夠顯著簡化項目中 Redis、Caffeine 等緩存技術(shù)的使用,但許多開發(fā)者在實際應用中會遇到各種"看似正確但緩存不生效"的問題。以下將系統(tǒng)性地分析這些問題的根源,并提供全面的解決方案。

這篇文章將會以一個親身經(jīng)歷者的角度,系統(tǒng)的講解SpringCache的用法,并且舉例介紹使用SpringCache遇到的常見問題。

一、介紹

1.1 基本介紹

Spring Cache 是 Spring 框架提供的一個緩存抽象層,它通過在方法上添加簡單的注解來實現(xiàn)緩存功能,從而減少重復計算,提高系統(tǒng)性能。

Spring Cache 利用了AOP,實現(xiàn)了基于注解的緩存功能,并且進行了合理的抽象,業(yè)務代碼不用關(guān)心底層是使用了什么緩存框架,只需要簡單地加一個注解,就能實現(xiàn)緩存功能了,做到了對代碼侵入性做小。

由于市面上的緩存工具實在太多,SpringCache框架還提供了CacheManager接口,可以實現(xiàn)降低對各種緩存框架的耦合。它不是具體的緩存實現(xiàn),它只提供一整套的接口和代碼規(guī)范、配置、注解等,用于整合各種緩存方案,比如Redis、Caffeine、Guava Cache、Ehcache。

1.2 核心概念

(1)緩存抽象

Spring Cache 提供了一組通用的緩存抽象接口,主要包括:

  • Cache - 緩存接口,定義緩存操作
  • CacheManager - 緩存管理器,用于管理各種緩存組件

(2)主要注解

Spring Cache 通過以下注解提供聲明式緩存:

  • @Cacheable - 表明方法的返回值可以被緩存
  • @CacheEvict - 表明方法會觸發(fā)緩存的清除
  • @CachePut - 表明方法會更新緩存,但總會執(zhí)行方法
  • @Caching - 組合多個緩存操作
  • @CacheConfig - 類級別的共享緩存配置

二、常見坑

2.1當一個類中的方法A(帶有@Cacheable注解)被同一個類中的另一個方法B調(diào)用時,@Cacheable注解會失效,緩存機制不會起作用

(1)案例演示

package com.example.demo;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/api")
public class Controller {
    @GetMapping("/test")
    public String get() {
        return innerGet();
    }
    @Cacheable(value = "inner")
    public String innerGet() {
        System.out.println("----------1111111111111------------");
        return "內(nèi)部調(diào)用";
    }
}

 通過反復調(diào)用該接口,我們發(fā)現(xiàn)系統(tǒng)并沒有命中緩存,而是每次都重復執(zhí)行了innerGet()方法,redis緩存中也確實沒有這個inner相關(guān)的key。

其實這個問題在我的idea中已經(jīng)有警告提示了,如下圖所示:

 意思是說

當一個類中的方法A(帶有@Cacheable注解)被同一個類中的另一個方法B調(diào)用時,@Cacheable注解會失效,緩存機制不會起作用。

(2)原因分析

這是由于Spring AOP(面向切面編程)的實現(xiàn)方式導致的:

  • Spring的緩存功能是通過AOP代理實現(xiàn)的
  • 當方法從類外部調(diào)用時,會經(jīng)過代理,緩存邏輯能正常執(zhí)行
  • 但當方法從類內(nèi)部調(diào)用時(自調(diào)用),會繞過代理直接調(diào)用,導致緩存邏輯被跳過

(3)解決辦法

  1. 將緩存方法移到另一個類?。?!(推薦)
  2. 自行注入自己(通過構(gòu)造函數(shù)或@Autowired),然后再用這個注入的當前類的對象去調(diào)用這個方法。(不太推薦)

(4)引申

這種自調(diào)用失效的問題是Spring AOP的普遍現(xiàn)象,不僅限于@Cacheable,其他如@Transactional、@Async等注解也有同樣的問題。

@Service
public class OrderService {
    public void placeOrder(Order order) {
        // 這里直接調(diào)用,@Transactional會失效
        updateInventory(order.getItems());  // 事務不會生效
        // 其他業(yè)務邏輯...
    }
    @Transactional
    public void updateInventory(List<Item> items) {
        // 更新庫存操作
        items.forEach(item -> {
            inventoryRepository.reduceStock(item.getId(), item.getQuantity());
        });
    }
}

為什么失效?

AOP 代理機制:Spring 的事務管理是通過 AOP 代理實現(xiàn)的。當 placeOrder 直接調(diào)用 updateInventory 時,調(diào)用發(fā)生在目標對象內(nèi)部,繞過了 Spring 創(chuàng)建的代理對象。因此事務攔截器沒有機會介入,事務不會開啟。

解決辦法

解決辦法是一樣的,這里也是推薦拆分到不同服務類。

@Service
public class OrderService {
    @Autowired
    private InventoryService inventoryService;
    public void placeOrder(Order order) {
        inventoryService.updateInventory(order.getItems()); // 現(xiàn)在會走代理,事務生效
    }
}
@Service
public class InventoryService {
    @Transactional
    public void updateInventory(List<Item> items) {
        // 更新庫存操作
    }
}

2.2 json序列化問題

(1)案例演示

存在亂碼的情況,如:內(nèi)部調(diào)用

(2)原因分析

  • 這是因為
  • Spring Cache 默認使用 JDK 序列化方式
  • JDK 序列化會產(chǎn)生二進制數(shù)據(jù),導致 Redis 中顯示亂碼
  • 您看到的 內(nèi)部調(diào)用 就是 JDK 序列化的結(jié)果

(3)解決辦法

配置 CacheManager 使用 JSON 序列化

@EnableCaching
@Configuration
public class CacheConfig {
    @Bean
    public CacheManager cacheManager(RedisConnectionFactory factory) {
        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
            .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
            .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()));
        return RedisCacheManager.builder(factory)
                .cacheDefaults(config)
                .build();
    }
}

 這樣就可以了,不信我們再寫兩個案例試試

@RestController
@RequestMapping("/api")
public class Controller {
    @Autowired
    private Controller controller;
    @GetMapping("/test")
    public String get() {
        return controller.innerGet();
    }
    @Cacheable(value = "inner")
    public String innerGet() {
        System.out.println("----------1111111111111------------");
        return "內(nèi)部調(diào)用";
    }
    @GetMapping("/cacheUser")
    public User cacheUser() {
        return controller.getUser();
    }
    @Cacheable(value = "user")
    public User getUser() {
        System.out.println("----------2222222222222------------");
        User user = new User();
        user.setName("張三");
        user.setAge(18);
        user.setPassword("2342a18");
        user.setEmail("32432kjkj@1523.com");
        return user;
    }
}

(4)提醒

如果不使用springcache,想通過RedisTemplate直接編程式的操作redis的話,記得也需要配置一下RedisTemplate的序列化。否則也會有這樣的問題。當然了,你還可以使用StringRedisTemplate,這在Redis入門教程中已經(jīng)講解過了,這里不在贅述。

@Configuration
public class RedisConfig {
    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(factory);
        // 使用 String 序列化 key
        template.setKeySerializer(new StringRedisSerializer());
        // 使用 Jackson 序列化 value
        template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
        template.setHashKeySerializer(new StringRedisSerializer());
        template.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
        template.afterPropertiesSet();
        return template;
    }
}

三、正確使用教程?

3.1 導入依賴

這里的緩存我們用Redis。當然如果是其他緩存,請自行引入即可。

<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>

3.2 緩存配置

這一步主要是進行序列化。

防止SpringCache注解加入緩存后,出現(xiàn)亂碼的情況。因為Spring Cache 默認使用 JDK 序列化方式。另外對RedisTemplate也進行了JDK序列化自定義,除非你的項目一定不用編程式操作redis。

具體原因在第二章已經(jīng)講解了,這里只給出正確配置。

package com.example.demo;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.StringRedisSerializer;
@EnableCaching // 一定要開啟緩存
@Configuration
public class CacheConfig {
    @Bean
    public CacheManager cacheManager(RedisConnectionFactory factory) {
        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
            .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
            .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()));
        return RedisCacheManager.builder(factory)
                .cacheDefaults(config)
                .build();
    }
    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(factory);
        // 使用 String 序列化 key
        template.setKeySerializer(new StringRedisSerializer());
        // 使用 Jackson 序列化 value
        template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
        template.setHashKeySerializer(new StringRedisSerializer());
        template.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
        template.afterPropertiesSet();
        return template;
    }
}

3.3 常用注解

(1)@Cacheable

用于標記方法的返回值可以被緩存。

  • 常用屬性:
    • value/cacheNames:指定緩存名稱(value和cacheNames是互斥的,即只能指定其中一個,二選一,必填)
  • key:指定緩存的key。可以使用Spring Expression Language(SpEL)來編寫key表達式,以實現(xiàn)動態(tài)鍵的生成
    • 默認情況下,若未顯式指定key,Spring會使用方法的??所有參數(shù)組合??作為鍵(通過SimpleKeyGenerator生成)
    • keyGenerator沖突??:keykeyGenerator屬性不能同時使用,需二選一
  • condition:指定緩存條件
  • unless:否決緩存的條件
//?? 參數(shù)作為鍵
@Cacheable(value = "userCache", key = "#id")  // 使用參數(shù)id作為鍵
public User getUserById(Long id) { ... }
// 若參數(shù)是對象,可訪問其屬性:
@Cacheable(value = "userCache", key = "#user.id")  // 使用user對象的id屬性
public User find(User user) { ... }
// ??多參數(shù)組合鍵
@Cacheable(value = "userCache", key = "#firstName + '-' + #lastName")
public User getUserByName(String firstName, String lastName) { ... }
// ??方法信息作為鍵
@Cacheable(value = "userCache", key = "#root.methodName + #id")  // 方法名+參數(shù)
public User getUserById(Long id) { ... }
/*
SpEL支持的元數(shù)據(jù)??
在SpEL表達式中,可通過以下變量生成鍵:
#root.methodName:當前方法名
#root.target:目標對象實例
#result:方法返回值(僅適用于unless或condition)
#參數(shù)名或#p0/#p1(按參數(shù)索引)
*/

(2)@CachePut

  • 總是執(zhí)行方法,并將結(jié)果放入緩存
  • 通常用于更新操作后更新緩存
@CachePut(value="users", key="#user.id")
public User updateUser(User user) {...}

(3)@CacheEvict

  • 用于清除緩存
  • 常用屬性:
    • allEntries:是否清除所有緩存(默認false)
    • beforeInvocation:是否在方法執(zhí)行前清除(默認false)
@CacheEvict(value="users", key="#userId")
public void deleteUser(String userId) {...}

(4)@Caching

  • 用于組合多個緩存操作
@Caching(evict = {
    @CacheEvict(value="primary", key="#user.id"),
    @CacheEvict(value="secondary", key="#user.username")
})
public void updateUser(User user) {...}

3.4 注意事項

??方法A調(diào)用同類中帶緩存的方法B時,若沒有自行注入自己,則無法引入緩存。(Spring AOP的普遍現(xiàn)象

??未自定義序列化操作,則可能出現(xiàn)亂碼現(xiàn)象

到此這篇關(guān)于Spring Cache用法很簡單,但你知道這中間的坑嗎?的文章就介紹到這了,更多相關(guān)Spring Cache用法內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • SpringCloud?Ribbon負載均衡流程分析

    SpringCloud?Ribbon負載均衡流程分析

    在Eureka注冊中心中我們在添加完@LoadBalanced注解,即可實現(xiàn)負載均衡功能,現(xiàn)在一起探索一下負載均衡的原理(Ribbon),感興趣的朋友一起看看吧
    2024-03-03
  • java獲取壓縮文件中的XML并解析保存到數(shù)據(jù)庫

    java獲取壓縮文件中的XML并解析保存到數(shù)據(jù)庫

    這篇文章主要為大家詳細介紹了如何使用java實現(xiàn)獲取壓縮文件中的XML并解析保存到數(shù)據(jù)庫,文中的示例代碼講解詳細,感興趣的小伙伴可以跟隨小編一起學習一下
    2025-06-06
  • Java文件基本操作總結(jié)

    Java文件基本操作總結(jié)

    今天給大家?guī)淼氖顷P(guān)于Java基礎的相關(guān)知識,文章圍繞著Java文件操作展開,文中有非常詳細的介紹及代碼示例,需要的朋友可以參考下
    2021-06-06
  • Spring Boot Starter中Bean 注冊與屬性綁定的兩大機制最佳實踐方案

    Spring Boot Starter中Bean 注冊與屬性綁定的兩大機制最佳實踐方案

    在 Spring Boot的世界里,Starter(啟動器)是簡化依賴管理和自動化配置的強大工具,本文將深入探討 Spring Boot Starter 中 Bean 初始化的核心機制,并指出最佳實踐,感興趣的朋友跟隨小編一起看看吧
    2025-10-10
  • SpringBoot中整合RabbitMQ(測試+部署上線最新完整)的過程

    SpringBoot中整合RabbitMQ(測試+部署上線最新完整)的過程

    本文詳細介紹了如何在虛擬機和寶塔面板中安裝RabbitMQ,并使用Java代碼實現(xiàn)消息的發(fā)送和接收,通過異步通訊,可以優(yōu)化性能,感興趣的朋友一起看看吧
    2025-02-02
  • SpringBoot整合郵件發(fā)送的四種方法

    SpringBoot整合郵件發(fā)送的四種方法

    這篇文章主要介紹了SpringBoot整合郵件發(fā)送的四種方法,SpringBoot中集成了發(fā)送郵件的功能,本文做了進一步優(yōu)化,需要的朋友可以參考下
    2023-03-03
  • SpringBoot 創(chuàng)建web項目并部署到外部Tomcat

    SpringBoot 創(chuàng)建web項目并部署到外部Tomcat

    本篇文章主要介紹了SpringBoot 創(chuàng)建web項目并部署到外部Tomcat,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2017-06-06
  • java中Object類4種方法詳細介紹

    java中Object類4種方法詳細介紹

    大家好,本篇文章主要講的是java中Object類4種方法詳細介紹,感興趣的同學趕快來看一看吧,對你有幫助的話記得收藏一下,方便下次瀏覽
    2022-01-01
  • MyBatis-Plus Sequence主鍵的實現(xiàn)

    MyBatis-Plus Sequence主鍵的實現(xiàn)

    這篇文章主要介紹了MyBatis-Plus Sequence主鍵的實現(xiàn),文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2020-12-12
  • SpringBoot注解生效原理全解析

    SpringBoot注解生效原理全解析

    Spring Boot注解自動生效依賴于Spring容器和處理機制,通過掃描、解析配置類生成BeanDefinition,再由容器實例化,實現(xiàn)@Autowired等注解的自動注入與組件注冊,本文給大家介紹SpringBoot注解生效原理分析,感興趣的朋友一起看看吧
    2025-09-09

最新評論

赤水市| 汉阴县| 琼海市| 霍林郭勒市| 临桂县| 闻喜县| 高邑县| 乌鲁木齐县| 绥滨县| 黄龙县| 晋州市| 崇文区| 浪卡子县| 高州市| 从江县| 梁山县| 汝城县| 锡林郭勒盟| 文安县| 稻城县| 筠连县| 伊春市| 区。| 北碚区| 元氏县| 瑞安市| 广汉市| 金堂县| 娱乐| 来凤县| 梁山县| 习水县| 台湾省| 阿尔山市| 娄烦县| 盖州市| 长垣县| 上杭县| 井陉县| 内丘县| 临夏市|