公司的Druid不允许批量提交,无奈自己动手写,觉得可以封装一下。
写了三个方案,这里整理出第一个方案,没有使用多线程,单纯异步的。
适用情形
1、连接池禁止直接向数据库批量插入;
2、跑批的业务场景较多;
3、插入前需要遍历处理数据;
4、需要异步执行的但不需要统一提交/回滚的;
封装示例
关键点:泛型、手动管理事务、函数式编程。
- 泛型解决与数据类型耦合问题
- 函数编程解决业务方法耦合问题
- 手动事务提交解决插入效率问题
/**
* @author SurfingMaster
* @date 2023/3/8
*/
@Component("batchGenerateExe")
public class BatchGenerateExe<T> {
private static final Logger log = LoggerFactory.getLogger(BatchGenerateExe.class);
/* 可以作为execute的参数 由调用方传入 */
private static final long PAGE_SIZE = 1000L;
@Autowired
private PlatformTransactionManager transactionManager;
/**
* BatchGenerateExe.execute <br>
*
* @param qurFunc qurFunc
* @param insertSingleFunc insertSingleFunc
* @author SurfingMaster
* @description BatchGenerateExe.execute
*/
public void execute(BiFunction<Long, Long, List<T>> qurFunc, Consumer<T> insertSingleFunc) {
log.info("===== BatchGenerateExe.execute-STARTED. =====");
List<T> partitionData;
for (long page = 1, rows = PAGE_SIZE, offset = 0L; ; offset = (++page - 1) * PAGE_SIZE, rows = (page * PAGE_SIZE)) {
try {
partitionData = qurFunc.apply(offset, rows);
} catch (Exception e) {
// TODO 异常处理的逻辑(若有)
partitionData = null;
}
if (CollectionUtils.isEmpty(partitionData)) {
break;
}
insertBatch(partitionData, insertSingleFunc);
}
log.info("===== BatchGenerateExe.execute-COMPLETED. =====");
}
/**
* BatchGenerateExe.insertBatch <br>
*
* @param records records
* @param insertSingleFunc insertSingleFunc
* @author SurfingMaster
* @description BatchGenerateExe.insertBatch
*/
// @
private void insertBatch(List<T> records, Consumer<T> insertSingleFunc) {
log.info("===== BatchGenerateExe.insertBatch-STARTED. =====");
if (CollectionUtils.isEmpty(records)) {
return;
}
DefaultTransactionDefinition def = new DefaultTransactionDefinition();
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
TransactionStatus status = transactionManager.getTransaction(def);
try {
records.forEach(insertSingleFunc);
} catch (Exception e) {
log.error("===== ERROR-BatchInsertTask.insertBatch-e:{} =====", e.getMessage());
// TODO 异常处理的逻辑(若有)
} finally {
transactionManager.commit(status);
}
log.info("===== BatchGenerateExe.insertBatch-COMPLETED. =====");
}
}
编译结果
不出意料,执行器里的这个for被编译成while,批量插入insertBatch的判空if也被展开了。
使用示例
- 需求:当天00:10查询前一天的数据。传参只有两个,大于等于前一天零点,小于当天零点。
- 实现:完成业务查询方法,完成插入前业务操作方法,调用封装好的批量执行器。
/**
* @author SurfingMaster
* @date 2023/3/8
*/
@DubboService
public class XxxDetailScheduleImpl implements XxxDetailScheduleI {
@Autowired
private BatchGenerateExe<XxxDetail> batchExe;
@Async
@Override
public void xxxDetailSchedule() {
log.info("===== xxx =====");
final Date zeroOfCurrentDay = getZeroOfCurrentDay();
final Date lastDayZero = getLastDayZero();
batchExe.execute(
(offset, rows) -> xxxDetailDao.selectXxxPartition(zeroOfCurrentDay, offset, rows),
(element) -> insertDetail(element, zeroOfCurrentDay, lastDayZero));
batchExe.execute(
(offset, rows) -> xxxDetailDao.selectXxxPartitionCase1(zeroOfCurrentDay, offset, rows),
(element) -> insertDetail(element, zeroOfCurrentDay, lastDayZero));
batchExe.execute(
(offset, rows) -> xxxDetailDao.selectXxxPartitionCase2(zeroOfCurrentDay, offset, rows),
(element) -> insertDetail(element, zeroOfCurrentDay, lastDayZero));
log.info("===== xxx =====");
}
public void insertDetail(xxxDetail xxx, Date zeroOfCurrentDay, Date lastDayZero) {
if (xxx == null) return;
// 业务逻辑...
// 数据 插入/更新...
xxxGwateway.save(xxx);
}
}
使用注意
- 代码性能:考虑到函数式编程的数据特性,请注意内存资源使用情况;
- 数据安全:处理异常、回滚等。
- 业务安全:处理可能被重复调用的情况。