日本免费高清视频-国产福利视频导航-黄色在线播放国产-天天操天天操天天操天天操|www.shdianci.com

學(xué)無(wú)先后,達(dá)者為師

網(wǎng)站首頁(yè) 編程語(yǔ)言 正文

SpringCache通用緩存學(xué)習(xí)

作者:編程指南針 更新時(shí)間: 2022-06-08 編程語(yǔ)言

?

1.1 介紹

Spring Cache是一個(gè)框架,實(shí)現(xiàn)了基于注解的緩存功能,只需要簡(jiǎn)單地加一個(gè)注解,就能實(shí)現(xiàn)緩存功能,大大簡(jiǎn)化我們?cè)跇I(yè)務(wù)中操作緩存的代碼。

Spring Cache只是提供了一層抽象,底層可以切換不同的cache實(shí)現(xiàn)。具體就是通過(guò)CacheManager接口來(lái)統(tǒng)一不同的緩存技術(shù)。CacheManager是Spring提供的各種緩存技術(shù)抽象接口。

針對(duì)不同的緩存技術(shù)需要實(shí)現(xiàn)不同的CacheManager:

CacheManager 描述
EhCacheCacheManager 使用EhCache作為緩存技術(shù)
GuavaCacheManager 使用Google的GuavaCache作為緩存技術(shù)
RedisCacheManager 使用Redis作為緩存技術(shù)

1.2 注解

在SpringCache中提供了很多緩存操作的注解,常見(jiàn)的是以下的幾個(gè):

注解 說(shuō)明
@EnableCaching 開(kāi)啟緩存注解功能
@Cacheable 在方法執(zhí)行前spring先查看緩存中是否有數(shù)據(jù),如果有數(shù)據(jù),則直接返回緩存數(shù)據(jù);若沒(méi)有數(shù)據(jù),調(diào)用方法并將方法返回值放到緩存中
@CachePut 將方法的返回值放到緩存中
@CacheEvict 將一條或多條數(shù)據(jù)從緩存中刪除

在spring boot項(xiàng)目中,使用緩存技術(shù)只需在項(xiàng)目中導(dǎo)入相關(guān)緩存技術(shù)的依賴包,并在啟動(dòng)類上使用@EnableCaching開(kāi)啟緩存支持即可。

例如,使用Redis作為緩存技術(shù),只需要導(dǎo)入Spring data Redis的maven坐標(biāo)即可。

1.3 入門(mén)程序

接下來(lái),我們將通過(guò)一個(gè)入門(mén)案例來(lái)演示一下SpringCache的常見(jiàn)用法。上面我們提到,SpringCache可以集成不同的緩存技術(shù),如Redis、Ehcache甚至我們可以使用Map來(lái)緩存數(shù)據(jù), 接下來(lái)我們?cè)谘菔镜臅r(shí)候,就先通過(guò)一個(gè)Map來(lái)緩存數(shù)據(jù),最后我們?cè)贀Q成Redis來(lái)緩存。

1.3.1 環(huán)境準(zhǔn)備

1). 數(shù)據(jù)庫(kù)準(zhǔn)備

將今天資料中的SQL腳本直接導(dǎo)入數(shù)據(jù)庫(kù)中。

DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(50) DEFAULT NULL,
  `age` int(11) DEFAULT NULL,
  `address` varchar(100) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

2). 導(dǎo)入基礎(chǔ)工程

基礎(chǔ)環(huán)境的代碼,在我們今天的資料中已經(jīng)準(zhǔn)備好了, 大家只需要將這個(gè)工程導(dǎo)入進(jìn)來(lái)就可以了。

package com.znz.entity;

import lombok.Data;
import java.io.Serializable;

@Data
public class User implements Serializable {

    private static final long serialVersionUID = 1L;

    private Long id;

    private String name;

    private int age;

    private String address;

}

package com.znz.controller;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.itheima.entity.User;
import com.itheima.service.UserService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.web.bind.annotation.*;
import java.util.ArrayList;
import java.util.List;

@RestController
@RequestMapping("/user")
@Slf4j
public class UserController {

    @Autowired
    private CacheManager cacheManager;

