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

SpringBoot?AOP+Redis實(shí)現(xiàn)延時(shí)雙刪功能的實(shí)戰(zhàn)指南

 更新時(shí)間:2025年07月29日 08:24:11   作者:字節(jié)跳躍者  
這篇文章主要為大家詳細(xì)介紹了SpringBoot?AOP如何結(jié)合Redis實(shí)現(xiàn)延時(shí)雙刪功能,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以了解一下

一、業(yè)務(wù)場(chǎng)景

在多線程并發(fā)情況下,假設(shè)有兩個(gè)數(shù)據(jù)庫(kù)修改請(qǐng)求,為保證數(shù)據(jù)庫(kù)與redis的數(shù)據(jù)一致性,修改請(qǐng)求的實(shí)現(xiàn)中需要修改數(shù)據(jù)庫(kù)后,級(jí)聯(lián)修改Redis中的數(shù)據(jù)。

  • 請(qǐng)求一:A修改數(shù)據(jù)庫(kù)數(shù)據(jù) B修改Redis數(shù)據(jù)
  • 請(qǐng)求二:C修改數(shù)據(jù)庫(kù)數(shù)據(jù) D修改Redis數(shù)據(jù)

并發(fā)情況下就會(huì)存在A —> C —> D —> B的情況

一定要理解線程并發(fā)執(zhí)行多組原子操作執(zhí)行順序是可能存在交叉現(xiàn)象的

1、此時(shí)存在的問(wèn)題

A修改數(shù)據(jù)庫(kù)的數(shù)據(jù)最終保存到了Redis中,C在A之后也修改了數(shù)據(jù)庫(kù)數(shù)據(jù)。

此時(shí)出現(xiàn)了Redis中數(shù)據(jù)和數(shù)據(jù)庫(kù)數(shù)據(jù)不一致的情況,在后面的查詢過(guò)程中就會(huì)長(zhǎng)時(shí)間去先查Redis, 從而出現(xiàn)查詢到的數(shù)據(jù)并不是數(shù)據(jù)庫(kù)中的真實(shí)數(shù)據(jù)的嚴(yán)重問(wèn)題。

2、解決方案

在使用Redis時(shí),需要保持Redis和數(shù)據(jù)庫(kù)數(shù)據(jù)的一致性,最流行的解決方案之一就是延時(shí)雙刪策略。

注意:要知道經(jīng)常修改的數(shù)據(jù)表不適合使用Redis,因?yàn)殡p刪策略執(zhí)行的結(jié)果是把Redis中保存的那條數(shù)據(jù)刪除了,以后的查詢就都會(huì)去查詢數(shù)據(jù)庫(kù)。所以Redis使用的是讀遠(yuǎn)遠(yuǎn)大于改的數(shù)據(jù)緩存。

延時(shí)雙刪方案執(zhí)行步驟

  • 刪除緩存
  • 更新數(shù)據(jù)庫(kù)
  • 延時(shí)500毫秒 (根據(jù)具體業(yè)務(wù)設(shè)置延時(shí)執(zhí)行的時(shí)間)
  • 刪除緩存

3、為何要延時(shí)500毫秒

這是為了我們?cè)诘诙蝿h除Redis之前能完成數(shù)據(jù)庫(kù)的更新操作。假象一下,如果沒(méi)有第三步操作時(shí),有很大概率,在兩次刪除Redis操作執(zhí)行完畢之后,數(shù)據(jù)庫(kù)的數(shù)據(jù)還沒(méi)有更新,此時(shí)若有請(qǐng)求訪問(wèn)數(shù)據(jù),便會(huì)出現(xiàn)我們一開(kāi)始提到的那個(gè)問(wèn)題。

4、為何要兩次刪除緩存

如果我們沒(méi)有第二次刪除操作,此時(shí)有請(qǐng)求訪問(wèn)數(shù)據(jù),有可能是訪問(wèn)的之前未做修改的Redis數(shù)據(jù),刪除操作執(zhí)行后,Redis為空,有請(qǐng)求進(jìn)來(lái)時(shí),便會(huì)去訪問(wèn)數(shù)據(jù)庫(kù),此時(shí)數(shù)據(jù)庫(kù)中的數(shù)據(jù)已是更新后的數(shù)據(jù),保證了數(shù)據(jù)的一致性。

二、代碼實(shí)踐

