sky-take-out項目中Redis的使用示例詳解
Spring Cache 是 Spring Framework 提供的一個抽象層,用于簡化應(yīng)用程序中的緩存管理。它允許開發(fā)者以聲明式的方式將緩存添加到應(yīng)用中,而不需要手動編寫冗長的緩存邏輯代碼。Spring Cache 通過提供一系列注解和配置選項,使得集成緩存變得非常簡單。將 Redis 集成到 Spring 應(yīng)用程序中,并將其作為 Spring Cache 的緩存提供者。這樣做的好處是可以在開發(fā)過程中專注于業(yè)務(wù)邏輯的實現(xiàn),只需通過 Spring Cache 提供的簡單注解即可完成復(fù)雜的緩存管理任務(wù)。與此同時,你還能享受到 Redis 提供的高效性能和豐富的功能特性。這種方法不僅提高了開發(fā)效率,還增強了應(yīng)用的可擴展性和性能表現(xiàn)。
Spring Cache主要特性
- 聲明式緩存:通過使用注解(如
@Cacheable,@CachePut,@CacheEvict等),可以非常方便地在方法上定義緩存行為。 - 多種緩存實現(xiàn)支持:Spring Cache 抽象層支持多種緩存解決方案,包括但不限于 Ehcache, Hazelcast, Infinispan, JCache (JSR-107), Guava Cache, Caffeine, Redis 等。
- 靈活的配置:可以通過 XML 配置、Java Config 或者自動配置來設(shè)置緩存管理器(Cache Manager)。
- 條件緩存:可以根據(jù)特定條件決定是否執(zhí)行緩存操作(例如,使用
condition屬性)。 - SpEL 支持:可以在注解中使用 Spring Expression Language (SpEL) 來動態(tài)指定緩存鍵或其他屬性值。
核心注解
1.@Cacheable
此注解通常用于查詢操作,當(dāng)調(diào)用被注解的方法時,如果緩存中存在對應(yīng)的數(shù)據(jù),則直接返回緩存結(jié)果,而不是執(zhí)行方法體。
@GetMapping("/list")
@ApiOperation("根據(jù)分類id查詢套餐")
@Cacheable(cacheNames = "setmealCache",key = "#categoryId")
public Result<List<Setmeal>> list(Long categoryId) {
Setmeal setmeal = new Setmeal();
setmeal.setCategoryId(categoryId);
setmeal.setStatus(StatusConstant.ENABLE);
List<Setmeal> list = setmealService.list(setmeal);
return Result.success(list);
}value: 指定緩存的名字。key: 可選參數(shù),用來指定緩存的鍵。可以使用 SpEL 表達式。
2.@CachePut
與 @Cacheable 不同,@CachePut 總是會執(zhí)行方法,并將方法的結(jié)果放入緩存中。適用于更新操作。
@GetMapping("/{id}")
@ApiOperation("根據(jù)id查詢套餐")
@CachePut(cacheNames = "setmealCache",key = "#categoryId")
public Result<SetmealVO> getById(@PathVariable Long id) {
SetmealVO setmealVO = setmealService.getByIdWithDish(id);
return Result.success(setmealVO);
}
3.@CacheEvict
用于清除緩存,適用于刪除或無效化數(shù)據(jù)的操作。
@PostMapping
@ApiOperation("新增套餐")
@CacheEvict(cacheNames = "setmealCache",key = "#setmealDTO.categoryId")
public Result save(@RequestBody SetmealDTO setmealDTO) {
setmealService.saveWithDish(setmealDTO);
return Result.success();
}
value: 指定緩存的名字。key: 用來指定緩存的鍵。可以使用 SpEL 表達式。
@CacheEvict(cacheNames = "setmealCache", allEntries = true)
public Result startOrStop(@PathVariable Integer status, Long id) {
setmealService.startOrStop(status, id);
return Result.success();
}
@CacheEvict(cacheNames = "setmealCache", allEntries = true) 被用來清除名為 setmealCache 的緩存中的所有條目。
cacheNames/value:指定要操作的緩存名稱。在這個例子中是
"setmealCache"。這兩個屬性是等價的,用cacheNames或者value來指定緩存名。allEntries:一個布爾值,默認為
false。如果設(shè)置為true,則表示刪除指定緩存中的所有條目。若為false,則根據(jù)key屬性(如果沒有提供key,則默認為空)來決定刪除哪個具體的緩存項。在例子中,由于allEntries = true,所以無論是否有特定的鍵被定義,都會清空整個setmealCache緩存。beforeInvocation:一個布爾值,默認為
false。如果設(shè)置為true,那么緩存的清除會在方法調(diào)用之前發(fā)生;否則,在方法成功執(zhí)行之后才會進行清除操作。這有助于處理方法執(zhí)行過程中可能發(fā)生的異常情況,避免不必要的緩存清除。使用
allEntries = true會一次性清除指定緩存中的所有條目,這對于緩存命中率和性能有一定的影響,特別是在緩存非常大的情況下。因此,應(yīng)該謹慎使用,并確保這是必要的操作。
不是所有 Redis 的功能都可以通過 Spring Cache 注解實現(xiàn)。
?Spring Cache 注解能做什么?
Spring Cache 的設(shè)計初衷是簡化緩存操作,提供一種統(tǒng)一的、聲明式的緩存管理方式,適用于常見的緩存場景,比如:
- 緩存方法結(jié)果(
@Cacheable) - 更新緩存(
@CachePut) - 刪除緩存(
@CacheEvict) - 組合多個緩存操作(
@Caching)
這些注解非常適合用于:
- 讀多寫少的業(yè)務(wù)場景(如查詢接口)
- 需要緩存方法返回值的場景
- 避免重復(fù)執(zhí)行耗時操作(如數(shù)據(jù)庫查詢)
?Spring Cache 注解不能做什么?
雖然 Spring Cache 很方便,但它是一個抽象層,只封裝了緩存操作中最常見的部分。它并不能覆蓋 Redis 所有的強大功能。以下是一些 Spring Cache 注解無法實現(xiàn)或難以實現(xiàn)的功能:
| Redis 功能 | Spring Cache 注解是否支持 |
|---|---|
| 字符串、哈希、列表、集合、有序集合等數(shù)據(jù)結(jié)構(gòu)操作 | ? 不支持,需使用 RedisTemplate 或 StringRedisTemplate |
| 發(fā)布/訂閱消息(Pub/Sub) | ? 不支持 |
| Lua 腳本執(zhí)行 | ? 不支持 |
| 分布式鎖(SETNX / RedLock) | ? 不支持 |
| HyperLogLog、Geo、Bitmap 等高級數(shù)據(jù)結(jié)構(gòu) | ? 不支持 |
| 批量操作(Pipeline) | ? 不支持 |
| 事務(wù)(MULTI / EXEC) | ? 不支持 |
| TTL、KEYS、SCAN 等管理命令 | ? 不支持 |
| 自定義緩存過期策略(比如不同 key 有不同的 TTL) | ?? 部分支持,但不夠靈活 |
???舉個例子:
場景:緩存用戶信息
@Cacheable("user")
public User getUserById(Long id) {
return userRepository.findById(id);
}
? 適合用 Spring Cache,因為這是個典型的“查數(shù)據(jù)庫緩存結(jié)果”的場景。
場景:實現(xiàn)一個分布式鎖
Boolean success = redisTemplate.opsForValue().setIfAbsent("lock_key", "locked", 30, TimeUnit.SECONDS);
? 無法用 Spring Cache 實現(xiàn),必須用 RedisTemplate 直接操作 Redis。
?什么時候用 Spring Cache?
- 你只需要緩存方法的返回值。
- 你希望代碼更簡潔,不想寫大量緩存邏輯。
- 你希望統(tǒng)一管理緩存策略(如過期時間、緩存名稱)。
- 你未來可能更換緩存實現(xiàn)(比如從 Redis 換成 Caffeine)。
?什么時候要直接使用 RedisTemplate?
- 你需要使用 Redis 的高級功能(如 Hash、List、Pub/Sub、Lua、分布式鎖等)。
- 你需要更細粒度地控制 Redis 操作(如 Pipeline、事務(wù))。
- 你需要實現(xiàn)一些緩存抽象層無法覆蓋的特殊邏輯。
?最佳實踐:結(jié)合使用
在實際項目中,推薦結(jié)合使用 Spring Cache 和 RedisTemplate:
- 對于通用的緩存需求(如方法結(jié)果緩存),使用 Spring Cache 注解。
- 對于需要 Redis 高級特性的場景,使用
RedisTemplate直接操作 Redis。
?? 總結(jié)一句話:
Spring Cache 注解只能覆蓋 Redis 的部分緩存功能,不能替代 Redis 的全部能力。
對于復(fù)雜的 Redis 操作,仍需使用RedisTemplate。
如果正在開發(fā)一個中大型項目,建議:
- 對通用緩存邏輯使用 Spring Cache 注解;
- 對 Redis 高級功能單獨封裝成 Redis 工具類或服務(wù)類,使用
RedisTemplate來實現(xiàn)。
這樣既能享受注解帶來的便捷,又能保留 Redis 的靈活性和強大功能。
1. 字符串、哈希、列表、集合、有序集合等數(shù)據(jù)結(jié)構(gòu)操作
Spring Cache 注解的支持情況:
- 不支持。Spring Cache 主要用于緩存方法的結(jié)果,并不直接支持對 Redis 的各種數(shù)據(jù)類型(如字符串、哈希、列表、集合和有序集合)的操作。
解決方案:
使用 RedisTemplate 或 StringRedisTemplate 來進行更復(fù)雜的 Redis 數(shù)據(jù)類型操作。
示例:字符串操作
@Autowired
private StringRedisTemplate stringRedisTemplate;
public void stringOperationsExample(String key, String value) {
// 設(shè)置字符串值
stringRedisTemplate.opsForValue().set(key, value);
// 獲取字符串值
String retrievedValue = stringRedisTemplate.opsForValue().get(key);
System.out.println("Retrieved Value: " + retrievedValue);
}示例:哈希操作
public void hashOperationsExample(String key, String field, String value) {
// 設(shè)置哈希表中的字段值
stringRedisTemplate.opsForHash().put(key, field, value);
// 獲取哈希表中字段的值
Object fieldValue = stringRedisTemplate.opsForHash().get(key, field);
System.out.println("Field Value: " + fieldValue);
}示例:列表操作
public void listOperationsExample(String key, String... values) {
// 右側(cè)插入元素到列表
for (String value : values) {
stringRedisTemplate.opsForList().rightPush(key, value);
}
// 獲取列表中的所有元素
List<String> elements = stringRedisTemplate.opsForList().range(key, 0, -1);
System.out.println("List Elements: " + elements);
}
示例:集合操作
public void setOperationsExample(String key, String... members) {
// 添加成員到集合
for (String member : members) {
stringRedisTemplate.opsForSet().add(key, member);
}
// 獲取集合中的所有成員
Set<String> membersSet = stringRedisTemplate.opsForSet().members(key);
System.out.println("Members of Set: " + membersSet);
}示例:有序集合操作
public void sortedSetOperationsExample(String key, String member, double score) {
// 添加成員到有序集合并指定分數(shù)
stringRedisTemplate.opsForZSet().add(key, member, score);
// 獲取有序集合中的所有成員及其分數(shù)
Set<ZSetOperations.TypedTuple<String>> tuples = stringRedisTemplate.opsForZSet().rangeWithScores(key, 0, -1);
tuples.forEach(tuple -> System.out.println("Member: " + tuple.getValue() + ", Score: " + tuple.getScore()));
}2. 發(fā)布/訂閱消息(Pub/Sub)
Spring Cache 注解的支持情況:
- 不支持。Spring Cache 不支持發(fā)布/訂閱模式。
解決方案:
使用 RedisTemplate 進行發(fā)布/訂閱操作。
@Autowired
private StringRedisTemplate stringRedisTemplate;
// 訂閱者
public void subscribeToChannel(String channelName) {
stringRedisTemplate.getConnectionFactory().getConnection().subscribe((message, pattern) -> {
System.out.println("Received message on channel [" + new String(pattern) + "] with content: " + new String(message.getBody()));
}, channelName.getBytes());
}
// 發(fā)布者
public void publishToChannel(String channelName, String message) {
stringRedisTemplate.convertAndSend(channelName, message);
}3. Lua 腳本執(zhí)行
Spring Cache 注解的支持情況:
- 不支持。Spring Cache 不支持執(zhí)行 Lua 腳本。
解決方案:
使用 RedisTemplate 執(zhí)行 Lua 腳本。
@Autowired
private StringRedisTemplate stringRedisTemplate;
public void executeLuaScript() {
DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
redisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("lua_script.lua")));
redisScript.setResultType(Long.class);
List<String> keys = Arrays.asList("key1", "key2");
Long result = stringRedisTemplate.execute(redisScript, keys, "arg1");
System.out.println("Lua Script Result: " + result);
}4. 分布式鎖(SETNX / RedLock)
Spring Cache 注解的支持情況:
- 不支持。Spring Cache 不支持分布式鎖機制。
解決方案:
使用 RedisTemplate 實現(xiàn)分布式鎖。
@Autowired
private StringRedisTemplate stringRedisTemplate;
public boolean acquireLock(String lockKey, long timeout) throws InterruptedException {
long millisecondsTimeout = System.currentTimeMillis() + timeout;
while (System.currentTimeMillis() < millisecondsTimeout) {
Boolean success = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, "locked", Duration.ofMinutes(1));
if (Boolean.TRUE.equals(success)) {
return true;
}
Thread.sleep(100); // 等待一段時間后重試
}
return false;
}
public void releaseLock(String lockKey) {
stringRedisTemplate.delete(lockKey);
}5. HyperLogLog、Geo、Bitmap 等高級數(shù)據(jù)結(jié)構(gòu)
Spring Cache 注解的支持情況:
- 不支持。Spring Cache 不支持這些高級數(shù)據(jù)結(jié)構(gòu)。
解決方案:
使用 RedisTemplate 操作這些高級數(shù)據(jù)結(jié)構(gòu)。
Geo 操作示例:
public void geoOperationsExample() {
stringRedisTemplate.opsForGeo().add("cities",
new Point(13.361389, 38.115556), "Palermo",
new Point(15.087269, 37.502669), "Catania");
Distance distance = stringRedisTemplate.opsForGeo().distance("cities", "Palermo", "Catania", RedisGeoCommands.DistanceUnit.KILOMETERS);
System.out.println("Distance between Palermo and Catania: " + distance.getValue() + " km");
}6. 批量操作(Pipeline)
Spring Cache 注解的支持情況:
- 不支持。Spring Cache 不支持批量操作。
解決方案:
使用 RedisTemplate 進行批量操作。
public void pipelineOperationsExample() {
List<Object> results = stringRedisTemplate.executePipelined((RedisCallback<String>) connection -> {
connection.openPipeline();
connection.set("key1".getBytes(), "value1".getBytes());
connection.incr("key2".getBytes());
return null;
});
System.out.println("Pipeline Results: " + results);
}7. 事務(wù)(MULTI / EXEC)
Spring Cache 注解的支持情況:
- 不支持。Spring Cache 不支持事務(wù)操作。
解決方案:
使用 RedisTemplate 進行事務(wù)操作。
public void transactionOperationsExample() {
stringRedisTemplate.execute(new SessionCallback<List<Object>>() {
@Override
public List<Object> execute(RedisOperations operations) throws DataAccessException {
operations.multi();
operations.opsForValue().increment("key1");
operations.opsForValue().increment("key2");
return operations.exec(); // 執(zhí)行并返回結(jié)果
}
});
}8. TTL、KEYS、SCAN 等管理命令
Spring Cache 注解的支持情況:
- 部分支持。雖然可以設(shè)置全局的 TTL,但不能為每個鍵單獨設(shè)置不同的 TTL。
解決方案:
使用 RedisTemplate 設(shè)置 TTL 和執(zhí)行其他管理命令。
public void ttlOperationsExample(String key) {
// 設(shè)置TTL
stringRedisTemplate.expire(key, Duration.ofMinutes(10));
}
public void scanKeysExample() {
ScanOptions options = ScanOptions.scanOptions().match("*").count(100).build();
Cursor<Map.Entry<byte[], byte[]>> cursor = stringRedisTemplate.executeWithStickyConnection(
redisConnection -> new ConvertingCursor<>(redisConnection.scan(options),
entry -> new AbstractMap.SimpleEntry<>(new String(entry.getKey()), new String(entry.getValue()))));
while (cursor.hasNext()) {
Map.Entry<String, String> entry = cursor.next();
System.out.println("Key: " + entry.getKey() + ", Value: " + entry.getValue());
}
}通過上述例子可以看出,雖然 Spring Cache 提供了便捷的方式來管理緩存,但對于 Redis 的復(fù)雜功能,需要依賴 RedisTemplate 或 StringRedisTemplate 來實現(xiàn)。這種方式不僅保留了 Redis 的靈活性,同時也使得開發(fā)者能夠充分利用 Redis 的強大功能。
到此這篇關(guān)于sky-take-out項目中Redis的使用的文章就介紹到這了,更多相關(guān)sky-take-out redis使用內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
SpringMVC如何域?qū)ο蠊蚕頂?shù)據(jù)
在Spring MVC中,可以使用域?qū)ο髞砉蚕頂?shù)據(jù),域?qū)ο笫且粋€Map類型的對象,可以在請求處理方法之間共享數(shù)據(jù),本文給大家介紹SpringMVC 域?qū)ο蠊蚕頂?shù)據(jù)的示例代碼,一起看看吧2023-09-09
spring定時器@Scheduled異步調(diào)用方式
在Spring Boot中,@Schedule默認使用單線程執(zhí)行定時任務(wù),多個定時器會按順序執(zhí)行,為實現(xiàn)異步執(zhí)行,可以通過自定義線程池或?qū)崿F(xiàn)SchedulingConfigurer接口,使用自定義線程池可以保證多個定時器并發(fā)執(zhí)行2024-11-11
java map轉(zhuǎn)Multipart/form-data類型body實例
這篇文章主要介紹了java map轉(zhuǎn)Multipart/form-data類型body實例,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-05-05
解決Maven項目加載spring bean的配置xml文件會提示找不到問題
這篇文章主要介紹了解決Maven項目加載spring bean的配置xml文件會提示找不到問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-08-08
Spring學(xué)習(xí)筆記之RedisTemplate的配置與使用教程
這篇文章主要給大家介紹了關(guān)于Spring學(xué)習(xí)筆記之RedisTemplate配置與使用的相關(guān)資料,文中通過示例代碼介紹的非常詳細,對大家學(xué)習(xí)或者使用spring具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2018-06-06

