首頁  >  文章  >  Java  >  SpringBoot專案中怎麼使用快取Cache

SpringBoot專案中怎麼使用快取Cache

PHPz
PHPz轉載
2023-05-16 14:34:13900瀏覽

前言

快取可以透過將經常存取的資料儲存在記憶體中,減少底層資料來源如資料庫的壓力,從而有效提高系統的效能和穩定性。我想大家的專案中或多或少都有使用過,我們專案也不例外,但是最近在review公司的程式碼的時候寫的很蠢且low, 大致寫法如下:

public User getById(String id) {
	User user = cache.getUser();
    if(user != null) {
        return user;
    }
    // 从数据库获取
    user = loadFromDB(id);
    cahce.put(id, user);
	return user;
}

其實Spring Boot 提供了強大的快取抽象,可以輕鬆地為您的應用程式添加快取。本文就講講如何使用 Spring 提供的不同快取註解實現快取的最佳實務。

啟用快取@EnableCaching

現在大部分項目都是SpringBoot項目,我們可以在啟動類別中加入註解@EnableCaching來開啟快取功能。

@SpringBootApplication
@EnableCaching
public class SpringCacheApp {

    public static void main(String[] args) {
        SpringApplication.run(Cache.class, args);
    }
}

既然要能使用緩存,就需要有一個快取管理器Bean,預設情況下,@EnableCaching 將註冊一個ConcurrentMapCacheManager的Bean,不需要單獨的bean 聲明。 ConcurrentMapCacheManager將值儲存在ConcurrentHashMap的實例中,這是快取機制的最簡單的執行緒安全實作。

自訂快取管理器

預設的快取管理器並不能滿足需求,因為她是儲存在jvm記憶體中的,那麼如何儲存到redis中呢?這時候需要新增自訂的快取管理器。

1.新增依賴

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

2.設定Redis快取管理器

@Configuration
@EnableCaching
public class CacheConfig {

    @Bean
    public RedisConnectionFactory redisConnectionFactory() {
        return new LettuceConnectionFactory();
    }

    @Bean
    public CacheManager cacheManager() {
        RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig()
            .disableCachingNullValues()
            .serializeValuesWith(SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()));

        RedisCacheManager redisCacheManager = RedisCacheManager.builder(redisConnectionFactory())
            .cacheDefaults(redisCacheConfiguration)
            .build();

        return redisCacheManager;
    }
}

現在有了快取管理器以後,我們如何在業務層面操作快取?

我們可以使用@Cacheable@CachePut 或@CacheEvict 註解來操作快取了。

@Cacheable

此註解可以將方法運行的結果進行緩存,在快取時效內再次呼叫方法時不會呼叫方法本身,而是直接從快取取得結果並傳回給調用方。

SpringBoot專案中怎麼使用快取Cache

範例1:快取資料庫查詢的結果。

@Service
public class MyService {

    @Autowired
    private MyRepository repository;

    @Cacheable(value = "myCache", key = "#id")
    public MyEntity getEntityById(Long id) {
        return repository.findById(id).orElse(null);
    }
}

在此範例中,@Cacheable 註解用於快取 getEntityById()方法的結果,該方法根據其 ID 從資料庫中檢索MyEntity 物件。

但是如果我們更新資料呢?舊數據仍然在緩存中?

@CachePut

然後@CachePut 出來了, 與 @Cacheable 註解不同的是使用 @CachePut 註解標註的方法,在執行前不會去檢查快取中是否存在之前執行過的結果,而是每次都會執行該方法,並將執行結果以鍵值對的形式寫入指定的快取中。 @CachePut 註解一般用於更新快取數據,相當於快取使用的是寫入模式中的雙寫模式。

@Service
public class MyService {

    @Autowired
    private MyRepository repository;

    @CachePut(value = "myCache", key = "#entity.id")
    public void saveEntity(MyEntity entity) {
        repository.save(entity);
    }
}

@CacheEvict

標註了 @CacheEvict 註解的方法被呼叫時,會從快取中移除已儲存的資料。 @CacheEvict 註解一般用於刪除快取數據,相當於快取使用的是寫入模式中的失效模式。

SpringBoot專案中怎麼使用快取Cache

@Service
public class MyService {

    @Autowired
    private MyRepository repository;

     @CacheEvict(value = "myCache", key = "#id")
    public void deleteEntityById(Long id) {
        repository.deleteById(id);
    }
}

@Caching

#@Caching 註解用於在一個方法或類別上,同時指定多個Spring Cache 相關的註解。

SpringBoot專案中怎麼使用快取Cache

範例1:@Caching註解中的evict屬性指定在呼叫方法 saveEntity# 時失效兩個緩存。

@Service
public class MyService {

    @Autowired
    private MyRepository repository;

    @Cacheable(value = "myCache", key = "#id")
    public MyEntity getEntityById(Long id) {
        return repository.findById(id).orElse(null);
    }

    @Caching(evict = {
        @CacheEvict(value = "myCache", key = "#entity.id"),
        @CacheEvict(value = "otherCache", key = "#entity.id")
    })
    public void saveEntity(MyEntity entity) {
        repository.save(entity);
    }

}

範例2:呼叫getEntityById方法時,Spring會先檢查結果是否已經快取在myCache快取中。如果是,Spring 將傳回快取的結果而不是執行該方法。如果結果尚未緩存,Spring 將執行該方法並將結果緩存在 myCache 快取中。方法執行後,Spring會根據@CacheEvict註解從otherCache快取中移除快取結果。

@Service
public class MyService {

