JAVA设计模式-大集合数据拆分

背景

我们在做软件开发时,经常会遇到把大集合的数据,拆分成子集合处理。例如批量数据插入数据库时,一次大约插入5000条数据比较合理,但是有时候待插入的数据远远大于5000条。这时候就需要进行数据拆分。数据拆分基本逻辑并不复杂,下面尝试把数据拆分逻辑封装一下。

拆分逻辑

拆分过程唯一要求就是数据不能遗漏,也不能重复处理。

  • 定义子集合大小
  • 遍历源数据集合,达到一个子集合大小,
  • 根据业务需要开始处理子集合数据
  • 直到处理完所有数据

代码

先实现基本功能代码

java 复制代码
	/**
     * @param dataList 原数据集合
     * @param subSize  子集合size
     * @throws Exception
     */
	public static <T> void processdSubData(List<T> dataList, int subSize) throws Exception {
      
      	//子集合对象
        List<T> subDataList = new ArrayList<>();
		//计数变量
        int count = 0;
        
        for (T t : dataList) {
			
            subDataList.add(t);
            count++;//累计子集合数据数量

            if (count >= subSize) {//这里可以使用等号==,个人习惯使用大于等于>=

                try {
                	//处理子集合数据
                    //doSomeThing(subDataList);
                } catch (Exception e) {
                    throw e;
                } finally {
                	//清空计数变量和子集合
                    count = 0;
                    subDataList.clear();
                }

            }
        }

		//这里的剩余数据处理,非常容易遗漏,这也是为什么要封装公共代码的一个原因
		//封装成公共代码后,就不用担心遗漏这一部分数据
        if (subDataList.size() > 0) {
            //最后一次剩余数据量小于subSize,这里再处理一次
            try {
                //处理子集合数据
                //doSomeThing(subDataList);
            } catch (Exception e) {
                throw e;
            }
        }
    }

以上的代码,逻辑清晰且没有复杂的索引计算,是个比较好的实现。但是代码没有通用性,每次遇到数据拆分,都要写一遍拆分呢逻辑,写的多了难免出问题。仔细看下代码,除了处理子集合数据的业务代码方法,其他代码都是一样的。下面改造一下,子集合数据的业务方法由外部传入。那么拆分逻辑部分就可以通用,不用担心出问题了。

新实现

  • 业务处理接口
java 复制代码
package cn.com.soulfox.common.functions.splitdata;

import java.util.List;

/**
 *
 * 子数据集合业务数据处理接口
 * @create 2024/6/24 10:21
 */
@FunctionalInterface//函数式接口,只有一个抽象方法
public interface SplitDataCallback<T> {

    void splitDataProcess(List<T> subDataList);
}
  • 拆分工具类
java 复制代码
package cn.com.soulfox.common.functions.splitdata;


import java.util.List;

/**
 * 大集合拆分处理
 *
 * 
 * @create 2024/6/24 10:35
 */
public class SplitDataListUtil {

    /**
     * @param dataList 待拆分数据集合
     * @param subSize  子集合的size
     * @param callback 子集合数据处理类
     * @throws Exception
     */
    public static <T> void processData(List<T> dataList, int subSize, SplitDataCallback<T> callback) throws Exception {
    	//如果不做成公共代码,下面的判空的代码,忙的时候就不会写了吧 -:)
        if (callback == null) {
            //处理类为空
            return;
        }
        if (dataList == null || dataList.isEmpty()) {
            //数据集合为空
            return;
        }
        if (subSize <= 0) {
            //子集长度小于等于 0
            return;
        }
		
        if (subSize >= dataList.size()) {
            //子集长度大于等于原集合,不需要拆分,直接处理
            try {
                callback.splitDataProcess(dataList);
            } catch (Exception e) {
                System.out.println("处理子数据集失败:"+e.getMessage());
                throw e;
            }
            return;
        }

        processdSubData(dataList, subSize, (SplitDataCallback<T>) callback);
    }

