背景
我们在做软件开发时,经常会遇到把大集合的数据,拆分成子集合处理。例如批量数据插入数据库时,一次大约插入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) {
//子集长度小于等于 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;
}
}
}
}
- 单元测试
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();
}
}
}
- 业务处理逻辑复杂
实现类
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);
});
}
}
加一个测试方法
@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();
}
}
测试结果
总结一下。。。
拆分数据功能并不复杂,封装公共代码,也看不什么好处,实际开发的时候直接复制拆分代码即可。
这里主要是为了提出一种,设计通用功能的思路。任何功能,总有一部分结构性代码是不变的,变化的是业务处理代码。例如,上面的例子中,把大集合拆分成小集合的逻辑是不变的,变化的是数据处理逻辑。把不变的部分抽象出来封装成公共代码,同时把一些判空,边界数据做一下统一处理,这样就会在提高代码复用率的同时,减少出错几率。