    @Caching(
        cacheable = {
            @Cacheable(value = "myCache", key = "#id")
        },
        evict = {
            @CacheEvict(value = "otherCache", key = "#id")
        }
    )
    public MyEntity getEntityById(Long id) {
        return repository.findById(id).orElse(null);
    }

}

範例3:當呼叫saveData方法時,Spring會根據@CacheEvict註解先從otherCache快取中移除資料。然後,Spring 將執行該方法並將結果儲存到資料庫或外部 API。

方法執行後,Spring 會依照@CachePut註解將結果加入 myCachemyOtherCache 和 myThirdCache 快取中。 Spring 也會根據@Cacheable註解檢查結果是否已快取在 myFourthCache 和 myFifthCache 快取中。如果結果尚未緩存,Spring 會將結果緩存在適當的緩存中。如果結果已經被緩存,Spring 將傳回快取的結果,而不是再次執行該方法。

@Service
public class MyService {

    @Caching(
        put = {
            @CachePut(value = "myCache", key = "#result.id"),
            @CachePut(value = "myOtherCache", key = "#result.id"),
            @CachePut(value = "myThirdCache", key = "#result.name")
        },
        evict = {
            @CacheEvict(value = "otherCache", key = "#id")
        },
        cacheable = {
            @Cacheable(value = "myFourthCache", key = "#id"),
            @Cacheable(value = "myFifthCache", key = "#result.id")
        }
    )
    public MyEntity saveData(Long id, String name) {
        // Code to save data to a database or external API
        MyEntity entity = new MyEntity(id, name);
        return entity;
    }

}

@CacheConfig

通过@CacheConfig 注解,我们可以将一些缓存配置简化到类级别的一个地方,这样我们就不必多次声明相关值:

@CacheConfig(cacheNames={"myCache"})
@Service
public class MyService {

    @Autowired
    private MyRepository repository;

    @Cacheable(key = "#id")
    public MyEntity getEntityById(Long id) {
        return repository.findById(id).orElse(null);
    }

    @CachePut(key = "#entity.id")
    public void saveEntity(MyEntity entity) {
        repository.save(entity);
    }

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

Condition & Unless

  • condition作用:指定缓存的条件(满足什么条件才缓存),可用 SpEL 表达式(如 #id>0,表示当入参 id 大于 0 时才缓存)

  • unless作用 : 否定缓存,即满足 unless 指定的条件时,方法的结果不进行缓存,使用 unless 时可以在调用的方法获取到结果之后再进行判断(如 #result == null,表示如果结果为 null 时不缓存)

//when id >10, the @CachePut works. 
@CachePut(key = "#entity.id", condition="#entity.id > 10")
public void saveEntity(MyEntity entity) {
	repository.save(entity);
}


//when result != null, the @CachePut works.
@CachePut(key = "#id", condition="#result == null")
public void saveEntity1(MyEntity entity) {
	repository.save(entity);
}

清理全部缓存

通过allEntriesbeforeInvocation属性可以来清除全部缓存数据,不过allEntries是方法调用后清理,beforeInvocation是方法调用前清理。

//方法调用完成之后,清理所有缓存
@CacheEvict(value="myCache",allEntries=true)
public void delectAll() {
    repository.deleteAll();
}

//方法调用之前,清除所有缓存
@CacheEvict(value="myCache",beforeInvocation=true)
public void delectAll() {
    repository.deleteAll();
}

SpEL表达式

Spring Cache注解中频繁用到SpEL表达式,那么具体如何使用呢?

SpEL 表达式的语法

SpringBoot專案中怎麼使用快取Cache

Spring Cache可用的变量

SpringBoot專案中怎麼使用快取Cache

最佳实践

通过Spring缓存注解可以快速优雅地在我们项目中实现缓存的操作,但是在双写模式或者失效模式下,可能会出现缓存数据一致性问题(读取到脏数据),Spring Cache 暂时没办法解决。最后我们再总结下Spring Cache使用的一些最佳实践。

  • 只缓存经常读取的数据:缓存可以显着提高性能,但只缓存经常访问的数据很重要。很少或从不访问的缓存数据会占用宝贵的内存资源,从而导致性能问题。

  • 根据应用程序的特定需求选择合适的缓存提供程序和策略。SpringBoot 支持多种缓存提供程序,包括 EhcacheHazelcast 和 Redis

  • 使用缓存时请注意潜在的线程安全问题。对缓存的并发访问可能会导致数据不一致或不正确,因此选择线程安全的缓存提供程序并在必要时使用适当的同步机制非常重要。

  • 避免过度缓存。缓存对于提高性能很有用,但过多的缓存实际上会消耗宝贵的内存资源,从而损害性能。在缓存频繁使用的数据和允许垃圾收集不常用的数据之间取得平衡很重要。

  • 使用适当的缓存逐出策略。使用缓存时,重要的是定义适当的缓存逐出策略以确保在必要时从缓存中删除旧的或陈旧的数据。

  • 使用适当的缓存键设计。缓存键对于每个数据项都应该是唯一的,并且应该考虑可能影响缓存数据的任何相关参数,例如用户 ID、时间或位置。

  • 常规数据(读多写少、即时性与一致性要求不高的数据)完全可以使用 Spring Cache,至于写模式下缓存数据一致性问题的解决,只要缓存数据有设置过期时间就足够了。

  • 特殊数据(读多写多、即时性与一致性要求非常高的数据),不能使用 Spring Cache,建议考虑特殊的设计(例如使用 Cancal 中间件等)。

以上是SpringBoot專案中怎麼使用快取Cache的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文轉載於:yisu.com。如有侵權,請聯絡admin@php.cn刪除