    /**
     * @param dataList 原数据集合
     * @param subSize  子集合size
     * @param callback 子集合数据处理类
     * @throws Exception
     */
    private static <T> void processdSubData(List<T> dataList, int subSize, SplitDataCallback<T> callback) throws Exception {
       //子集合对象
        List<T> subDataList = new ArrayList<>();
        int count = 0;
        for (T t : dataList) {

            subDataList.add(t);
            //计数
            count++;

            if (count >= subSize) {//这里可以使用等号==,个人习惯使用大于等于>=
				//数量达到subSize,做一次处理
                try {
                    callback.splitDataProcess(subDataList);
                } catch (Exception e) {
                    System.out.println("处理子数据集失败:"+e.getMessage());
                    throw e;
                } finally {
                	//清空计数变量和子集合
                    count = 0;
                    subDataList.clear();
                }

            }
        }

		//这里的剩余数据处理,非常容易遗漏,这也是为什么要封装公共代码的一个原因
		//封装成公共代码后,就不用担心遗漏这一部分数据
        if (subDataList.size() > 0) {
            //最后一次剩余数据量小于subSize,这里再处理一次
            try {
                callback.splitDataProcess(subDataList);
            } catch (Exception e) {
                System.out.println("处理子数据集失败:"+e.getMessage());
                throw e;
            }
        }
    }
}
  • 单元测试
java 复制代码
package cn.com.soulfox.common.functions.splitdata;

import org.junit.Before;
import org.junit.Test;

import java.util.Arrays;
import java.util.List;

/**
 *
 * @create 2024/6/24 15:50
 */
public class SplitDataListUtilTest {

    private List<String> dataList;

    @Before
    public void setup(){
        //准备数据
        dataList = Arrays.asList("a","b","c","1","2");
    }

    @Test
    public void test(){
        //定义子集合size
        int subSize = 2;
        //业务逻辑比较简单, 可直接写业务代码
        try {
            SplitDataListUtil.processData(this.dataList, subSize,
                    (subDataList -> {
                        System.out.println("简单业务代码++++");
                        subDataList.forEach(data ->{
                            System.out.println("简单业务代码: "+data);
                        });
                    }));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
  • 业务处理逻辑复杂
    实现类
java 复制代码
package cn.com.soulfox.common.functions.splitdata;

import java.util.List;

/**
 * 业务逻辑复杂
 * @create 2024/6/24 16:05
 */
public class ComplexBusinessImpl implements SplitDataCallback<String>{

    @Override
    public void splitDataProcess(List<String> subDataList) {
        System.out.println("复杂业务代码++++");
        subDataList.forEach(data ->{
            System.out.println("复杂业务代码: "+data);
        });
    }

}

加一个测试方法

java 复制代码
	@Test
    public void testComplexBusiness(){
        //定义子集合size
        int subSize = 2;
        //业务逻辑比较复杂, 创建接口实现类ComplexBusinessImpl 传入方法中
        ComplexBusinessImpl complexBusiness = new ComplexBusinessImpl();
        try {
            SplitDataListUtil.processData(this.dataList, subSize, complexBusiness);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

测试结果

总结一下。。。

拆分数据功能并不复杂,封装公共代码,也看不什么好处,实际开发的时候直接复制拆分代码即可。

这里主要是为了提出一种,设计通用功能的思路。任何功能,总有一部分结构性代码是不变的,变化的是业务处理代码。例如,上面的例子中,把大集合拆分成小集合的逻辑是不变的,变化的是数据处理逻辑。把不变的部分抽象出来封装成公共代码,同时把一些判空,边界数据做一下统一处理,这样就会在提高代码复用率的同时,减少出错几率。

相关推荐
keepDXRcuriosity29 分钟前
IDEA识别lombok注解问题
java·ide·intellij-idea
酷飞飞33 分钟前
C语言的复合类型、内存管理、综合案例
java·c语言·前端
宸津-代码粉碎机1 小时前
LLM 模型部署难题的技术突破:从轻量化到分布式推理的全栈解决方案
java·大数据·人工智能·分布式·python
都叫我大帅哥2 小时前
TOGAF实战解码:六大行业案例解析与成功启示
java
都叫我大帅哥2 小时前
RabbitMQ消息确认机制:从外卖小哥到数据安全的奇幻漂流
java·rabbitmq
周航宇JoeZhou4 小时前
JP3-3-MyClub后台后端(二)
java·mysql·vue·ssm·springboot·项目·myclub
羊锦磊5 小时前
[ java 网络 ] TPC与UDP协议
java·网络·网络协议
找不到、了5 小时前
Java设计模式之<建造者模式>
java·设计模式·建造者模式
Code blocks6 小时前
关于“LoggerFactory is not a Logback LoggerContext but Logback is on ......“的解决方案
java·spring boot·后端
04Koi.9 小时前
八股训练--Spring
java·后端·spring