0
2
1
0
专栏/.../

TiDB 的数据加载性能调优方案

 Gin  发表于  2023-02-01

作者:@heiheipp@Gin

一、数据加载调优项

  1. 调整数据加载程序的 jdbc 连接串:JDBC:mysql://{TiDBIP}:{TiDBPort}/{DBName}?characterEncoding=utf8&useSSL=false&useServerPrepStmts=true&cachePrepStmts=true&prepStmtCacheSqlLimit=10000000&useConfigs=maxPerformance&rewriteBatchedStatements=true&defaultfetchsize=-2147483648
  2. 将单行 insert 改为 batch insert 方式写入数据,形如 insert into table values(),(),(),()......,();
  3. 增加配置参数用于灵活调整 batch insert 语句的 batch size,最佳实践的 batch size 为 100~300 之间,也就是每个 insert 语句写入 100~300 行记录,每个 insert 作为一个事务,自动提交。
  4. batch insert 的并发可以调整到 64 或更高,同样需要增加配置参数用于灵活调整并发数。
  5. 并发调高了,但在 TiDB 上观察连接数不够,可能是连接池设置问题,参考本文调整连接池最大连接数:专栏 - 使用 TiDB 时的连接池和负载均衡器配置策略 | TiDB 社区
  6. 经常需要进行大量数据加载的表,需要设置为非聚簇索引表,并在表结构上增加 SHARD_ROW_ID_BITSPRE_SPLIT_REGIONS 以避免写入热点,参考官方文档进行调整 TiDB 热点问题处理 | PingCAP 文档中心
  7. 先创建索引,再加载数据。

二、MyBatis 的 batch insert 示例代码

1. 配置信息

程序基于 Sprint Boot 框架,在工程 resources 目录下的 application.yml 或 properties 文件中指定可被 spring context 获取的batch 提交参数,该参数用于程序中控制每一批提交的行数,如下图所示:

图片1.png

图1

2. 程序内容

2.1 获取配置

通过 spring 的 @Value@ConfigurationProperties 注解获取上述文件中的控制参数,本示例采用的是后者,即通过单独的参数实体类进行的数据封装,如下图所示:

图片2.png

图2

参考类文件 CtiqTransMainTableConfigModel.java:

package com.heiheipp.dataprepare.model;

import com.heiheipp.common.config.AbstractConfigModel;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

/**
 * @author heiheipp
 * @version 1.0
 * @className CtiqTransMainTableConfigModel
 * @desc TODO
 * @date 2022/3/16 17:05
 */
@Data
@Component("ctiqTransMainTableConfigModel")
@ConfigurationProperties(prefix = "ctiq.config.transmaintable")
public class CtiqTransMainTableConfigModel extends AbstractConfigModel {

    /**
     * 客户数量
     */
    private int custNum;

    /**
     * 每个客户的卡号数量
     */
    private int perCustCardNums;

    /**
     * 客户每日交易笔数
     */
    private int custTransNumEveryday;

    /**
     * 交易日期跨度
     */
    private int days;

    /**
     * 交易起始日期
     */
    private String startDay;

    /**
     * 交易终止日期
     */
    private String endDay;

    /**
     * 数据库单批次提交数量
     */
    private int batchNum;

    /**
     * 客户类型
     */
    private String custType;

    /**
     * 个人客户号前缀
     */
    private String personalCustIdPrefix;

    /**
     * 对公客户号前缀
     */
    private String companyCustIdPrefix;

    /**
     * 客户号长度
     */
    private int custIdLength;

    /**
     * 卡号前缀
     */
    private String cardBin;

    /**
     * 卡号长度
     */
    private int cardLength;

    /**
     * 账号前缀
     */
    private String accountPrefix;

    /**
     * 账号长度
     */
    private int accountLength;

    /**
     * 获取配置模型描述
     * @return
     */
    @Override
    public String getConfigModelDesc() {
        return "真实场景交易基础信息主表";
    }

    /**
     * 获取总处理数量
     * @return
     */
    @Override
    public int getTotalNums() {
        return getCustNum();
    }

    /**
     * 获取表头文件名
     * @return
     */
    @Override
    public String getFileHeaderName() {
        return "test_table_1_header.csv";
    }