    @Autowired
    private UserService userService;

    /**
     * CachePut:將方法返回值放入緩存
     * value:緩存的名稱,每個(gè)緩存名稱下面可以有多個(gè)key
     * key:緩存的key
     */
    @CachePut(value = "userCache",key = "#user.id")
    @PostMapping
    public User save(User user){
        userService.save(user);
        return user;
    }

    /**
     * CacheEvict:清理指定緩存
     * value:緩存的名稱,每個(gè)緩存名稱下面可以有多個(gè)key
     * key:緩存的key
     */
    @CacheEvict(value = "userCache",key = "#p0")
    //@CacheEvict(value = "userCache",key = "#root.args[0]")
    //@CacheEvict(value = "userCache",key = "#id")
    @DeleteMapping("/{id}")
    public void delete(@PathVariable Long id){
        userService.removeById(id);
    }

    //@CacheEvict(value = "userCache",key = "#p0.id")
    //@CacheEvict(value = "userCache",key = "#user.id")
    //@CacheEvict(value = "userCache",key = "#root.args[0].id")
    @CacheEvict(value = "userCache",key = "#result.id")
    @PutMapping
    public User update(User user){
        userService.updateById(user);
        return user;
    }

    /**
     * Cacheable:在方法執(zhí)行前spring先查看緩存中是否有數(shù)據(jù),如果有數(shù)據(jù),則直接返回緩存數(shù)據(jù);若沒(méi)有數(shù)據(jù),調(diào)用方法并將方法返回值放到緩存中
     * value:緩存的名稱,每個(gè)緩存名稱下面可以有多個(gè)key
     * key:緩存的key
     * condition:條件,滿足條件時(shí)才緩存數(shù)據(jù)
     * unless:滿足條件則不緩存
     */
    @Cacheable(value = "userCache",key = "#id",unless = "#result == null")
    @GetMapping("/{id}")
    public User getById(@PathVariable Long id){
        User user = userService.getById(id);
        return user;
    }

    @Cacheable(value = "userCache",key = "#user.id + '_' + #user.name")
    @GetMapping("/list")
    public List<User> list(User user){
        LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(user.getId() != null,User::getId,user.getId());
        queryWrapper.eq(user.getName() != null,User::getName,user.getName());
        List<User> list = userService.list(queryWrapper);
        return list;
    }
}

package com.znz.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.itheima.entity.User;
import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface UserMapper extends BaseMapper<User>{
}

package?com.znz.service;

import com.baomidou.mybatisplus.extension.service.IService;
import com.itheima.entity.User;

public interface UserService extends IService<User> {
}

package com.znz.service.impl;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.itheima.entity.User;
import com.itheima.mapper.UserMapper;
import com.itheima.service.UserService;
import org.springframework.stereotype.Service;

@Service
public class UserServiceImpl extends ServiceImpl<UserMapper,User> implements UserService {
}

由于SpringCache的基本功能是Spring核心(spring-context)中提供的,所以目前我們進(jìn)行簡(jiǎn)單的SpringCache測(cè)試,是可以不用額外引入其他依賴的。

3). 注入CacheManager

我們可以在UserController注入一個(gè)CacheManager,在Debug時(shí),我們可以通過(guò)CacheManager跟蹤緩存中數(shù)據(jù)的變化。

而在上述的這幾個(gè)實(shí)現(xiàn)中,默認(rèn)使用的是 ConcurrentMapCacheManager。稍后我們可以通過(guò)斷點(diǎn)的形式跟蹤緩存數(shù)據(jù)的變化。

4). 引導(dǎo)類上加@EnableCaching

在引導(dǎo)類上加該注解,就代表當(dāng)前項(xiàng)目開(kāi)啟緩存注解功能。

import lombok.extern.slf4j.Slf4j;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.cache.annotation.EnableCaching;
@Slf4j@SpringBootApplication@EnableCachingpublic class CacheDemoApplication {    public static void main(String[] args) {        SpringApplication.run(CacheDemoApplication.class,args);        log.info("項(xiàng)目啟動(dòng)成功...");    }}