1、引入Redis和SpringBoot AOP依賴

<!-- redis使用 -->
<dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- aop -->
<dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

2、編寫(xiě)自定義aop注解和切面

ClearAndReloadCache延時(shí)雙刪注解

/**
 *延時(shí)雙刪
 **/
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Target(ElementType.METHOD)
public @interface ClearAndReloadCache {
    String name() default "";
}

ClearAndReloadCacheAspect延時(shí)雙刪切面

@Aspect
@Component
public class ClearAndReloadCacheAspect {

@Autowired
private StringRedisTemplate stringRedisTemplate;

/**
* 切入點(diǎn)
*切入點(diǎn),基于注解實(shí)現(xiàn)的切入點(diǎn)  加上該注解的都是Aop切面的切入點(diǎn)
*
*/

@Pointcut("@annotation(com.pdh.cache.ClearAndReloadCache)")
public void pointCut(){

}
/**
* 環(huán)繞通知
* 環(huán)繞通知非常強(qiáng)大,可以決定目標(biāo)方法是否執(zhí)行,什么時(shí)候執(zhí)行,執(zhí)行時(shí)是否需要替換方法參數(shù),執(zhí)行完畢是否需要替換返回值。
* 環(huán)繞通知第一個(gè)參數(shù)必須是org.aspectj.lang.ProceedingJoinPoint類型
* @param proceedingJoinPoint
*/
@Around("pointCut()")
public Object aroundAdvice(ProceedingJoinPoint proceedingJoinPoint){
    System.out.println("----------- 環(huán)繞通知 -----------");
    System.out.println("環(huán)繞通知的目標(biāo)方法名:" + proceedingJoinPoint.getSignature().getName());

    Signature signature1 = proceedingJoinPoint.getSignature();
    MethodSignature methodSignature = (MethodSignature)signature1;
    Method targetMethod = methodSignature.getMethod();//方法對(duì)象
    ClearAndReloadCache annotation = targetMethod.getAnnotation(ClearAndReloadCache.class);//反射得到自定義注解的方法對(duì)象

    String name = annotation.name();//獲取自定義注解的方法對(duì)象的參數(shù)即name
    Set<String> keys = stringRedisTemplate.keys("*" + name + "*");//模糊定義key
    stringRedisTemplate.delete(keys);//模糊刪除redis的key值

    //執(zhí)行加入雙刪注解的改動(dòng)數(shù)據(jù)庫(kù)的業(yè)務(wù) 即controller中的方法業(yè)務(wù)
    Object proceed = null;
    try {
        proceed = proceedingJoinPoint.proceed();
    } catch (Throwable throwable) {
        throwable.printStackTrace();
    }

    //開(kāi)一個(gè)線程 延遲1秒(此處是1秒舉例,可以改成自己的業(yè)務(wù))
    // 在線程中延遲刪除  同時(shí)將業(yè)務(wù)代碼的結(jié)果返回 這樣不影響業(yè)務(wù)代碼的執(zhí)行
    new Thread(() -> {
        try {
            Thread.sleep(1000);
            Set<String> keys1 = stringRedisTemplate.keys("*" + name + "*");//模糊刪除
            stringRedisTemplate.delete(keys1);
            System.out.println("-----------1秒鐘后,在線程中延遲刪除完畢 -----------");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }).start();

    return proceed;//返回業(yè)務(wù)代碼的值
    }
}

3、application.yml

server:
  port: 8082

spring:
  # redis setting
  redis:
    host: localhost
    port: 6379

  # cache setting
  cache:
    redis:
      time-to-live: 60000 # 60s

  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/test
    username: root
    password: 1234

# mp setting
mybatis-plus:
  mapper-locations: classpath*:com/pdh/mapper/*.xml
  global-config:
    db-config:
      table-prefix:
  configuration:
    # log of sql
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
    # hump
    map-underscore-to-camel-case: true

4、user_db.sql腳本

用于生產(chǎn)測(cè)試數(shù)據(jù)

DROP TABLE IF EXISTS `user_db`;
CREATE TABLE `user_db`  (
  `id` int(4) NOT NULL AUTO_INCREMENT,
  `username` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 8 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of user_db
-- ----------------------------
INSERT INTO `user_db` VALUES (1, '張三');
INSERT INTO `user_db` VALUES (2, '李四');
INSERT INTO `user_db` VALUES (3, '王二');
INSERT INTO `user_db` VALUES (4, '麻子');
INSERT INTO `user_db` VALUES (5, '王三');
INSERT INTO `user_db` VALUES (6, '李三');

5、UserController

/**
 * 用戶控制層
 */
@RequestMapping("/user")
@RestController
public class UserController {
    @Autowired
    private UserService userService;