    /**
     * 获取表文件名
     * @return
     */
    @Override
    public String getTargetFileName() {
        return "test_table_1.csv";
    }
}

2.2 程序控制

在另外的造数程序中,通过获取上述类的控制参数,通过循环控制的方式进行定制化批次大小的触发,即先循环构建每一条待插入的记录,并缓存到list对象中,再由数据库写入方法按照批次大小判断是否提交:

图片3.png

图3

图片4.png

图4

参考类文件 CtiqTransMainTableFutureTask.java:

package com.heiheipp.dataprepare.executor;

import cn.hutool.core.date.DateUnit;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.date.SystemClock;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil;
import com.github.javafaker.Faker;
import com.heiheipp.common.constant.ConfigConstant;
import com.heiheipp.common.context.RuntimeContext;
import com.heiheipp.common.context.SpringContextUtil;
import com.heiheipp.common.executor.AbstractFutureTask;
import com.heiheipp.common.mbg.model.TestTable1;
import com.heiheipp.common.service.TaskLogService;
import com.heiheipp.common.service.TestTable1Service;
import com.heiheipp.common.util.DataBuildUtil;
import com.heiheipp.common.util.DateTimeUtil;
import com.heiheipp.common.constant.DataModelConstant;
import com.heiheipp.dataprepare.model.CtiqTransMainTableConfigModel;
import com.heiheipp.dataprepare.service.impl.CTIQDataPrepareServiceImpl;

import java.io.File;
import java.math.BigDecimal;
import java.sql.Timestamp;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;

import lombok.extern.slf4j.Slf4j;

/**
 * @author heiheipp
 * @version 1.0
 * @className CtiqTransMainTableFutureTask
 * @desc TODO
 * @date 2022/3/16 16:58
 */
@Slf4j
public class CtiqTransMainTableFutureTask extends AbstractFutureTask<String> {

    private int targetType;

    private String fileLocation;

    private int threadCustNums;

    private long threadTotalNums;

    private int batchNums;

    private TaskLogService taskLogService;

    private TestTable1Service testTable1Service;

    private Map<String, Object> runtimeDatas = new ConcurrentHashMap<>();

    private String subThreadId;

    private AtomicLong processedNums = new AtomicLong(0L);

    private int committedRows = 0;

    private int startOffset;

    private CtiqTransMainTableConfigModel configModel;

    private Faker faker = new Faker(Locale.CHINA);

    private boolean isFirstLine;

    private SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");

    private Date start;

    private Date end;

    private long dayBetween = 1L;

    private boolean isRegisterDBLog;

    /**
     * 构造函数
     *
     * @param parentThreadId
     * @param threadCustNums
     * @param threadOrder
     * @param configModel
     */
    public CtiqTransMainTableFutureTask(long parentThreadId, int threadCustNums, int threadOrder,
                                        CtiqTransMainTableConfigModel configModel) {
        this.parentThreadId = String.valueOf(parentThreadId);
        this.threadCustNums = threadCustNums;
        this.taskLogService = SpringContextUtil.getBean(TaskLogService.class);
        this.testTable1Service = SpringContextUtil.getBean(TestTable1Service.class);
        this.configModel = configModel;
        this.batchNums = this.configModel.getBatchNum();

        // 参数计算
        this.startOffset = this.threadCustNums * configModel.getPerCustCardNums() * threadOrder + 1;
        this.targetType = SpringContextUtil.getBean(CTIQDataPrepareServiceImpl.class).getTargetType();
        this.fileLocation = SpringContextUtil.getBean(CTIQDataPrepareServiceImpl.class).getFileLocation();
        this.isFirstLine = !SpringContextUtil.getBean(CTIQDataPrepareServiceImpl.class).isFileMerge();
        this.isRegisterDBLog = SpringContextUtil.getBean(CTIQDataPrepareServiceImpl.class).isRegisterDBLog();

        // 以startDay和endDay计算要循环的天数
        if (!StrUtil.isEmpty(configModel.getStartDay()) && !StrUtil.isEmpty(configModel.getEndDay())) {
            try {
                this.start = this.dateFormat.parse(configModel.getStartDay());
                this.end = this.dateFormat.parse(configModel.getEndDay());

                // 日期校验
                if (this.start.compareTo(this.end) > 0) {
                    log.error("起始日期不能大于终止日期");
                    throw new RuntimeException("起始日期不能大于终止日期");
                }

                // 计算时间差
                this.dayBetween = DateUtil.between(this.start, this.end, DateUnit.DAY) + 1L;
            } catch (ParseException e) {
                log.error("子线程[{}]解析起始、终止时间异常", this.subThreadId);
                e.printStackTrace();
                throw new RuntimeException("解析起始、终止时间异常");
            }
        } else if (configModel.getDays() > 0) {
            this.dayBetween = configModel.getDays();
        }

        // 计算当前子线程要处理的总记录数
        this.threadTotalNums = (threadCustNums * configModel.getCustTransNumEveryday()) * this.dayBetween;
    }

