add 新增ResultHandler处理器

This commit is contained in:
AprilWind 2025-08-03 15:42:16 +08:00
parent 0ba909c52e
commit a58caadbaa
3 changed files with 213 additions and 0 deletions

View File

@ -0,0 +1,101 @@
package org.dromara.common.mybatis.handler;
import cn.hutool.core.collection.CollUtil;
import org.apache.ibatis.session.ResultContext;
import org.apache.ibatis.session.ResultHandler;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
/**
* 批量结果处理器
*
* <p>将查询结果按指定批量大小缓存
* 达到批量大小时调用批量消费函数进行批量处理避免一次性加载全部数据导致内存压力过大
*
* <p>适用于导出Excel批量写库定时上报等需要批量操作场景
* 能有效降低内存使用并提升处理性能
*
* <p><b>使用须知</b><br>
* 1. 构造时传入非空的批量消费函数和合法的批量大小>0<br>
* 2. 结果回调时自动缓存并批量触发消费函数<br>
* 3. 查询完成后务必调用 {@link #flush()}处理剩余不足批量的数据避免数据遗漏
*
* @param <T> 查询结果类型
* @author AprilWind
*/
public class BatchResultHandler<T> implements ResultHandler<T> {
/**
* 批量数据消费函数
*
* <p>注意长时间的批量处理操作如写Excel写数据库或调用远程接口可能导致数据库连接超时或断开
* 以及MyBatis查询线程阻塞影响系统性能和稳定性
*
* <p>因此推荐
* <ul>
* <li>结合合适的批量大小和限速策略避免瞬时过载</li>
* <li>采用异步写入机制将批量处理操作与MyBatis查询线程解耦减少阻塞</li>
* <li>合理拆分查询任务避免长时间持有数据库连接</li>
* </ul>
*
* <p>这样能有效保障系统的高性能和稳定运行
*/
private final Consumer<List<T>> batchConsumer;
/**
* 批量大小必须大于0
*/
private final int batchSize;
/**
* 当前缓存数据
*/
private final List<T> buffer;
/**
* 构造批量结果处理器
*
* @param batchConsumer 批量消费函数不能为null
* @param batchSize 批量大小必须大于0
* @throws NullPointerException batchConsumer为null时抛出
* @throws IllegalArgumentException batchSize小于等于0时抛出
*/
public BatchResultHandler(Consumer<List<T>> batchConsumer, int batchSize) {
if (batchConsumer == null) {
throw new NullPointerException("batchConsumer不能为null");
}
if (batchSize <= 0) {
throw new IllegalArgumentException("batchSize必须大于0");
}
this.batchConsumer = batchConsumer;
this.batchSize = batchSize;
this.buffer = new ArrayList<>(batchSize);
}
/**
* MyBatis 查询结果回调缓存单条结果
* 达到批量大小时触发批量消费函数处理缓存数据
*
* @param context 查询结果上下文包含单条结果
*/
@Override
public void handleResult(ResultContext<? extends T> context) {
buffer.add(context.getResultObject());
if (buffer.size() >= batchSize) {
flush();
}
}
/**
* 刷新缓存触发批量消费函数处理剩余数据
* 通常查询结束后调用确保所有数据均被处理
*/
public void flush() {
if (CollUtil.isNotEmpty(buffer)) {
batchConsumer.accept(new ArrayList<>(buffer));
buffer.clear();
}
}
}

View File

@ -0,0 +1,64 @@
package org.dromara.common.mybatis.handler;
import org.apache.ibatis.session.ResultContext;
import org.apache.ibatis.session.ResultHandler;
import java.util.function.Consumer;
import java.util.function.Predicate;
/**
* 过滤处理器
* <p>
* 通过传入的过滤条件{@link Predicate}筛选查询结果
* 仅对满足条件的结果调用消费函数进行处理
* <p>
* 适用于需要对查询结果做条件过滤再处理的场景
* 避免无用数据进入后续处理逻辑提高效率
*
* @param <T> 查询结果的数据类型
* @author AprilWind
*/
public class FilterResultHandler<T> implements ResultHandler<T> {
/**
* 过滤条件用于判断是否处理该条结果
*/
private final Predicate<T> filter;
/**
* 处理满足过滤条件的结果的消费函数
*/
private final Consumer<T> consumer;
/**
* 构造过滤处理器
*
* @param filter 过滤条件
* @param consumer 满足过滤条件时的处理函数
* @throws NullPointerException 如果 filter consumer null
*/
public FilterResultHandler(Predicate<T> filter, Consumer<T> consumer) {
if (filter == null) {
throw new NullPointerException("过滤条件,不能为空");
}
if (consumer == null) {
throw new NullPointerException("处理函数,不能为空");
}
this.filter = filter;
this.consumer = consumer;
}
/**
* 查询结果回调先用过滤条件判断是否处理
* 满足条件则调用消费函数否则忽略该条结果
*
* @param context 查询结果上下文包含单条结果
*/
@Override
public void handleResult(ResultContext<? extends T> context) {
T result = context.getResultObject();
if (filter.test(result)) {
consumer.accept(result);
}
}
}

View File

@ -0,0 +1,48 @@
package org.dromara.common.mybatis.handler;
import org.apache.ibatis.session.ResultContext;
import org.apache.ibatis.session.ResultHandler;
import java.util.function.Consumer;
/**
* 单条处理处理器
*
* <p>适用于无需缓存或批量处理直接对结果逐条消费的场景
* 逻辑简单直接实时处理每条数据
*
* @param <T> 查询结果的数据类型
* @author AprilWind
*/
public class SimpleResultHandler<T> implements ResultHandler<T> {
/**
* 消费单条查询结果的函数
*/
private final Consumer<T> consumer;
/**
* 构造 SimpleResultHandler 实例
*
* @param consumer 处理单条查询结果的消费函数不能为空
* @throws NullPointerException 如果 consumer null 抛出
*/
public SimpleResultHandler(Consumer<T> consumer) {
if (consumer == null) {
throw new NullPointerException("consumer不能为null");
}
this.consumer = consumer;
}
/**
* MyBatis 查询结果回调方法
* 每接收到一条查询结果即调用消费函数进行处理
*
* @param context 查询结果上下文包含当前单条结果对象
*/
@Override
public void handleResult(ResultContext<? extends T> context) {
T result = context.getResultObject();
consumer.accept(result);
}
}