重新认识Java中防止重复提交

后端拦截与缓存优化:实战防止重复提交的几种策略
本文探讨了如何通过后端拦截机制,结合HashMap、固定大小数组和LRUMap等数据结构,实现业务请求的防重复提交。从基本的HashMap到更高效的LRUMap,作者详细介绍了每种方法的优缺点及适用场景,同时提到了自定义工具封装和注解拦截的高级解决方案。

防止重复提交可以通过前端拦截校验和后端拦截校验的方式,在此对后端拦截进行重复提交判断进行尝试。
后端拦截的实现思路是在方法执行之前,先判断此业务是否已经执行过,如果执行过则不再执行,否则就正常执行。我们将请求的业务 ID 存储在内存中,并且通过添加互斥锁来保证多线程下的程序执行安全,将数据存储在内存中,最简单的方法就是使用 HashMap 存储,或者是使用 Guava Cache 也是同样的效果,但很显然 HashMap 可以更快的实现功能,所以我们先来实现一个 HashMap 的防重(防止重复)版本。
升级方法是加上双重检测锁DCL;再升级方法是采用LRUMap实现;
代码示例:
方式一:HashMap

@RestController
public class DemoController1 {
    //缓存ID集合
    private Map<String,Integer> map = new HashMap<>();

    @RequestMapping("addUser1")
    public String addUser(String id) {
        synchronized (this.getClass()) {
            //重复请求判断
            if (map.containsKey(id)) {
                //重复请求
                System.out.println("请勿重复提交..." + id);
                return "请求失败";
            }
            //存储ID
            map.put(id,1);
        }
        //业务代码
        System.out.println("添加用户ID:" + id);
        return "请求成功";
        //此方式的问题是HashMap是无限增长的,占用内存会越来越大;需要添加可以清除过期的数据
    }
}

方式二:固定大小数组

@RestController
public class DemoController2 {
    //ID集合,大小为100
    private static String[] cache = new String[100];
    //请求计数器
    private static Integer counter = 0;

    @RequestMapping("addUser2")
    public String addUser(String id) {
        synchronized (this.getClass()) {
            //重复请求判断
            if (Arrays.asList(cache).contains(id)) {
                //重复请求
                System.out.println("请勿重复提交..." + id);
                return "请求失败";
            }
            //记录ID
            if (counter >= cache.length) {
                counter = 0;
            }
            cache[counter] = id;
            //下标后移一位
            counter++;
        }
        //业务代码
        System.out.println("添加用户ID:" + id);
        return "请求成功";
        //此方式的问题是HashMap是无限增长的,占用内存会越来越大;需要添加可以清除过期的数据
        //此方式解决了HashMap未清除过期数据的问题
    }
}

方式三:方式一的升级

	//缓存ID集合
    private Map<String,Integer> map = new HashMap<>();

    @RequestMapping("addUser3")
    public String addUser(String id) {
        //重复请求判断
        if (map.containsKey(id)) {
            //重复请求
            System.out.println("请勿重复提交..." + id);
            return "请求失败";
        }
        synchronized (this.getClass()) {
            //重复请求判断
            if (map.containsKey(id)) {
                //重复请求
                System.out.println("请勿重复提交..." + id);
                return "请求失败";
            }
            //存储ID
            map.put(id,1);
        }
        //业务代码
        System.out.println("添加用户ID:" + id);
        return "请求成功";
        //此方式的问题是HashMap是无限增长的,占用内存会越来越大;需要添加可以清除过期的数据
        //添加了双重检测机制提升代码执行效率,注意DCL使用重复提交频繁的业务场景,相反并不适用
    }

方式四:方式二的升级

//ID集合,大小为100
    private static String[] cache = new String[100];
    //请求计数器
    private static Integer counter = 0;

    @RequestMapping("addUser4")
    public String addUser(String id) {
        if (Arrays.asList(cache).contains(id)) {
            //重复请求
            System.out.println("请勿重复提交..." + id);
            return "请求失败";
        }
        synchronized (this.getClass()) {
            //重复请求判断
            if (Arrays.asList(cache).contains(id)) {
                //重复请求
                System.out.println("请勿重复提交..." + id);
                return "请求失败";
            }
            //记录ID
            if (counter >= cache.length) {
                counter = 0;
            }
            cache[counter] = id;
            //下标后移一位
            counter++;
        }
        //业务代码
        System.out.println("添加用户ID:" + id);
        return "请求成功";
        //此方式的问题是HashMap是无限增长的,占用内存会越来越大;需要添加可以清除过期的数据
        //此方式解决了HashMap未清除过期数据的问题,添加了双重检测机制提升代码执行效率,注意DCL使用重复提交频繁的业务场景,相反并不适用
    }

方式五:LRUMap

//最大容量100个,根据LRU算法淘汰数据的Map集合
    private LRUMap<String,Integer> lruMap = new LRUMap<>(100);

    @RequestMapping("addUser5")
    public String addUser(String id) {
        if (lruMap.containsKey(id)) {
            //重复请求
            System.out.println("请勿重复提交..." + id);
            return "请求失败";
        }
        synchronized (this.getClass()) {
            if (lruMap.containsKey(id)) {
                //重复请求
                System.out.println("请勿重复提交..." + id);
                return "请求失败";
            }
            //存储ID
            lruMap.put(id,1);
        }
        //业务代码
        System.out.println("添加用户ID:" + id);
        return "请求成功";
        //此方式的问题是HashMap是无限增长的,占用内存会越来越大;需要添加可以清除过期的数据
        //此方式解决了HashMap未清除过期数据的问题,通过LRU算法清除最不常使用的数据
    }

方式六:方式五的工具化封装

public class IdempotentUtil {
    //根据LRU算法淘汰最近最少使用的数据的Map集合,最大容量100个
    private static LRUMap<String,Integer> lruMap = new LRUMap<>(100);

    /**
     * 幂等性判断
     */
    public static boolean check(String id, Object lockClass) {
        synchronized (lockClass) {
            //重复请求判断
            if (lruMap.containsKey(id)) {
                //重复请求
                System.out.println("请勿重复提交..." + id);
                return false;
            }
            //非重复请求,存储ID
            lruMap.put(id,1);
        }
        return true;
    }
}
@RequestMapping("addUser6")
    public String addUser(String id) {
        //调用幂等性工具判断
        if (!IdempotentUtil.check(id,this.getClass())) {
            return "请求失败";
        }
        //业务代码
        System.out.println("添加用户ID:" + id);
        return "请求成功";
    }

后面还可以通过自定义注解+反射+拦截器实现校验。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值