    /**
     * 任务执行方法
     *
     * @return
     */
    @Override
    protected String submit(long subThreadId) {
        String result = "";
        this.subThreadId = String.valueOf(subThreadId);
        long startTime = System.currentTimeMillis();

        log.info("Sub thread[{}] start, threadTotalNums is {}, custNums is {}, duration is {} days, batchNums is {}, startOffset is {}, isRegisterDBLog is {}.",
                this.subThreadId, this.threadTotalNums, this.threadCustNums, this.dayBetween,
                this.batchNums, this.startOffset, this.isRegisterDBLog);

        // 计算公共变量
        String fileName = null;
        if (this.targetType == 2) {
            fileName = "boc_poc.test_table_1.0" + this.subThreadId + ".csv";
            this.fileLocation = this.fileLocation.equalsIgnoreCase("default") ?
                    new File(".").getAbsolutePath() : this.fileLocation;
            this.fileLocation += File.separator + fileName;
            log.info("Sub thread[{}] write data to file[{}].", this.subThreadId, this.fileLocation);

            // 将文件名写入主线程
            SpringContextUtil.getBean(CTIQDataPrepareServiceImpl.class).getTmpFileNameList().add(this.fileLocation);
        }

        // 处理业务公共信息
        int custIdFillLength;
        String custIdPrefix = null;
        switch (this.configModel.getCustType()) {
            case "Personal":
                custIdFillLength = this.configModel.getCustIdLength() -
                        this.configModel.getPersonalCustIdPrefix().length() - DataModelConstant.PROVINCE_LENGTH;
                custIdPrefix = this.configModel.getPersonalCustIdPrefix();
                break;
            case "Company":
                custIdFillLength = this.configModel.getCustIdLength() -
                        this.configModel.getCompanyCustIdPrefix().length() - DataModelConstant.PROVINCE_LENGTH;
                custIdPrefix = this.configModel.getCompanyCustIdPrefix();
                break;
            default:
                custIdFillLength = 0;
                custIdPrefix = "";
                break;
        }

        int cardNoFillLength = this.configModel.getCardLength() - this.configModel.getCardBin().length();
        String cardNoPrefix = this.configModel.getCardBin();
        int accountFillLength = this.configModel.getAccountLength() - this.configModel.getAccountPrefix().length();
        String accountPrefix = this.configModel.getAccountPrefix();

        // 登记任务开始日志
        if (isRegisterDBLog) {
            recordTask(ConfigConstant.TaskStatusEnum.START);
        }

        // 制造数据
        List<TestTable1> testTable1s = new ArrayList<>();
        TestTable1 testTable1 = null;
        int currentPercentage = 0, prevPercentage = 0;
        try {
            for (int i = 0; i < this.threadCustNums; i++) {
                // 按单客户每次交易笔数制造数据
                buildMultiTestTable1(testTable1s, testTable1, custIdPrefix, cardNoPrefix, accountPrefix, i,
                        custIdFillLength, cardNoFillLength, accountFillLength);

                // 数据写入操作
                if (this.processedNums.get() < this.threadTotalNums) {
                    writeData(testTable1s, false);
                } else {
                    writeData(testTable1s, true);
                }

                // 登记任务处理中日志
                prevPercentage = currentPercentage;
                currentPercentage = DataBuildUtil.getPercentge(this.processedNums.get(), this.threadTotalNums);
                if (DataBuildUtil.getPercentageWtihTens(this.processedNums.get(), this.threadTotalNums) &&
                        prevPercentage < currentPercentage) {
                    if (isRegisterDBLog) {
                        recordTask(ConfigConstant.TaskStatusEnum.PROCESSING);
                    }
                    log.info("Sub thread {} has been processing {}%, processedNums is {}, totalNums is {}, execution time is {}.",
                            this.subThreadId, currentPercentage, this.processedNums.get(), this.threadTotalNums,
                            SystemClock.now() - startTime);
                }
            }

            // 补提交剩余部分
            writeData(testTable1s, true);
        } catch (Exception e) {
            log.error("Sub thread {} error, and execution time is {}ms.", this.subThreadId,
                    System.currentTimeMillis() - startTime);
            e.printStackTrace();

            // 登记任务失败信息
            if (isRegisterDBLog) {
                recordTask(ConfigConstant.TaskStatusEnum.ERROR);
            }
        } finally {
            log.info("Sub thread {} finish, and execution time is {}ms.", this.subThreadId,
                    System.currentTimeMillis() - startTime);

            // 登记任务结束信息
            if (isRegisterDBLog) {
                recordTask(ConfigConstant.TaskStatusEnum.SUCCESS);
            }

            // 清理线程上下文
            RuntimeContext.clearRuntimeData();

            // 计数器操作
            CTIQDataPrepareServiceImpl.getCountDownLatch().countDown();
        }
        return result;
    }