    @GetMapping("/get/{id}")
    @Cache(name = "get method")
    //@Cacheable(cacheNames = {"get"})
    public Result get(@PathVariable("id") Integer id){
        return userService.get(id);
    }

    @PostMapping("/updateData")
    @ClearAndReloadCache(name = "get method")
    public Result updateData(@RequestBody User user){
        return userService.update(user);
    }

    @PostMapping("/insert")
    public Result insert(@RequestBody User user){
        return userService.insert(user);
    }

    @DeleteMapping("/delete/{id}")
    public Result delete(@PathVariable("id") Integer id){
        return userService.delete(id);
    }
}

6、UserService

/**
 * service層
 */
@Service
public class UserService {

    @Resource
    private UserMapper userMapper;

    public Result get(Integer id){
        LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(User::getId,id);
        User user = userMapper.selectOne(wrapper);
        return Result.success(user);
    }

    public Result insert(User user){
        int line = userMapper.insert(user);
        if(line > 0)
            return Result.success(line);
        return Result.fail(888,"操作數(shù)據(jù)庫(kù)失敗");
    }

    public Result delete(Integer id) {
        LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(User::getId, id);
        int line = userMapper.delete(wrapper);
        if (line > 0)
            return Result.success(line);
        return Result.fail(888, "操作數(shù)據(jù)庫(kù)失敗");
    }

    public Result update(User user){
        int i = userMapper.updateById(user);
        if(i > 0)
            return Result.success(i);
        return Result.fail(888,"操作數(shù)據(jù)庫(kù)失敗");
    }
}

三、測(cè)試驗(yàn)證

1、ID=10,新增一條數(shù)據(jù)

2、第一次查詢數(shù)據(jù)庫(kù),Redis會(huì)保存查詢結(jié)果

3、第一次訪問(wèn)ID為10

4、第一次訪問(wèn)數(shù)據(jù)庫(kù)ID為10,將結(jié)果存入Redis

5、更新ID為10對(duì)應(yīng)的用戶名(驗(yàn)證數(shù)據(jù)庫(kù)和緩存不一致方案)

數(shù)據(jù)庫(kù)和緩存不一致驗(yàn)證方案:

打個(gè)斷點(diǎn),模擬A線程執(zhí)行第一次刪除后,在A更新數(shù)據(jù)庫(kù)完成之前,另外一個(gè)線程B訪問(wèn)ID=10,讀取的還是舊數(shù)據(jù)。

6、采用第二次刪除,根據(jù)業(yè)務(wù)場(chǎng)景設(shè)置延時(shí)時(shí)間,兩次刪除緩存成功后,Redis結(jié)果為空。讀取的都是數(shù)據(jù)庫(kù)真實(shí)數(shù)據(jù),不會(huì)出現(xiàn)讀緩存和數(shù)據(jù)庫(kù)不一致情況。

四、代碼工程及地址

核心代碼紅色方框所示

gitee.com/jike11231/redisDemo.git

以上就是SpringBoot AOP+Redis實(shí)現(xiàn)延時(shí)雙刪功能的實(shí)戰(zhàn)指南的詳細(xì)內(nèi)容,更多關(guān)于SpringBoot Redis延時(shí)雙刪的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • 淺析Java?ReentrantLock鎖的原理與使用

    淺析Java?ReentrantLock鎖的原理與使用

    這篇文章主要為大家詳細(xì)介紹了Java中ReentrantLock鎖的原理與使用,文中的示例代碼講解詳細(xì),具有一定的借鑒價(jià)值,感興趣的小伙伴可以了解下
    2023-08-08
  • 解決idea web項(xiàng)目中out目錄更新不同步問(wèn)題

    解決idea web項(xiàng)目中out目錄更新不同步問(wèn)題

    這篇文章給大家介紹了idea web項(xiàng)目中out(maven中是target)目錄更新不同步,導(dǎo)致訪問(wèn)404的問(wèn)題,本文給大家分享解決方法,對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友參考下吧
    2020-09-09
  • SpringBoot實(shí)現(xiàn)敏感配置信息加密與解密