1.3.2 @CachePut注解

@CachePut 說(shuō)明:

作用: 將方法返回值,放入緩存

value: 緩存的名稱, 每個(gè)緩存名稱下面可以有很多key

key: 緩存的key ?----------> 支持Spring的表達(dá)式語(yǔ)言SPEL語(yǔ)法

1). 在save方法上加注解@CachePut

當(dāng)前UserController的save方法是用來(lái)保存用戶信息的,我們希望在該用戶信息保存到數(shù)據(jù)庫(kù)的同時(shí),也往緩存中緩存一份數(shù)據(jù),我們可以在save方法上加上注解 @CachePut,用法如下:

?/**
?* CachePut:將方法返回值放入緩存
?* value:緩存的名稱,每個(gè)緩存名稱下面可以有多個(gè)key
?* key:緩存的key
?*/
?@CachePut(value?=?"userCache",?key?=?"#user.id")
?@PostMapping
?public?User?save(User?user){
??? ?userService.save(user);
??? ?return?user;
?}

key的寫(xiě)法如下:

#user.id : #user指的是方法形參的名稱, id指的是user的id屬性 , 也就是使用user的id屬性作為key ;

#user.name: #user指的是方法形參的名稱, name指的是user的name屬性 ,也就是使用user的name屬性作為key ;

#result.id : #result代表方法返回值,該表達(dá)式 代表以返回對(duì)象的id屬性作為key ;

#result.name : #result代表方法返回值,該表達(dá)式 代表以返回對(duì)象的name屬性作為key ;

2). 測(cè)試

啟動(dòng)服務(wù),通過(guò)postman請(qǐng)求訪問(wèn)UserController的方法, 然后通過(guò)斷點(diǎn)的形式跟蹤緩存數(shù)據(jù)。

第一次訪問(wèn)時(shí),緩存中的數(shù)據(jù)是空的,因?yàn)閟ave方法執(zhí)行完畢后才會(huì)緩存數(shù)據(jù)。

第二次訪問(wèn)時(shí),我們通過(guò)debug可以看到已經(jīng)有一條數(shù)據(jù)了,就是上次保存的數(shù)據(jù),已經(jīng)緩存了,緩存的key就是用戶的id。

注意: 上述的演示,最終的數(shù)據(jù),實(shí)際上是緩存在ConcurrentHashMap中,那么當(dāng)我們的服務(wù)器重啟之后,緩存中的數(shù)據(jù)就會(huì)丟失。我們后面使用了Redis來(lái)緩存就不存在這樣的問(wèn)題了。

1.3.3 @CacheEvict注解

@CacheEvict 說(shuō)明:

作用: 清理指定緩存

value: 緩存的名稱,每個(gè)緩存名稱下面可以有多個(gè)key

key: 緩存的key ?----------> 支持Spring的表達(dá)式語(yǔ)言SPEL語(yǔ)法

1). 在 delete 方法上加注解@CacheEvict

當(dāng)我們?cè)趧h除數(shù)據(jù)庫(kù)user表的數(shù)據(jù)的時(shí)候,我們需要?jiǎng)h除緩存中對(duì)應(yīng)的數(shù)據(jù),此時(shí)就可以使用@CacheEvict注解, 具體的使用方式如下:

?/**
?* CacheEvict:清理指定緩存
?* value:緩存的名稱,每個(gè)緩存名稱下面可以有多個(gè)key
?* key:緩存的key
?*/
?@CacheEvict(value?=?"userCache",key?=?"#p0") ?//#p0 代表第一個(gè)參數(shù)
?//@CacheEvict(value = "userCache",key = "#root.args[0]") //#root.args[0] 代表第一個(gè)參數(shù)
?//@CacheEvict(value = "userCache",key = "#id") //#id 代表變量名為id的參數(shù)
?@DeleteMapping("/{id}")
?public?void?delete(@PathVariable?Long?id){
??? ?userService.removeById(id);
?}

2). 測(cè)試