    /**
     * 构建任务日志数据
     */
    private void buildRuntimeDatas(ConfigConstant.TaskStatusEnum taskStatusEnum, Object... args) {
        switch (taskStatusEnum) {
            case START:
                // 登记开始任务
                runtimeDatas.put(ConfigConstant.PARENT_THREAD_ID_KEY, this.parentThreadId);
                runtimeDatas.put(ConfigConstant.SUB_THREAD_ID_KEY, subThreadId);
                runtimeDatas.put(ConfigConstant.TASK_TYPE_KEY, ConfigConstant.TaskTypeEnum.DATA_PREPARE.getTypeDesc());
                runtimeDatas.put(ConfigConstant.TASK_CONTENT_KEY, getTaskContent());
                runtimeDatas.put(ConfigConstant.TASK_STATUS_KEY, ConfigConstant.TaskStatusEnum.START.getStatus());
                runtimeDatas.put(ConfigConstant.TASK_TOTAL_NUMS_KEY, this.threadTotalNums);
                runtimeDatas.put(ConfigConstant.TASK_PROCESSED_NUMS_KEY, 0L);
                runtimeDatas.put(ConfigConstant.TASK_CREATE_TIME_KEY, SystemClock.now());
                runtimeDatas.put(ConfigConstant.TASK_UPDATE_TIME_KEY, SystemClock.now());
                runtimeDatas.put(ConfigConstant.TASK_STATUS_ENUM_KEY, ConfigConstant.TaskStatusEnum.START);
                break;
            case PROCESSING:
                // 更新处理中任务
                runtimeDatas.put(ConfigConstant.TASK_STATUS_KEY, ConfigConstant.TaskStatusEnum.PROCESSING.getStatus());
                runtimeDatas.put(ConfigConstant.TASK_PROCESSED_NUMS_KEY, processedNums.get());
                runtimeDatas.put(ConfigConstant.TASK_STATUS_ENUM_KEY, ConfigConstant.TaskStatusEnum.PROCESSING);
                runtimeDatas.put(ConfigConstant.TASK_UPDATE_TIME_KEY, SystemClock.now());
                break;
            case ERROR:
                // 任务异常
                runtimeDatas.put(ConfigConstant.TASK_STATUS_KEY, ConfigConstant.TaskStatusEnum.ERROR.getStatus());
                runtimeDatas.put(ConfigConstant.TASK_PROCESSED_NUMS_KEY, processedNums.get());
                runtimeDatas.put(ConfigConstant.TASK_STATUS_ENUM_KEY, ConfigConstant.TaskStatusEnum.ERROR);
                runtimeDatas.put(ConfigConstant.TASK_UPDATE_TIME_KEY, SystemClock.now());
                break;
            case SUCCESS:
                // 任务成功结束
                runtimeDatas.put(ConfigConstant.TASK_STATUS_KEY, ConfigConstant.TaskStatusEnum.SUCCESS.getStatus());
                runtimeDatas.put(ConfigConstant.TASK_PROCESSED_NUMS_KEY, processedNums.get());
                runtimeDatas.put(ConfigConstant.TASK_STATUS_ENUM_KEY, ConfigConstant.TaskStatusEnum.SUCCESS);
                runtimeDatas.put(ConfigConstant.TASK_UPDATE_TIME_KEY, SystemClock.now());
                break;
            default:
                break;
        }
    }

