1.项目中解析ctl文件写死的固定行数获取,不具有容错性,实际项目中对ctl文件中各项标志的顺序实际没有要求; 2.数据文件使用的是拼接的sql语句,不安全,并且当数据中有 ’ 单引号的时候就会报错,修改为使用PreparedStatement 初始化 sql语句,并且设置参数方式,执行数据导入;
CtlFileParser.java
package com.xiaoma.fileimport.service; import lombok.Data; import java.util.List; /** * 信号文件实体类,解析获取信号文件 * @author mawuhui * @since 2020-07-03 16:34 */ @Data public class CtlFileParser { // 数据文件字段列表 private String columnList; // 数据文件字段个数 private Integer size; // 预编译sql语句 private String sql; // 分隔符 private String split; // 表名 private String tableName; // 数据文件行数 private Integer rows; // 数据文件类型 private String type; public CtlFileParser(List<String> ctlFile) { // 解析ctl文件 initParser(ctlFile); // 根据解析文件生成sql语句 parserSql(); } // 构造器私有化,只能使用带参构造器 private CtlFileParser() { } /** * 解析ctl文件,获取ctl文件的值 * * @param ctlFile */ private void initParser(List<String> ctlFile) { for (String line : ctlFile) { String[] baseline = line.split("="); if (baseline[0].equals("columnList")) { this.columnList = baseline[1]; } else if (baseline[0].equals("split")) { this.split = baseline[1]; } else if (baseline[0].equals("tableName")) { this.tableName = baseline[1]; } else if (baseline[0].equals("rows")) { this.rows = Integer.parseInt(baseline[1]); } else if (baseline[0].equals("type")) { this.type = baseline[1]; } } } /** * 生成sql语句 */ private void parserSql() { StringBuilder builder1 = new StringBuilder(); // 如果是增量报送,sql 语句表应该为 表名_TEMP if (this.type.equals("ADD")) { builder1.append("insert into ").append(tableName).append("_TEMP ("); } else if (this.type.equals("ALL")) { builder1.append("insert into ").append(tableName).append("("); } String columns = this.columnList.replace(split, "").replace("][", ","); builder1.append(columns.substring(1, columns.length() - 1)); builder1.append(") values("); // 获取参数个数 this.size = this.columnList.split(split).length; for (int i = 0; i < this.size - 1; i++) { builder1.append("?, "); } builder1.append("? )"); this.sql = builder1.toString(); } }自定义BasePreparedStatementSetter.java 实现BatchPreparedStatementSetter 接口
package com.xiaoma.fileimport.service; import org.springframework.jdbc.core.BatchPreparedStatementSetter; import java.sql.PreparedStatement; import java.sql.SQLException; import java.util.List; /** * * @author mawuhui * @since 2020-07-02 21:35 */ public class BasePreparedStatementSetter implements BatchPreparedStatementSetter { // 数据文件所有数据 List<String> lines; // sql语句中参数的个数 Integer size; // 数据文件中的分隔符 String split; /** * @param lines 数据文件 * @param size sql语句中参数的个数 * @param split 数据文件分隔符 */ public BasePreparedStatementSetter(List<String> lines, Integer size, String split) { this.lines = lines; this.size = size; this.split = split; } // 构造器私有化 只能使用带参构造器 private BasePreparedStatementSetter() { } /** * 每个批次中对sql进行设置值 * @param ps * @param i * @throws SQLException */ @Override public void setValues(PreparedStatement ps, int i) throws SQLException { String line = lines.get(i); Object[] values = line.split(this.split); // PreparedStatement 设置参数从1 开始 int i1 = 1; for (Object value : values) { ps.setObject(i1, value); i1++; } // 当sql语句中参数的个数 大于 读取的数据文件中的参数个数时 // 对后面的参数赋值为空字符串(数据库的字段可能有非空标志) if (size > values.length) { for (int i2 = values.length + 1; i2 < size + 1; i1++) ps.setObject(i1, ""); } } /** * 用来获取批次的大小 * @return */ @Override public int getBatchSize() { return this.lines.size(); } } 文件导入数据库类修改为使用 jdbcTemplate.batchUpdate()方法 package com.xiaoma.fileimport.service; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Component; import java.util.List; /** * 文件数据解析导入数据库 * * @author mawuhui * @since 2020-06-30 18:21 */ @Component public class FileToDataBase { @Autowired JdbcTemplate jdbcTemplate; /** * 之前方法名使用的是execute * 防止误导修改为doImport * @param datFile * @param ctlFileParser * @return */ public int doImport(List<String> datFile, CtlFileParser ctlFileParser) { BasePreparedStatementSetter preparedStatement = new BasePreparedStatementSetter(datFile, ctlFileParser.getSize(), ctlFileParser.getSplit()); jdbcTemplate.batchUpdate(ctlFileParser.getSql(), preparedStatement); return 0; } } AbsBatTaskRun.java中修改,信号文件解析放在读取信号文件之后,修改fileToDataBase执行方法; package com.xiaoma.fileimport.common; import com.xiaoma.fileimport.service.CtlFileParser; import com.xiaoma.fileimport.service.FileReader; import com.xiaoma.fileimport.service.FileToDataBase; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.JdbcTemplate; import java.time.LocalDate; import java.time.format.DateTimeFormatter; import java.util.List; /** * @author mawuhui * @since 2020-07-01 13:42 */ public abstract class AbsBatTaskRun implements BatTaskRun { @Autowired public FileReader reader; @Autowired public FileToDataBase fileToDataBase; @Autowired public JdbcTemplate jdbcTemplate; @Override public int execute(String taskNo) { // 未传入时间的时候使用当前时间 如 20200630 DateTimeFormatter df = DateTimeFormatter.ofPattern("yyyyMMdd"); String taskDate = LocalDate.now().format(df); return execute(taskNo, taskDate); } @Override public int execute(String taskNo, String taskDate) { // 文件名获取 String fileName = String.format("%s_%s", taskNo, taskDate); // 1. 信号文件读取 List<String> ctlFile = reader.readTxtFile(String.format("E:\\project\\%s.ctl", fileName)); // 2. 数据文件读取 List<String> datFile = reader.readTxtFile(String.format("E:\\project\\%s.dat", fileName)); // 解析信号文件 , 创建ctl对象 CtlFileParser ctlFileParser = new CtlFileParser(ctlFile); // 3. 校验行数 /** 信号文件中的行数 */ Integer ctllines = ctlFileParser.getRows(); /** 数据文件中的行数 */ Integer datlines = datFile.size(); if (!ctllines.equals(datlines)) { throw new RuntimeException(String.format("文件校验出错,文件实际条数:%s,信号文件条数:%s;", datlines, ctllines)); } // 入库前操作 preProcessor(taskNo); // 4. 执行入库, 之前使用的方法名为execute,修改为doImport fileToDataBase.doImport(datFile, ctlFileParser); // 入库后操作 postProcessor(taskNo); return 0; } /** * 前置处理 * * @param taskNo * @return */ public abstract boolean preProcessor(String taskNo); /** * 后置处理 * * @param taskNo * @return */ public abstract boolean postProcessor(String taskNo); }修改测试文件 T_USER_20200630.ctl 中各行的顺序; 修改数据文件 T_USER_20200630.dat 中的数值; 执行FileImportApplication.main方法进行测试; 测试通过;
写代码很少有一次写完美的, 实际生产上总会遇到新的问题, 遇到问题解决问题就行了; 需求理解的偏差也会导致项目问题,就像我理解的ctl文件格式,每行应该是固定的,但实际上确不是,就会导致错误; 在代码中写死的部分,在以后一旦发生变更,代码还是会出问题; 比如: 文件获取路径写死在E:project,目前是在windows 下进行测试,以后一旦上生产迁移环境到linux中,无法在根目录下提供E:project 目录,就需要修改目录,就需要修改代码了,就会引起新的bug;