要測(cè)試緩存的刪除,我們先訪問(wèn)save方法4次,保存4條數(shù)據(jù)到數(shù)據(jù)庫(kù)的同時(shí),也保存到緩存中,最終我們可以通過(guò)debug看到緩存中的數(shù)據(jù)信息。然后我們?cè)谕ㄟ^(guò)postman訪問(wèn)delete方法, 如下:

刪除數(shù)據(jù)時(shí),通過(guò)debug我們可以看到已經(jīng)緩存的4條數(shù)據(jù):

當(dāng)執(zhí)行完delete操作之后,我們?cè)俅伪4嬉粭l數(shù)據(jù),在保存的時(shí)候debug查看一下刪除的ID值是否已經(jīng)被刪除。

3). 在 update 方法上加注解@CacheEvict

在更新數(shù)據(jù)之后,數(shù)據(jù)庫(kù)的數(shù)據(jù)已經(jīng)發(fā)生了變更,我們需要將緩存中對(duì)應(yīng)的數(shù)據(jù)刪除掉,避免出現(xiàn)數(shù)據(jù)庫(kù)數(shù)據(jù)與緩存數(shù)據(jù)不一致的情況。

?
//@CacheEvict(value = "userCache",key = "#p0.id") ? //第一個(gè)參數(shù)的id屬性
?//@CacheEvict(value = "userCache",key = "#user.id") //參數(shù)名為user參數(shù)的id屬性
?//@CacheEvict(value = "userCache",key = "#root.args[0].id") //第一個(gè)參數(shù)的id屬性
?@CacheEvict(value?=?"userCache",key?=?"#result.id") ? ? ? ??//返回值的id屬性
?@PutMapping
?public?User?update(User?user){
??? ?userService.updateById(user);
??? ?return?user;
?}

加上注解之后,我們可以重啟服務(wù),然后測(cè)試方式,基本和上述相同,先緩存數(shù)據(jù),然后再更新某一條數(shù)據(jù),通過(guò)debug的形式查詢緩存數(shù)據(jù)的情況。

1.3.4 @Cacheable注解

@Cacheable 說(shuō)明:

作用: 在方法執(zhí)行前,spring先查看緩存中是否有數(shù)據(jù),如果有數(shù)據(jù),則直接返回緩存數(shù)據(jù);若沒(méi)有數(shù)據(jù),調(diào)用方法并將方法返回值放到緩存中

value: 緩存的名稱,每個(gè)緩存名稱下面可以有多個(gè)key

key: 緩存的key ?----------> 支持Spring的表達(dá)式語(yǔ)言SPEL語(yǔ)法

1). 在getById上加注解@Cacheable

?/**
?* Cacheable:在方法執(zhí)行前spring先查看緩存中是否有數(shù)據(jù),如果有數(shù)據(jù),則直接返回緩存數(shù)據(jù);若沒(méi)有數(shù)據(jù),調(diào)用方法并將方法返回值放到緩存中
?* value:緩存的名稱,每個(gè)緩存名稱下面可以有多個(gè)key
?* key:緩存的key
?*/
?@Cacheable(value?=?"userCache",key?=?"#id")
?@GetMapping("/{id}")
?public?User?getById(@PathVariable?Long?id){
??? ?User?user?=?userService.getById(id);
??? ?return?user;
?}

2). 測(cè)試

我們可以重啟服務(wù),然后通過(guò)debug斷點(diǎn)跟蹤程序執(zhí)行。我們發(fā)現(xiàn),第一次訪問(wèn),會(huì)請(qǐng)求我們controller的方法,查詢數(shù)據(jù)庫(kù)。后面再查詢相同的id,就直接獲取到數(shù)據(jù)庫(kù),不用再查詢數(shù)據(jù)庫(kù)了,就說(shuō)明緩存生效了。

當(dāng)我們?cè)跍y(cè)試時(shí),查詢一個(gè)數(shù)據(jù)庫(kù)不存在的id值,第一次查詢緩存中沒(méi)有,也會(huì)查詢數(shù)據(jù)庫(kù)。而第二次再查詢時(shí),會(huì)發(fā)現(xiàn),不再查詢數(shù)據(jù)庫(kù)了,而是直接返回,那也就是說(shuō)如果根據(jù)ID沒(méi)有查詢到數(shù)據(jù),那么會(huì)自動(dòng)緩存一個(gè)null值。我們可以通過(guò)debug,驗(yàn)證一下:

