函数式编程实践:抽象缓存维护通用逻辑


前言

日常开发为重中常遇到数据库和缓存同步的问题,目前比较常用的流程是:查询时先查询缓存,缓存不存在则从数据库查询后同时维护缓存;修改时候先修改数据库,再删除对应的缓存数据;同时结合缓存本身的淘汰策略进行整个缓存的维护。本文主要讨论查询过程中为了同步更新的写法问题,正常情况下我们可以做以下写法:

	public static BizObj getBizObj(Long id) {
		BizObj bizObj = cache.get(id);
		if (bizObj == null) {
			bizObj = getFromDb(id);
			if (bizObj != null) {
				cache.put(id, bizObj);
			}
		}
		return bizObj;
	}

但是某些模块可能涉及比较多的缓存,所以以上的判断缓存是否存在和维护缓存的逻辑会非常多,导致重复代码;通过函数式接口可以解决以上问题,在动手之前我们先介绍一下函数式接口。


一、函数式接口概念

函数式接口实际上对我们并不陌生,常见到的Runnable、Callable、Comparator、Function、BiFunction、Consumer、Predicate、Supplier等都是函数式接口声明。其上都有@FunctionalInterface注解,但是需要说明的FunctionalInterface注解只能作用于接口,该注解主要用于编译级错误检查。使用@FunctionalInterface注解修饰接口后,如果写的接口不符合函数式接口规范,则编译器会报错,并非加了该注解才算是函数式接口。
函数式接口就是指对于一个接口只能有一个抽象方法,这种类型的接口也称为SAM(Single Abstract Method)接口。如果使用该注解同时定义多个抽象方法,编译阶段则会报错:

在这里插入图片描述

二、函数式接口实现缓存查询工具类和demo

通过这个接口定义,我们可以将以上缓存信息判断的逻辑统一到工具方法中去,数据库查询方法通过函数式接口的方式传入工具方法实现,后续有以上逻辑的时候就可以直接调用工具类来实现,demo如下。

1.自定义函数式接口和工具方法实现

代码如下(示例):

public class CollectionUtils<T> {

    public static <K, V> V getFromCache(Map<K, V> map, K key, CacheFunction<K, V> function) {
        try {
            if (key == null) {
                return null;
            }
            V v = map.get(key);
            if (v == null) {
                V fromDb = function.getFromDb(key);
                System.err.println("数据库查询结果" + fromDb);
                if (fromDb != null) {
                    map.put(key, fromDb);
                    System.err.println("数据库查询结果同步缓存-完成");
                    return fromDb;
                }
            }
            return v;
        } catch (Exception e) {
            System.err.println("从缓存查询数据异常,从数据库查询" + key);
            return function.getFromDb(key);
        }
    }

    /**
     * 可以直接使用接口{@link java.util.function.Function}
     * 为了代码排查方便,使用自定义的接口
     */
    @FunctionalInterface
    public interface CacheFunction<K, V> {
        V getFromDb(K k);
    }
}

2.模拟缓存调用demo

代码如下(示例):

public class CacheTest {

    // 缓存
    private static Map<Long, BizObj> cache = new HashMap<>();

    // 数据库数据模拟
    private static Map<Long, BizObj> dbMock = new HashMap<Long, BizObj>() {
        {
            put(1L, new BizObj(1L, "router"));
            put(2L, new BizObj(2L, "switch"));
            put(3L, new BizObj(3L, "voice"));
        }
    };

    // 业务调用模拟
    public static void main(String[] args) {
        System.err.println("【查询结果】" + getBizObj(1L));
        System.err.println("【查询结果】" + getBizObj(2L, "switch"));
    }

    public static BizObj getBizObj1(Long id) {
        BizObj bizObj = cache.get(id);
        if (bizObj == null) {
            bizObj = getFromDb(id);
            if (bizObj != null) {
                cache.put(id, bizObj);
            }
        }
        return bizObj;
    }

    // 对外提供的查询接口
    public static BizObj getBizObj(Long id) {
        return CollectionUtils.getFromCache(cache, id, CacheTest::getFromDb);
    }

    // 对外提供的查询接口
    public static BizObj getBizObj(Long id, String name) {
        return CollectionUtils.getFromCache(cache, id, key -> getFromDb(id, name));
    }

    // 查询数据库的接口不对外提供
    private static BizObj getFromDb(Long id) {
        return dbMock.get(id);
    }

    // 查询数据库的接口不对外提供
    private static BizObj getFromDb(Long id, String name) {
        BizObj bizObj = dbMock.get(id);
        return bizObj != null && bizObj.getName().equals(name) ? bizObj : null;
    }
}

业务数据类定义如下(此处省略get/set方法):

/**
 * 业务数据结构定义
 */
public class BizObj 
{
    private Long id;
    private String name;
}

3.补充批量查询的工具方法

代码如下(示例):

    public static <K, V> Map<K, V> getMultiFromCache(Map<K, V> map, Set<K> keys, CacheMultiFunction<K, V> function) {
        try {
            HashSet notHitKeys;
            if (org.apache.commons.collections4.CollectionUtils.isEmpty(keys)) {
                return new HashMap<>();
            }
            Map cache = map.getAll(keys);
            HashSet hashSet = notHitKeys = cache == null ? keys : new HashSet(org.apache.commons.collections4.CollectionUtils.
                    subtract((Iterable) keys, cache.keySet()));
            if (!notHitKeys.isEmpty()) {
                Map<K, V> fromDb = function.getFromDb(notHitKeys);
                System.err.println("数据库查询结果大小:" + fromDb != null ? Integer.valueOf(fromDb.size()) : null);
                if (fromDb != null && !fromDb.isEmpty()) {
                    map.putAll(fromDb);
                    System.err.println("更新缓存-putAll-ok;");
                    if (cache != null) {
                        cache.putAll(fromDb);
                    } else {
                        return fromDb;
                    }
                }
            }
            return cache == null ? new HashMap() : cache;
        } catch (Exception e) {
            System.err.println("从缓存获取信息异常,尝试从数据库读取: " + keys + e.getMessage());
            return function.getFromDb(keys);
        }
    }

    public static interface CacheMultiFunction<K, V> {
        public Map<K, V> getFromDb(Set<K> keys);
    }


总结

以上实践通过函数式接口抽象了通用的业务逻辑,消除了重复代码。不过实际业务中需要考虑的问题还很多,比如同步问题、更新策略、缓存淘汰策略等,需要具体问题具体分析。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值