自定义redis缓存和本地缓存注解

数据查询顺序

一级缓存:本地缓存 -》二级缓存:redis缓存 -》数据库

在这里插入图片描述

本地缓存和分布式缓存

  • 本地缓存基于jvm, 意思是程序放在哪,数据就存储在哪,不需要网络请求,特别快,但是需要占用jvm的内存,所以存储的东西不能太大,也不能太久,有内存淘汰机制。缓存组件包括: Guava CacheCaffeineEncache
  • 分布式缓存:请求需要网络传输,速度慢一点,组件包括:RedisMongoDB

哪些场景用到缓存?

  • 邀请入会的助力排行榜,key= 活动id+pageSize,缓存过期时间1分钟
/**
     * 查询邀请入会的助力排行榜
     * @param activityId
     * @param pageSize
     * @return
     */

    @Override
    @MallCache(value = "queryHelpRankingList", expireTime = 1, timeUnit = TimeUnitEnum.MINUTES)
    public List<ActivityHelpStatisticsEntity> queryRankingList(String activityId, Integer pageSize) {
        List<ActivityHelpStatisticsLogPO> statisticsLogs = mapper.queryRankingList(activityId, pageSize);
        if (CollectionUtils.isEmpty(statisticsLogs)) {
            return new ArrayList<>();
        }
        return statisticsLogs.stream().map(po -> ActivityHelpStatisticsConverter.convert(po))
                .collect(Collectors.toList());
    }
  • 验证用户是否在分组中,可以=groupId, String nascentId, String groupIds,缓存五秒
 @MallCache(value = "checkExistGroup", expireTime = 5, timeUnit = TimeUnitEnum.SECONDS)
    private boolean checkExistGroupCache(long groupId, String nascentId, String groupIds){
        return OpenPlatformHelperV4.checkExistGroup(groupId, nascentId, groupIds);
    }
  • 等级体系,缓存30分钟
@MallCache(value = "GradeSystem", expireTime = 30)
    @Override
    public GradeSystem queryGradeSystem(Long groupId, Long viewId, Long shopId) {
        GradeSystemGetRequest gradeSystemGetRequest = new GradeSystemGetRequest();
        gradeSystemGetRequest.setViewId(viewId);
        gradeSystemGetRequest.setShopId(shopId);
        gradeSystemGetRequest.setGroupId(groupId);
        GradeSystemGetResponse gradeResponse = OpenPlatformClient.exec(groupId, gradeSystemGetRequest);
        GradeSystem result = gradeResponse.getResult();
        return result;
    }
  • 等级名称 30分钟,这种需要调用三方接口,缓存可以减少调用接口次数
/**
     * 获取等级名称
     *
     * @param groupId
     * @param shopId
     * @param grade
     * @return
     */
    @MallCache(value = "CustomerGradeName", expireTime = 30)
    public String getCustomerGradeName(long groupId, long shopId, int grade) {
        List<Long> viewIds = cloudPlatformService.queryViewIdByAreaIdOrBrandId(groupId, getMallId(), Arrays.asList(shopId));
        return getCustomerGradeName(groupId, shopId, grade, viewIds.get(0));
    }

    public String getCustomerGradeName(long groupId, long shopId, int grade, long viewId) {
        if (grade == 0) {
            return "非会员";
        }
        GradeInfoGetResponse response = OpenPlatformHelperV4.getGradeInfo(groupId, shopId, grade, viewId);
        AssertUtil.assertTrue(response != null && response.getSuccess(), "获取等级信息失败");
        GradeInfo gradeInfo = response.getResult();
        AssertUtil.assertNotNull(gradeInfo, "获取等级信息失败");
        return gradeInfo.getGradeName();
    }
@Override
    @MallCache(value = "VIEWID", expireTime = 60)
    public Long queryViewIdByAreaIdOrBrandId(Long groupId, Long mallId, Long shopId) {
        WmCompanyDO company = WmCompanyDao.dao().findByCompanyId(groupId);
        List<Long> viewIds = CloudPlatformHelper.queryViewIdByShopIds(Arrays.asList(shopId), groupId, company.getViewOperationType());
        AssertUtil.assertTrue(!CollectionUtils.isEmpty(viewIds), "查询运营视角失败,请检查");
        return viewIds.get(0);
    }
  • 线下门店列表 缓存60分钟,这种东西一般不会经常变化,且进入首页就要用,