    /**
     * 获取任务类型
     *
     * @return
     */
    private String getTaskContent() {
        return "TEST_TABLE_1表数据准备";
    }

    /**
     * 登记任务日志
     *
     * @param taskStatusEnum
     */
    private void recordTask(ConfigConstant.TaskStatusEnum taskStatusEnum) {
        switch (taskStatusEnum) {
            case START:
                // 登记任务开始日志
                buildRuntimeDatas(ConfigConstant.TaskStatusEnum.START);
                break;
            case PROCESSING:
                // 登记任务处理中日志
                buildRuntimeDatas(ConfigConstant.TaskStatusEnum.PROCESSING);
                break;
            case ERROR:
                // 登记任务失败信息
                buildRuntimeDatas(ConfigConstant.TaskStatusEnum.ERROR);
                break;
            case SUCCESS:
                // 登记任务结束信息
                buildRuntimeDatas(ConfigConstant.TaskStatusEnum.SUCCESS);
                break;
            default:
                break;
        }

        RuntimeContext.setRuntimeDatas(runtimeDatas);

        try {
            taskLogService.recordTaskLog();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 构建TestTable1对象
     * @param testTable1
     * @param custId
     * @param cardNoArray
     * @param accountArray
     * @param custName
     * @param startLongTime
     * @param endLongTime
     */
    private void buildTestTable1(TestTable1 testTable1, String custId, String[] cardNoArray, String[] accountArray,
                                 String custName, long startLongTime, long endLongTime) {
        int num = DataBuildUtil.getIntegerRandom(this.configModel.getPerCustCardNums());

        testTable1.setColumn1(new BigDecimal(IdUtil.getSnowflakeNextIdStr()));
        testTable1.setColumn2(DataBuildUtil.getRandomWithLength(3));
        //testTable1.setColumn3(DataBuildUtil.getRandomWithLength(5));
        testTable1.setColumn3(String.valueOf(RandomUtil.randomInt(1, 10000)));
        testTable1.setColumn4(accountArray[num]);
        testTable1.setColumn5(new Timestamp(DateTimeUtil.randomLongTime(startLongTime, endLongTime)));
        testTable1.setColumn6(DataBuildUtil.getRandomWithLength(8));
        testTable1.setColumn7(DataBuildUtil.getRandomWithLength(8));
        testTable1.setColumn8(DataBuildUtil.getRandomWithLength(5));
        testTable1.setColumn9(testTable1.getColumn4());
        testTable1.setColumn10("0001");
        //testTable1.setColumn11(Integer.valueOf(DataBuildUtil.getRandomWithLength(3)));
        testTable1.setColumn11(RandomUtil.randomInt(1, 999));
        testTable1.setColumn12(Integer.valueOf(DataBuildUtil.getRandomWithLength(2)));
        testTable1.setColumn13(DataBuildUtil.getRandomWithLength(12));
        testTable1.setColumn14(UUID.randomUUID().toString().replaceAll("-", ""));
        testTable1.setColumn15(DataBuildUtil.getRandomWithLength(4));
        testTable1.setColumn16(DataBuildUtil.getRandomWithLength(4));
        testTable1.setColumn17(DataBuildUtil.generateMsg("某", 12, "产品"));
        testTable1.setColumn18(DataBuildUtil.generateMsg("某", 16, "文书合同"));
        testTable1.setColumn19("1");
        testTable1.setColumn20("01");
        testTable1.setColumn21("001");
        testTable1.setColumn22(cardNoArray[num]);
        testTable1.setColumn23("01");
        testTable1.setColumn24("1");
        testTable1.setColumn25(DataBuildUtil.getTranType());
        testTable1.setColumn26(DataBuildUtil.getTranTypeDesc(testTable1.getColumn25()));
        testTable1.setColumn27(DateUtil.date(testTable1.getColumn5().getTime()).toTimeStr());
        testTable1.setColumn28(DataBuildUtil.getRandomWithLength(7));
        testTable1.setColumn29(DataModelConstant.DEFAULT_BRANCH_NO);
        testTable1.setColumn30(DataModelConstant.DEFAULT_BRANCH_NAME);
        testTable1.setColumn31(DateUtil.parse("2021-05-01", "yyyy-MM-dd"));
        testTable1.setColumn32("0");
        testTable1.setColumn33("112233");
        testTable1.setColumn34("112233");
        testTable1.setColumn35("01");
        testTable1.setColumn36("提示码");
        testTable1.setColumn37(DataBuildUtil.getChannel());
        testTable1.setColumn38(DateUtil.date(testTable1.getColumn5().getTime()).toSqlDate());
        testTable1.setColumn39(testTable1.getColumn13());
        testTable1.setColumn40("156");
        testTable1.setColumn41("CNY");
        testTable1.setColumn42(NumberUtil.round("1.00", 2));
        testTable1.setColumn43(DataBuildUtil.getDebitOrCreditFlag());
        testTable1.setColumn44(DataBuildUtil.getChongzhengFlag(testTable1.getColumn43()));
        testTable1.setColumn45(DataBuildUtil.getRandomBigDecimal(10000, 2));
        testTable1.setColumn46(testTable1.getColumn45().add(new BigDecimal(100)));
        testTable1.setColumn47(testTable1.getColumn46());
        testTable1.setColumn48(DataModelConstant.ZERO_AMOUNT);
        testTable1.setColumn49(DataModelConstant.ZERO_AMOUNT);
        testTable1.setColumn50(DataModelConstant.ZERO_AMOUNT);
        testTable1.setColumn51(DataModelConstant.ZERO_AMOUNT);
        testTable1.setColumn52(DataModelConstant.ZERO_AMOUNT);
        testTable1.setColumn53(DataModelConstant.ZERO_AMOUNT);
        testTable1.setColumn54(DataModelConstant.ZERO_AMOUNT);
        testTable1.setColumn55(DataModelConstant.ZERO_AMOUNT);
        testTable1.setColumn56(DataModelConstant.ZERO_AMOUNT);
        testTable1.setColumn57(DataModelConstant.ZERO_AMOUNT);
        testTable1.setColumn58(DataModelConstant.ZERO_AMOUNT);
        testTable1.setColumn59(DataModelConstant.ZERO_AMOUNT);
        testTable1.setColumn60(DataBuildUtil.generateMsg("某", 30, "备注"));
        testTable1.setColumn61(DataBuildUtil.generateMsg("某", 15, "用途"));
        testTable1.setColumn62(DataBuildUtil.generateMsg("某", 30, "附言"));
        testTable1.setColumn63(DataBuildUtil.generateMsg("某", 30, "摘要"));
        testTable1.setColumn64("1");
        testTable1.setColumn65("现钞");
        testTable1.setColumn66(DataModelConstant.DEFAULT_PAYER_NAME);
        testTable1.setColumn67("1234567890123456789");
        testTable1.setColumn68("1234567890123456789");
        testTable1.setColumn69("1111");
        testTable1.setColumn70(Integer.valueOf(DataBuildUtil.getRandomWithLength(3)));
        testTable1.setColumn71(Integer.valueOf(DataBuildUtil.getRandomWithLength(2)));
        testTable1.setColumn72(DataModelConstant.DEFAULT_BRANCH_NAME);
        testTable1.setColumn73(DataModelConstant.DEFAULT_BRANCH_NO);
        testTable1.setColumn74(DataModelConstant.DEFAULT_PROVINCE_LH_NO);
        // 付款人姓名
        if ("D".equalsIgnoreCase(testTable1.getColumn43())) {
            testTable1.setColumn75(custName);
        } else {
            testTable1.setColumn75(DataModelConstant.DEFAULT_PAYER_NAME);
        }

        testTable1.setColumn76("1234567890123456789");
        testTable1.setColumn77("12345678901234567890001");
        testTable1.setColumn78("1111");
        testTable1.setColumn79(Integer.valueOf(DataBuildUtil.getRandomWithLength(3)));
        testTable1.setColumn80(Integer.valueOf(DataBuildUtil.getRandomWithLength(2)));
        testTable1.setColumn81(DataModelConstant.DEFAULT_BRANCH_NAME);
        testTable1.setColumn82(DataModelConstant.DEFAULT_BRANCH_NO);
        testTable1.setColumn83(DataModelConstant.DEFAULT_PROVINCE_LH_NO);
        testTable1.setColumn84(DataModelConstant.DEFAULT_RECEIVER_NAME);
        testTable1.setColumn85("9876543210987654321");
        testTable1.setColumn86("12345678901234567890001");
        testTable1.setColumn87("1111");
        testTable1.setColumn88(Integer.valueOf(DataBuildUtil.getRandomWithLength(3)));
        testTable1.setColumn89(Integer.valueOf(DataBuildUtil.getRandomWithLength(2)));
        testTable1.setColumn90(DataModelConstant.DEFAULT_BRANCH_NAME);
        testTable1.setColumn91(DataModelConstant.DEFAULT_BRANCH_NO);
        testTable1.setColumn92(DataModelConstant.DEFAULT_PROVINCE_LH_NO);
        // 收款人姓名
        if ("C".equalsIgnoreCase(testTable1.getColumn43())) {
            testTable1.setColumn93(custName);
        } else {
            testTable1.setColumn93(DataModelConstant.DEFAULT_RECEIVER_NAME);
        }

        testTable1.setColumn94("9876543210987654321");
        testTable1.setColumn95("98765432109876543210001");
        testTable1.setColumn96("1111");
        testTable1.setColumn97(Integer.valueOf(DataBuildUtil.getRandomWithLength(3)));
        testTable1.setColumn98(Integer.valueOf(DataBuildUtil.getRandomWithLength(2)));
        testTable1.setColumn99(DataModelConstant.DEFAULT_BRANCH_NAME);
        testTable1.setColumn100(DataModelConstant.DEFAULT_BRANCH_NO);
        testTable1.setColumn101(DataModelConstant.DEFAULT_PROVINCE_LH_NO);
        testTable1.setColumn102("2222");
        testTable1.setColumn103(DataBuildUtil.generateMsg("某", 2, "凭证"));
        testTable1.setColumn104("112233445566778899");
        testTable1.setColumn105("2222");
        testTable1.setColumn106(DataBuildUtil.generateMsg("某", 2, "产生凭证"));
        testTable1.setColumn107("112233445566778899");
        testTable1.setColumn108("998877665544332211");
        testTable1.setColumn109("1111");
        testTable1.setColumn110(Integer.valueOf(4));
        testTable1.setColumn111(DataModelConstant.ZERO_AMOUNT);
        testTable1.setColumn112(DataModelConstant.ZERO_AMOUNT);
        testTable1.setColumn113(DataModelConstant.DEFAULT_ADDRESS_PHONE);
        testTable1.setColumn114(DataModelConstant.DEFAULT_ADDRESS_PHONE);
        testTable1.setColumn115("1122334455");
        testTable1.setColumn116("1122334455");
        testTable1.setColumn117("1122334455");
        testTable1.setColumn118("1122334455");
        testTable1.setColumn119("1122334455");
        testTable1.setColumn120("1122334455");
        testTable1.setColumn121(DataBuildUtil.generateMsg("某", 1, "系统"));
        testTable1.setColumn122("11111");
        testTable1.setColumn123(DateUtil.date(testTable1.getColumn5().getTime()).toSqlDate());
        testTable1.setColumn124("1111");
        testTable1.setColumn125("11111");
        testTable1.setColumn126("1");
        testTable1.setColumn127("1");
        testTable1.setColumn128("11112222");
        testTable1.setColumn129(DataModelConstant.DEFAULT_BRANCH_NO);
        testTable1.setColumn130("1");
        testTable1.setColumn131("1");
        testTable1.setColumn132("1");
        testTable1.setColumn133("1");
        testTable1.setColumn134(DateUtil.date(testTable1.getColumn5().getTime()).toSqlDate());
        testTable1.setColumn135("1");
        testTable1.setColumn136("1");
        testTable1.setColumn137("1");
        testTable1.setColumn138("1");
        testTable1.setColumn139("1");
        testTable1.setColumn140("1");
        testTable1.setColumn141("1");
        testTable1.setColumn142("1");
        testTable1.setColumn143("1");
        testTable1.setColumn144(custId);
        testTable1.setColumn145("1");
        testTable1.setColumn146("1");

        // 生成json字符串
        testTable1.setColumn151(DataModelConstant.DEFAULT_JSON_STRING);
    }

    /**
     * 构建TestTable1列表
     * @param testTable1s
     * @param testTable1
     * @param custIdPrefix
     * @param cardNoPrefix
     * @param accountPrefix
     * @param currentOffset
     * @param custIdFillLength
     * @param cardNoFillLength
     * @param accountFillLength
     */
    private void buildMultiTestTable1(List<TestTable1> testTable1s, TestTable1 testTable1, String custIdPrefix,
                                      String cardNoPrefix, String accountPrefix, int currentOffset,
                                      int custIdFillLength, int cardNoFillLength, int accountFillLength) {
        // 公共信息
        int offset = currentOffset * this.configModel.getPerCustCardNums();
        String custId = DataBuildUtil.generateCustId(custIdPrefix, this.startOffset, offset, custIdFillLength);
        String custName = this.faker.name().fullName();
        String[] cardNoArray = new String[this.configModel.getPerCustCardNums()];
        String[] accountArray = new String[this.configModel.getPerCustCardNums()];
        for (int k = 0; k < this.configModel.getPerCustCardNums(); k++) {
            cardNoArray[k] = DataBuildUtil.generateCardNo(cardNoPrefix, this.startOffset,
                    offset + k, cardNoFillLength);
            accountArray[k] = DataBuildUtil.generateAccount(accountPrefix, this.startOffset,
                    offset + k, accountFillLength);
        }

        // 外层按照日期循环
        for (int i = 0; i < (new Long(this.dayBetween)).intValue(); i++) {
            long startLongTime = DateUtil.offsetDay(this.start, i).getTime();
            long endLongTime = DateUtil.offsetDay(this.start, i + 1).getTime() - 1L;

            // 内层按照每日交易数循环
            for (int j = 0; j < this.configModel.getCustTransNumEveryday(); j++) {
                testTable1 = new TestTable1();
                buildTestTable1(testTable1, custId, cardNoArray, accountArray, custName, startLongTime, endLongTime);

                testTable1s.add(testTable1);

                // 递增总生成数量和待提交数量
                this.processedNums.incrementAndGet();
                this.committedRows++;
            }
        }
    }

    /**
     * 写文件操作
     * @param testTable1s
     * @param flag
     */
    private void writeData(List<TestTable1> testTable1s, boolean flag) {
        if (testTable1s.size() > 0) {
            if (this.targetType == 1) {
                //写入数据库
                if (this.committedRows >= this.batchNums) {
                    this.testTable1Service.batchInsert(testTable1s);
                    testTable1s.clear();
                    this.committedRows = 0;
                } else if (flag) {
                    this.testTable1Service.batchInsert(testTable1s);
                    testTable1s.clear();
                    this.committedRows = 0;
                }
            } else if (this.targetType == 2) {
                //写入csv文件
                this.testTable1Service.writeCsv(this.fileLocation, testTable1s, this.isFirstLine);
                testTable1s.clear();
                this.isFirstLine = false;

                //末尾追加空行
                if (flag) {
                    this.testTable1Service.writeCsv();
                }
            }
        }
    }
}

2.3 DAO 相关操作

本工程使用的是 MyBatis ORM 框架,在 mapper xml 文件中增加了 batchInsert sql,通过 foreach 方式进行的批量 insert,如下图:

图片5.png

图5

图片6.png

图6

对应的 mapper interface 类如下,红线部分主要是为了 mapper xml 中的参数定位:

图片7.png

图7

对应的 service 类通过 spring 事务的方式、以 PROPAGATION_REQUIRES_NEW 的事务传播方式进行的事务控制:

图片8.png

图8

0
2
1
0

版权声明:本文为 TiDB 社区用户原创文章,遵循 CC BY-NC-SA 4.0 版权协议,转载请附上原文出处链接和本声明。

评论
暂无评论