我們能不能做到,當(dāng)查詢到的值不為null時(shí),再進(jìn)行緩存,如果為null,則不緩存呢? 答案是可以的。

3). 緩存非null值

在@Cacheable注解中,提供了兩個(gè)屬性分別為:condition, unless 。

condition : 表示滿足什么條件, 再進(jìn)行緩存 ;

unless : 表示滿足條件則不緩存 ; 與上述的condition是反向的 ;

具體實(shí)現(xiàn)方式如下:

?/**
??* Cacheable:在方法執(zhí)行前spring先查看緩存中是否有數(shù)據(jù),如果有數(shù)據(jù),則直接返回緩存數(shù)據(jù);若沒(méi)有數(shù)據(jù),調(diào)用方法并將方法返回值放到緩存中
??* value:緩存的名稱,每個(gè)緩存名稱下面可以有多個(gè)key
??* key:緩存的key
??* condition:條件,滿足條件時(shí)才緩存數(shù)據(jù)
??* unless:滿足條件則不緩存
??*/
?@Cacheable(value?=?"userCache",key?=?"#id",?unless?=?"#result == null")
?@GetMapping("/{id}")
?public?User?getById(@PathVariable?Long?id){
??? ?User?user?=?userService.getById(id);
??? ?return?user;
?}

注意:此處,我們使用的時(shí)候只能夠使用 unless, 因?yàn)樵赾ondition中,我們是無(wú)法獲取到結(jié)果 #result的。

4). 在list方法上加注解@Cacheable

在list方法中進(jìn)行查詢時(shí),有兩個(gè)查詢條件,如果傳遞了id,根據(jù)id查詢;如果傳遞了name, 根據(jù)name查詢,那么我們緩存的key在設(shè)計(jì)的時(shí)候,就需要既包含id,又包含name。具體的代碼實(shí)現(xiàn)如下:

?@Cacheable(value?=?"userCache",key?=?"#user.id + '_' + #user.name")
?@GetMapping("/list")
?public?List<User>?list(User?user){
??? ?LambdaQueryWrapper<User>?queryWrapper?=?new?LambdaQueryWrapper<>();
??? ?queryWrapper.eq(user.getId()?!=?null,User::getId,user.getId());
??? ?queryWrapper.eq(user.getName()?!=?null,User::getName,user.getName());
??? ?List<User>?list?=?userService.list(queryWrapper);
??? ?return?list;
?}

然后再次重啟服務(wù),進(jìn)行測(cè)試。

第一次查詢時(shí),需要查詢數(shù)據(jù)庫(kù),在后續(xù)的查詢中,就直接查詢了緩存,不再查詢數(shù)據(jù)庫(kù)了。

?

2 ?集成Redis

在使用上述默認(rèn)的ConcurrentHashMap做緩存時(shí),服務(wù)重啟之后,之前緩存的數(shù)據(jù)就全部丟失了,操作起來(lái)并不友好。在項(xiàng)目中使用,我們會(huì)選擇使用redis來(lái)做緩存,主要需要操作以下幾步:

1). pom.xml

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

2). application.yml

?spring:
??redis:
??? host:?192.168.200.200
??? port:?6379
??? password:?root@123456
??? database:?0
??cache:
??? redis:
??? ? time-to-live:?1800000 ??#設(shè)置緩存過(guò)期時(shí)間,可選

3). 測(cè)試

重新啟動(dòng)項(xiàng)目,通過(guò)postman發(fā)送根據(jù)id查詢數(shù)據(jù)的請(qǐng)求,然后通過(guò)redis的圖形化界面工具,查看redis中是否可以正常的緩存數(shù)據(jù)。

以上是針對(duì)SpringCache通用緩存框架的應(yīng)用介紹,有了它以后,可以理方便的在我們的程序中使用緩存,而且可以無(wú)縫移植。

?

原文鏈接:https://blog.csdn.net/whirlwind526/article/details/124929476

欄目分類
最近更新