/**
     *
     * @Description  获取线下门店列表,并存入缓存
     * @Param
     * @param groupId
     * @param shopId
     * @return
     */
    @MallCache(value = "offlineShops", expireTime = 60)
    public TableResponse<Record> getOfflineShops(Long groupId,Long shopId){
        TableResponse<Record> tableResponse = new TableResponse<>();
        List<Record> offlineShops = WmOfflineShopDao.dao().getOfflineByDigitalShopId(groupId,shopId);
        tableResponse.setData(offlineShops);
        return tableResponse;
    }
/**
     * 积分别名
     *
     * @param groupId
     * @param shopId
     * @param sysId
     * @return
     */
    @MallCache(value = "getGameIntegralAlias", expireTime = 60)
    public String getIntegralAlias(Long groupId, Long mallId, Long shopId, String sysId) {
        Long viewId = cloudViewUtil.getViewId(groupId, mallId, shopId);
        return openIntegralCacheUtil.getIntegralAliasCache(groupId, mallId, shopId, sysId, viewId);
    }

项目中如何做缓存设计?

基于AOP的切面注解

目录结构

在这里插入图片描述

自定义@MallCache注解

/**
 * 二级缓存:redis+本地缓存
 * 默认启用redis缓存,本地缓存由withLocalCache控制
 * @author
 */
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface MallCache {

    String value() default "MallCache::";

    /**
     * 是否启用本地缓存
     *
     * @return
     */
    boolean withLocalCache() default false;

    /**
     * 分钟级缓存:
     * 一级缓存的失效时间为10分钟,这里二级缓存的失效时间应该大于等于10分钟,
     * 不然失去二级缓存的意义
     * 秒级缓存:本地缓存失效时间是5s
     * @return 缓存失效时间 (默认分钟)
     */
    long expireTime() default 20;

    /**
     * 缓存失效时间单位
     * TimeUnitEnum.MINUTES使用分钟级缓存
     * TimeUnitEnum.SECONDS使用秒级缓存
     *
     * @return
     */
    TimeUnitEnum timeUnit() default TimeUnitEnum.MINUTES;
}

引用注解@MallCache

	/**
     * 查询邀请入会的助力排行榜前100
     * @param activityId
     * @param pageSize
     * @return
     */
	@Override
    @MallCache(value = "queryHelpRankingList", expireTime = 1, timeUnit = TimeUnitEnum.MINUTES)
    public List<ActivityHelpStatisticsEntity> queryRankingList(String activityId, Integer pageSize) {
        List<ActivityHelpStatisticsLogPO> statisticsLogs = mapper.queryRankingList(activityId, pageSize);
        if (CollectionUtils.isEmpty(statisticsLogs)) {
            return new ArrayList<>();
        }
        return statisticsLogs.stream().map(po -> ActivityHelpStatisticsConverter.convert(po))
                .collect(Collectors.toList());
    }

需要得到的效果

比如 上面的queryRankingList(String activityId, Integer pageSize) 方法引用了@MallCache(value = "queryHelpRankingList", expireTime = 1, timeUnit = TimeUnitEnum.MINUTES)注解。
最终执行过程应该是:

  1. 首先用 queryHelpRankingListactivityIdpageSize拼接成key,比如activityId=hh34hrnih352nb3kh5o3g34, pageSize=100,那拼接的key = queryHelpRankingList_hh34hrnih352nb3kh5o3g34_100
  2. 拿着key去本地查缓存,本地缓存有的话返回给前端
  3. 本地缓存没有就去查redis缓存,redis缓存有的话返回给前端,同时更新到本地缓存
  4. redis缓存没有的话,调用queryRankingList(String activityId, Integer pageSize)的代码去查数据库
  5. 同时将数据库的最新数据更新到redis和本地缓存

LocalCacheAspect

用于切面加强

@Slf4j
@Aspect
@Component
@Order(-1)
@ConditionalOnBean(CommonCacheManager.class)
public class LocalCacheAspect {


    @Autowired
    private CommonCacheManager commonCacheManager;



