springboot+redis实现接口级别缓存信息-编程思维

本文主要讲述如何通过SpringBoot+Redis实现接口级别缓存信息

背景

近期因为一直在处理公司的老项目,恰好碰到产品说页面有一些信息展示慢,简单看了一下页面接口,发现查询的是系统中几张大表(数据量在千万级别),还会关联一些其他的表,导致接口性能极差,但是由于这些信息也不存在"及时性"这么一说,便想着通过接口缓存来控制

相关技术

jdk 1.8
reids 5.0.7

实现思路

通过注解来标识需要缓存的接口,依据注解的内容去找到对应的建造者,通过建造者来找到具体去执行的类,最终达可扩展+缓存的效果

注解相关代码

package com.com.example.springdemo;

import com.com.example.springdemo.aspect.enums.RedisCacheEnums;

import java.lang.annotation.*;
import java.util.concurrent.TimeUnit;

/**
 * @CreateAt: 2023-11-3 11:25:37
 * @ModifyAt: 2023-11-3 11:25:37
 * @Version 1.0
 */
@Target({ElementType.PARAMETER, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RedisCache {


    /**
     * 缓存的key
     **/
    String key() default "";

    /**
     * 参数类型
     *
     * @return
     */
    RedisCacheEnums type();

    /**
     * 缓存时长,默认-1表示永久有效
     **/
    int time() default 300;

    /**
     * 缓存时长单位,默认单位秒
     **/
    TimeUnit timeType() default TimeUnit.SECONDS;
}

枚举相关代码


package com.example.springdemo.enums;

import java.util.Arrays;

/**
 * 缓存类型
 *
 * @CreateAt: 2023-11-3 11:26:22
 * @ModifyAt: 2023-11-3 11:26:22
 * @Version 1.0
 */
public enum RedisCacheEnums {

    QUERY_MEMBER_INFO(1, "查询会员信息"),
    ;

    private Integer code;
    private String type;

    RedisCacheEnums(Integer code, String type) {
        this.code = code;
        this.type = type;
    }

    public static RedisCacheEnums getByCode(int code) {
        return Arrays.stream(RedisCacheEnums.values())
                .filter(item -> item.getCode().intValue() == code)
                .findFirst()
                .orElse(null);
    }

    public Integer getCode() {
        return code;
    }

    public String getType() {
        return type;
    }
}

切换相关代码

package com.example.springdemo.aspect;

import com.example.springdemo.RedisCacheProvider;
import com.example.springdemo.handler.RedisCacheHandler;
import com.google.gson.Gson;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;

/**
 * @CreateAt: 2023-11-3 11:27:20
 * @ModifyAt: 2023-11-3 11:27:20
 * @Version 1.0
 */
@Aspect
@Slf4j
@Component
@AllArgsConstructor
public class RedisCacheAspect {

    @Pointcut("@annotation(com.example.springdemo.RedisCache)")
    public void annotationPoint() throws Throwable {

    }

    private RedisCacheProvider redisCacheProvider;

    /**
     * 却面切入点
     * implements Serializable 对象需要继承
     * @param proceedingJoinPoint
     * @return
     * @throws Throwable
     */
    @Around(value = "annotationPoint()")
    public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        try {
            MethodSignature method = (MethodSignature) proceedingJoinPoint.getSignature();
            RedisCache redisCache = method.getMethod().getAnnotation(RedisCache.class);
            Object[] args = proceedingJoinPoint.getArgs();
            RedisCacheHandler apply = redisCacheProvider.apply(redisCache.type());
            Boolean hasKey = apply.existHandler(args, redisCache.key());
            if (hasKey) {
                return apply.queryHandler(args, redisCache.key());
            } else {
                Object result = proceedingJoinPoint.proceed();
                apply.handler(redisCache.type(), args, result, redisCache.time(), redisCache.timeType(), redisCache.key());
                return result;
            }
        } catch (Exception e) {
            log.info("RedisCacheAspect Error:{}", e.toString());
            return proceedingJoinPoint.proceed();
        }
    }
}

建造者和相关hannder对应代码

package com.example.springdemo;

import com.example.springdemo.enums.RedisCacheEnums;
import com.example.springdemo.handler.RedisCacheHandler;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;

import java.util.List;

/**
 * 缓存提供者
 * @CreateAt: 2023-11-3 11:28:42
 * @ModifyAt: 2023-11-3 11:28:42
 * @Version 1.0
 */
@Component
@AllArgsConstructor
@Slf4j
public class RedisCacheProvider {

    private List<RedisCacheHandler> handlers;

    public RedisCacheHandler apply(RedisCacheEnums type) {
        RedisCacheHandler redisCacheHandler = handlers.stream().filter(x -> x.support(type)).findFirst().orElse(null);
        Assert.notNull(redisCacheHandler, "未找到对应的处理器");
        return redisCacheHandler;
    }
}

package com.example.springdemo.handler;


import com.example.springdemo.enums.RedisCacheEnums;

import java.util.concurrent.TimeUnit;

/**
 * @CreateAt: 2023-11-3 11:29:39
 * @ModifyAt: 2023-11-3 11:29:39
 * @Version 1.0
 */
public interface RedisCacheHandler {

    /**
     * 是否支持处理
     *
     * @param type
     * @return
     */
    boolean support(RedisCacheEnums type);

    /**
     * 查询缓存信息
     *
     * @param args
     * @param originalKey
     * @return
     */
    Object queryHandler(Object[] args, String originalKey) throws Exception;

    /**
     * 缓存信息是否存在
     *
     * @param args
     * @param originalKey
     * @return
     */
    Boolean existHandler(Object[] args, String originalKey) throws Exception;

    /**
     * 生成缓存信息
     *
     * @param type        类型
     * @param args        参数
     * @param result      结果
     * @param time        时间
     * @param timeType    时间类型
     * @param originalKey
     */
    void handler(RedisCacheEnums type, Object[] args, Object result, Integer time, TimeUnit timeType, String originalKey) throws Exception;
}

package com.example.springdemo.handler;

import com.example.springdemo.enums.RedisCacheEnums;
import com.example.springdemo.common.utils.RedisUtil;
import com.google.gson.Gson;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import java.util.concurrent.TimeUnit;

/**
 * @CreateAt: 2023-11-3 11:30:30
 * @ModifyAt: 2023-11-3 11:30:30
 * @Version 1.0
 */
@Slf4j
@Service
@AllArgsConstructor
public class MemberHandler implements RedisCacheHandler {

    private final String redisKey = "test";

	// 切换成自己项目使用的redis工具类即可
    private RedisUtil resdisUtil;

    /**
     * 是否支持处理
     *
     * @param type
     * @return
     */
    @Override
    public boolean support(RedisCacheEnums type) {
        return RedisCacheEnums.QUERY_MEMBER_INFO.equals(type);
    }

    /**
     * 查询缓存信息
     *
     * @param args
     * @param originalKey
     * @return
     */
    @Override
    public Object queryHandler(Object[] args, String originalKey) throws Exception {
        String key = getKey(args, originalKey);
        return resdisUtil.get(key);
    }


    /**
     * 查询缓存信息
     *
     * @param args
     * @param originalKey
     * @return
     */
    @Override
    public Boolean existHandler(Object[] args, String originalKey) throws Exception {
        String key = getKey(args, originalKey);
        return resdisUtil.hasKey(key);
    }


    /**
     * 生成操作记录对象
     *
     * @param type
     * @param args
     * @param result
     * @param time
     * @param timeType
     * @param originalKey
     * @return
     */
    @Override
    public void handler(RedisCacheEnums type, Object[] args, Object result, Integer time, TimeUnit timeType, String originalKey) throws Exception {
        String key = getKey(args, originalKey);
        resdisUtil.setByTime(key, result, time, timeType);
    }

    /**
     * 获取Key信息
     *
     * @param args
     * @return
     */
    private String getKey(Object[] args, String originalKey) throws Exception {
        try {
            Object omiMemberInquiryVO = (Object ) args[0];
            // 拼装缓存key信息
            String key = "test";
            log.info("RedisCacheAspect key:{}",key);
            return key;
        } catch (Exception e) {
            log.info("RedisCacheAspect 拼装Key参数异常:{},e:{}", new Gson().toJson(args), e.toString());
            throw new Exception("拼装Key参数异常");
        }
    }
}

手动ps:可能很多人都会问为什么不用Spring自带的,而需要自己去写,主要原因还是这是一个老系统,压根找不全对数据进行修改、删除的地方


如有哪里讲得不是很明白或是有错误,欢迎指正
如您喜欢的话不妨点个赞收藏一下吧🙂

版权声明:本文版权归作者所有,遵循 CC 4.0 BY-SA 许可协议, 转载请注明原文链接
https://www.cnblogs.com/ancold/p/17807294.html

spring/springboot中的声明式事务和编程式事务源码、区别、优缺点、适用场景、实战-编程思维

一、前言 在现代软件开发中,事务处理是必不可少的一部分。当多个操作需要作为一个整体来执行时,事务可以确保数据的完整性和一致性,并避免出现异常和错误情况。在SpringBoot框架中,我们可以使用声明式事务和编程式事务来管理事务处理。其中事务的坑也是不少,比较常见的就是事务失效,大家可以看看!后面小编在出一篇事务失效场景

浅析springboot加载配置的6种方式-编程思维

从配置文件中获取属性应该是SpringBoot开发中最为常用的功能之一,但就是这么常用的功能,仍然有很多开发者抓狂~今天带大家简单回顾一下这六种的使用方式: 说明 Environment对象 Environment 是 springboot 核心的环境配置接口,它提供了简单的方法来访问应用程序属性,包括

极速指南:在 springboot 中快速集成腾讯云短信功能-编程思维

前言 今天分享一个SpringBoot集成腾讯云短信的功能,平常除了工作,很多xdm做自己的小项目都可能用到短信,但自己去看文档挺费劲的,我这边就帮你节省时间,直接把步骤给你列出来,照做就行。 实战 1、申请密钥及签名模板 首先,要使用腾讯云短信,你得先在腾讯云有个账号,申请密钥及签名模板。 1)、找到访问管理-API

springboot3多环境配置-编程思维

SpringBoot3多环境配置 前言 这篇文章是我在学习SpringBoot3时对多环境配置概念学习的记录,以便日后遗忘查阅; 目录 目录SpringBoot3多环境配置前言目录一、如何理解多环境配置1.什么是软件运行环境2.为什么要设立多个软件运行环境二、如何在SpringBoot中进行运行环境切换1.Spri