背景
我们在做软件开发时,经常会遇到把大集合的数据,拆分成子集合处理。例如批量数据插入数据库时,一次大约插入5000条数据比较合理,但是有时候待插入的数据远远大于5000条。这时候就需要进行数据拆分。数据拆分基本逻辑并不复杂,下面尝试把数据拆分逻辑封装一下。
拆分逻辑
拆分过程唯一要求就是数据不能遗漏,也不能重复处理。
- 定义子集合大小
- 遍历源数据集合,达到一个子集合大小,
- 根据业务需要开始处理子集合数据
- 直到处理完所有数据
代码
先实现基本功能代码
/*** @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;}}}
以上的代码,逻辑清晰且没有复杂的索引计算,是个比较好的实现。但是代码没有通用性,每次遇到数据拆分,都要写一遍拆分呢逻辑,写的多了难免出问题。仔细看下代码,除了处理子集合数据的业务代码方法,其他代码都是一样的。下面改造一下,子集合数据的业务方法由外部传入。那么拆分逻辑部分就可以通用,不用担心出问题了。
新实现
- 业务处理接口
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);
}
- 拆分工具类
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) {//子集长度小于等于 0return;}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;}}}
}
- 单元测试
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;@Beforepublic void setup(){//准备数据dataList = Arrays.asList("a","b","c","1","2");}@Testpublic void test(){//定义子集合sizeint subSize = 2;//业务逻辑比较简单, 可直接写业务代码try {SplitDataListUtil.processData(this.dataList, subSize,(subDataList -> {System.out.println("简单业务代码++++");subDataList.forEach(data ->{System.out.println("简单业务代码: "+data);});}));} catch (Exception e) {e.printStackTrace();}}
}
- 业务处理逻辑复杂
实现类
package cn.com.soulfox.common.functions.splitdata;import java.util.List;/*** 业务逻辑复杂* @create 2024/6/24 16:05*/
public class ComplexBusinessImpl implements SplitDataCallback<String>{@Overridepublic void splitDataProcess(List<String> subDataList) {System.out.println("复杂业务代码++++");subDataList.forEach(data ->{System.out.println("复杂业务代码: "+data);});}}
加一个测试方法
@Testpublic void testComplexBusiness(){//定义子集合sizeint subSize = 2;//业务逻辑比较复杂, 创建接口实现类ComplexBusinessImpl 传入方法中ComplexBusinessImpl complexBusiness = new ComplexBusinessImpl();try {SplitDataListUtil.processData(this.dataList, subSize, complexBusiness);} catch (Exception e) {e.printStackTrace();}}
测试结果
总结一下。。。
拆分数据功能并不复杂,封装公共代码,也看不什么好处,实际开发的时候直接复制拆分代码即可。
这里主要是为了提出一种,设计通用功能的思路。任何功能,总有一部分结构性代码是不变的,变化的是业务处理代码。例如,上面的例子中,把大集合拆分成小集合的逻辑是不变的,变化的是数据处理逻辑。把不变的部分抽象出来封装成公共代码,同时把一些判空,边界数据做一下统一处理,这样就会在提高代码复用率的同时,减少出错几率。