    @Around("@annotation(com.nascent.ecrp.mall.core.cache.annotation.MallCache)")
    public Object mallCache(ProceedingJoinPoint proceedingJoinPoint){
        MethodSignature methodSignature = (MethodSignature) proceedingJoinPoint.getSignature();
        Method method = methodSignature.getMethod();
        Type genericReturnType = methodSignature.getMethod().getAnnotatedReturnType().getType();

        MallCache mallCache = method.getAnnotation(MallCache.class);

        String cachePrefix = mallCache.value();
        long expire = mallCache.expireTime();
        boolean withLocalCache = mallCache.withLocalCache();
        TimeUnitEnum timeUnit = mallCache.timeUnit();

        Object[] args = proceedingJoinPoint.getArgs();

        // 用切点方法的参数拼接成cacheKey 比如groupId_viewId_shopId
        String cacheKey = Arrays.stream(args).map(arg -> {
            return String.valueOf(arg);
        }).collect(Collectors.joining("_"));


        Object result = commonCacheManager.queryData(cachePrefix + "_" + cacheKey,
                    expire ,timeUnit, withLocalCache, genericReturnType ,()->{
                        try {
                            // 接口查询数据库拿到数据
                            return proceedingJoinPoint.proceed(args);
                        } catch (Throwable throwable) {
                            log.error("LocalCacheAspect.mallCache 异常:{}", throwable);
                            return null;
                        }
                    });

            return result;
        }
}

CommonCacheFactory

本地缓存的初始化类

  • 初始化缓存,设置缓存的级别,最大缓存数,过期时间
  • 增加缓存,删除缓存
public class CommonCacheFactory {

    /**
     * 秒级时间的缓存
     */
    private static Cache<String, Object> localCacheSecond;
    /**
     * 分钟时间的缓存
     */
    private static Cache<String, Object> localCacheMinute;

    static {
        localCacheSecond = Caffeine.newBuilder()
                .maximumSize(1000)
                .expireAfterWrite(5, TimeUnit.SECONDS)
                .recordStats()
                .build();

        localCacheMinute = Caffeine.newBuilder()
                .maximumSize(1000)
                .expireAfterWrite(10, TimeUnit.MINUTES)
                .recordStats()
                .build();
    }
    private CommonCacheFactory(){

    }

    public static  Cache<String, ? > get() {
        return getMinute();
    }

    public static void set(String key, Object value){
        setMinute(key, value);
    }

    public static  Cache<String, ? > getSecond() {
        return localCacheSecond;
    }

    public static void setSecond(String key, Object value){
        localCacheSecond.put(key, value);
    }

    public static  Cache<String, ? > getMinute() {
        return localCacheMinute;
    }

    public static void setMinute(String key, Object value){
        localCacheMinute.put(key, value);
    }

}


CommonCacheManager

本地缓存管理类,用于本地缓存的查询和更新

@Slf4j
@Component
public class CommonCacheManager extends AbstractCacheManager<String, String>{


    private static final String LOCAL_CACHE_ENABLE_KEY =  "LOCAL_CACHE_ENABLE";

    @Autowired
    private RedisTemplate<String, String> redisTemplate;


    @Override
    public boolean useCache() {
        // todo 优化成直接在内存中获取
        String enable = redisTemplate.opsForValue().get(LOCAL_CACHE_ENABLE_KEY);
        if(StringUtil.isNotBlank(enable) && "CLOSE".equals(enable)){
            return false;
        }else {
            return true;
        }
    }

    @Override
    public Cache<String, String> primaryCache(TimeUnitEnum timeUnit) {
        if(TimeUnitEnum.SECONDS.equals(timeUnit)){
            Cache<String, String> stringResultCache = (Cache<String, String>) CommonCacheFactory.getSecond();
            return stringResultCache;
        }
        return (Cache<String, String>) CommonCacheFactory.getMinute();
    }


    @Override
    public String cacheKey() {
        return "LOCAL::CACHE::";
    }



    public <T> T queryData(String k, Long expireTime, TimeUnitEnum timeUnit, boolean withLocalCache, Type type, Supplier<T> supplier) {
        if (!useCache()) {
            // 不使用本地缓存,默认是使用的
            log.info("直接在缓存中获取数据:{}", k);
            return supplier.get();// supplier.get()能获取函数的返回值,直接返回数据库数据
        }
        try {
            CacheContext<String,T> resultCacheContext =
                    CacheContext
                            .<String,T>builder()
                            .key(cacheKey() + k)
                            .reference(type)
                            .expireSeconds(expireTime)
                            .timeUnit(timeUnit.getTimeUnit())
                            .callback(() -> supplier.get())// 数据库数据,这里只作为实参,还并没有调用
                            .build();
            // 不用本地缓存,用redis缓存
            if(!withLocalCache){
                return JSON.parseObject(getFromSecondary(resultCacheContext), type);
            }
            // 用本地缓存
            String result = primaryCache(timeUnit).get(k, (x) -> getFromSecondary(resultCacheContext));
            return JSON.parseObject(result, type);
        } catch (Exception e) {
            log.error("一级缓存获取数据失败:{}", e.getMessage());
            return supplier.get();
        }
    }


}