    SpringBoot實(shí)現(xiàn)敏感配置信息加密與解密

    SpringBoot支持多種配置加載方式,不管用哪種方式,敏感信息(比如數(shù)據(jù)庫(kù)密碼、Redis密碼、第三方API密鑰)如果明文存儲(chǔ),都會(huì)有泄露風(fēng)險(xiǎn),下面我們就來(lái)聊聊如何對(duì)敏感配置信息加密與解密吧
    2025-07-07
  • Java讀取圖片EXIF信息的方法

    Java讀取圖片EXIF信息的方法

    這篇文章主要介紹了Java讀取圖片EXIF信息的方法,較為詳細(xì)的分析了圖片EXIF信息的概念、功能及java讀取EXIF信息的實(shí)現(xiàn)技巧,需要的朋友可以參考下
    2015-07-07
  • Spring源碼如何修改Bean的屬性用到的相關(guān)類

    Spring源碼如何修改Bean的屬性用到的相關(guān)類

    這篇文章主要介紹了Spring源碼如何修改Bean的屬性用到的相關(guān)類,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2024-05-05
  • Java中繼承thread類與實(shí)現(xiàn)Runnable接口的比較

    Java中繼承thread類與實(shí)現(xiàn)Runnable接口的比較

    這篇文章主要介紹了Java中繼承thread類與實(shí)現(xiàn)Runnable接口的比較的相關(guān)資料,需要的朋友可以參考下
    2017-06-06
  • 最簡(jiǎn)單的Spring Cloud教程第一篇:服務(wù)的注冊(cè)與發(fā)現(xiàn)(Eureka)

    最簡(jiǎn)單的Spring Cloud教程第一篇:服務(wù)的注冊(cè)與發(fā)現(xiàn)(Eureka)

    這篇文章主要給大家介紹了關(guān)于Spring Cloud服務(wù)的注冊(cè)與發(fā)現(xiàn)(Eureka)的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用spring cloud具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來(lái)一起看看吧。
    2017-08-08
  • 詳解備忘錄模式及其在Java設(shè)計(jì)模式編程中的實(shí)現(xiàn)

    詳解備忘錄模式及其在Java設(shè)計(jì)模式編程中的實(shí)現(xiàn)

    這篇文章主要介紹了詳解備忘錄模式及其在Java設(shè)計(jì)模式編程中的實(shí)現(xiàn),備忘錄模式數(shù)據(jù)的存儲(chǔ)過(guò)程中應(yīng)當(dāng)注意淺拷貝和深拷貝的問(wèn)題,需要的朋友可以參考下
    2016-04-04
  • 一文全解Java 泛型

    一文全解Java 泛型

    文章主要介紹了Java泛型的概念、定義、使用方法及其優(yōu)勢(shì),文章詳細(xì)介紹了泛型類、泛型方法、泛型接口以及通配符的使用方法和應(yīng)用場(chǎng)景,最后強(qiáng)調(diào)了類型擦除的概念和通配符的使用注意事項(xiàng),感興趣的朋友跟隨小編一起看看吧
    2026-05-05
  • 選擇Spring Boot項(xiàng)目的內(nèi)嵌容器的理由

    選擇Spring Boot項(xiàng)目的內(nèi)嵌容器的理由

    Spring Boot是由Pivotal團(tuán)隊(duì)提供的全新框架,其設(shè)計(jì)目的是用來(lái)簡(jiǎn)化新Spring應(yīng)用的初始搭建以及開(kāi)發(fā)過(guò)程。這篇文章主要介紹了選擇Spring Boot項(xiàng)目的內(nèi)嵌容器,需要的朋友可以參考下
    2017-11-11

最新評(píng)論

简阳市| 铁力市| 响水县| 得荣县| 孟村| 克东县| 通州市| 大石桥市| 涟源市| 黄石市| 兰考县| 文安县| 体育| 华池县| 香格里拉县| 库伦旗| 子洲县| 广水市| 木兰县| 炉霍县| 阿鲁科尔沁旗| 婺源县| 翁牛特旗| 卓资县| 叙永县| 林西县| 东兰县| 杨浦区| 嘉禾县| 密云县| 丰城市| 古蔺县| 邯郸县| 青岛市| 中山市| 林芝县| 故城县| 江西省| 资兴市| 扶风县| 垣曲县|