AbstractCacheManager

redis缓存的管理接口类

@Slf4j
public abstract class AbstractCacheManager<K, V> implements CacheManage<K, V> {

    /** 查询redis缓存、存在则读取并返回,不存在则更新成最新数据
     *
     * @param context
     * @return
     * @param <T>
     */

    protected <T> V getFromSecondary(CacheContext<K,T> context) {
        K key = context.key;
        T dbValue = null;
        try {

            String valueFromSecondary = secondaryCache().getValueFromSecondary(key);
            // 二级缓存,即redis层缓存,有数据就使用redis缓存
            if (StringUtil.isNotBlank(valueFromSecondary)) {
                return (V) valueFromSecondary;
            }
            // redis缓存没有,查询数据库
            dbValue = context.getCallback().get();

            if (dbValue == null) {
                return null;
            }

            Object jsonValue = JSONObject.toJSONString(dbValue);
            //把数据库的数据跟新到二级缓存
            secondaryCache().setValueForSecondary(key, context.expireSeconds, context.timeUnit, jsonValue);

            return (V)jsonValue;
        } catch (Exception e) {
            log.error("二级缓存中获取数据失败:{}", e.getMessage());
            return null;
        }
    }



    @Override
    public SecondaryCache secondaryCache() {
        RedisSecondaryCache redisSecondaryCache = SecondaryCacheFactory.create(RedisSecondaryCache.class);
        return redisSecondaryCache;
    }


    @Override
    public void invalidate(Object o) {
        for(TimeUnitEnum timeUnit : TimeUnitEnum.values()){
            primaryCache(timeUnit).invalidate(o);
        }
        secondaryCache().invalidate(o);
    }


    @Override
    public void invalidateAll(List o) {
        for(TimeUnitEnum timeUnit : TimeUnitEnum.values()){
            primaryCache(timeUnit).invalidateAll();
        }
        secondaryCache().invalidateAll(o);
    }


    @Override
    public Map<String,Map<String, V>> showAsMap() {
        Map<String,Map<String, V>> map = Maps.newHashMap();
        for(TimeUnitEnum timeUnit : TimeUnitEnum.values()) {
            Map<String,V> newMap = Maps.newHashMap();
            primaryCache(timeUnit).asMap().entrySet().forEach(entry -> newMap.put(Objects.toString(entry.getKey()),entry.getValue()));
            map.put(timeUnit.name(), newMap);
        }
        return map;
    }

    @Override
    public Map<String,Set<K>> keys() {
        Map<String,Set<K>> map = new HashMap();
        for(TimeUnitEnum timeUnit : TimeUnitEnum.values()) {
            map.put(timeUnit.name(), primaryCache(timeUnit).asMap().keySet());
        }
        return map;
    }

}

RedisSecondaryCache

@Slf4j
@Component
public class RedisSecondaryCache implements SecondaryCache<String>{


    @Autowired
    private RedisTemplate<String, Object> redisTemplate;


    @Override
    public String getValueFromSecondary(String key){
        Object redisCache = redisTemplate.opsForValue().get(key);
        return redisCache == null ? null : redisCache.toString();
    }

    @Override
    public String getValueFromSecondaryBatch(List<String> cacheKeys) {
        Object redisCache = redisTemplate.opsForValue().multiGet(cacheKeys);
        return redisCache == null ? null : redisCache.toString();
    }

    @Override
    public <T> void setValueForSecondary(String cacheKey,long expireSeconds, TimeUnit timeUnit, T value) {
        redisTemplate.opsForValue().set(cacheKey,JSONObject.toJSON(value),expireSeconds, timeUnit);
    }

    @Override
    public <T> void setValueForSecondaryBatch(Map<String, T> kv, long expireSeconds) {
        kv.forEach((k, v) -> {
            redisTemplate.opsForValue().set(k, v, expireSeconds, TimeUnit.MINUTES);
        });
    }

    @Override
    public void invalidate(Object o) {
        redisTemplate.delete(String.valueOf(o));
    }

    @Override
    public void invalidateAll(List keys) {
        redisTemplate.delete(keys);
    }

}

CacheContext

@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class CacheContext<K, T> {
    K key;

    Type reference;

    // 传递函数,Supplier可以把函数作为实参传递,调用Supplier.get()时返回函数的结果
    Supplier<T> callback;

    Long expireSeconds;

    TimeUnit timeUnit;
}

CacheManage

缓存管理接口

-定义

public interface CacheManage<K, V> extends InvalidateCommand {

    boolean useCache();

    String cacheKey();

    Cache<K, V> primaryCache(TimeUnitEnum timeUnit);

    SecondaryCache secondaryCache();

    Map<String, Map<String, V>> showAsMap();

    Map<String,Set<K>> keys();

}

SecondaryCache

redis缓存接口类

  • 定义一些接口,比如获取缓存,设置缓存
public interface SecondaryCache<K> extends InvalidateCommand{

    String getValueFromSecondary(K key);

    String getValueFromSecondaryBatch(List<K> keys);

    <T> void setValueForSecondary(K key, long expireSeconds, TimeUnit timeUnit, T value);

    <T> void setValueForSecondaryBatch( Map<K, T > kv, long expireSeconds);
}

SecondaryCacheFactory

二级缓存工厂类

  • 感觉啥也没干,不知道这个有什么用
public class SecondaryCacheFactory {

    private SecondaryCacheFactory(){

    }

    public static <T> T create(Class<T> clazz) {
        return (T) SpringContext.me().getBean(clazz);
    }
}

TimeUnitEnum

/**
 * 缓存支持的时间的单位
 * @author
 */
public enum TimeUnitEnum {
    MINUTES(TimeUnit.MINUTES),
    SECONDS(TimeUnit.SECONDS);

    @Getter
    private TimeUnit timeUnit;

    TimeUnitEnum(TimeUnit timeUnit){
        this.timeUnit = timeUnit;
    }
}

InvalidateCommand

public interface InvalidateCommand<K> {

    void invalidate(K k);

    void invalidateAll(List<K> k);
}

  • 12
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
MyBatis Plus提供了默认的Redis缓存管理器,但是有时候我们需要根据自己的业务需求来定制一个适合自己的缓存管理器,这里简单介绍一下如何自定义Redis缓存管理器: 1. 实现Cache接口 首先需要实现MyBatis的Cache接口,该接口定义缓存操作的基本接口方法。可以参考默认的RedisCache实现。 ``` public interface Cache { String getId(); void putObject(Object key, Object value); Object getObject(Object key); Object removeObject(Object key); void clear(); int getSize(); default void putObject(Object key, Object value, long ttl) { throw new UnsupportedOperationException("not support"); } } ``` 2. 实现CacheManager接口 接着需要实现MyBatis的CacheManager接口,该接口负责管理各个缓存实例的生命周期以及提供创建缓存的方法。可以参考默认的RedisCacheManager实现。 ``` public interface CacheManager { Cache getCache(String var1); void addCache(String var1); Set<String> getCacheNames(); default void destroy() { } } ``` 3. 配置缓存管理器 在MyBatis的配置文件中配置自定义缓存管理器和缓存实现类。可以像下面这样配置: ``` <cache type="com.example.MyCacheManager"> <!-- 缓存过期时间,单位:秒,默认为永不过期 --> <property name="ttl" value="3600"></property> <!-- RedisHost --> <property name="host" value="localhost"></property> <!-- RedisPort --> <property name="port" value="6379"></property> <!-- RedisPassword --> <property name="password" value=""></property> <!-- RedisDatabase --> <property name="database" value="0"></property> <!-- RedisTimeout --> <property name="timeout" value="5000"></property> </cache> ``` 其中type属性值为自定义的CacheManager实现类。 4. 使用缓存 在Mapper接口方法上使用@CacheNamespace注解开启缓存功能并指定缓存管理器,例如: ``` @Mapper @CacheNamespace(implementation = MyCacheManager.class) public interface UserMapper { @Select("select * from user where id = #{id}") @Options(useCache = true, flushCache = Options.FlushCachePolicy.FALSE) User selectById(@Param("id") int id); } ``` 这样就可以在查询User对象时自动使用自定义Redis缓存管理器进行缓存

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值