通过小项目学习23种设计模式(四)

    技术2022-07-11  89

    通过读取文件导入数据库功能学习23种设计模式

    第一次重构代码

    目前代码写的很随性,导致以后业务增加时拓展起来繁杂,所以我们将已有逻辑进行第一重构:

    抽取公共的行为生成接口

    package com.xiaoma.fileimport.common; /** * 任务主执行类 * 使用工厂模式,首先将任务共同行为抽象出来 * * @author mawuhui * @since 2020-06-30 17:59 */ public interface BatTaskRun { /** * 具体任务执行 * * @param taskNo 任务编号 * @return */ int execute(String taskNo); /** * 具体任务执行 * * @param taskNo 任务编号 * @param taskDate 任务日期 * @return */ int execute(String taskNo, String taskDate); }

    部分已经确定的逻辑进行实现,不确定的部分抽象成方法,编写抽象类

    package com.xiaoma.fileimport.common; import com.xiaoma.fileimport.handler.Processor; 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 Processor processor; @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)); // 3. 校验行数 /** 信号文件中的行数 */ Integer ctllines = Integer.parseInt(ctlFile.get(3).split("=")[1]); /** 数据文件中的行数 */ Integer datlines = datFile.size(); if (!ctllines.equals(datlines)) { throw new RuntimeException(String.format("文件校验出错,文件实际条数:%s,信号文件条数:%s;", datlines, ctllines)); } // 入库前操作 preProcessor(taskNo); // 4. 执行入库 fileToDataBase.execute(ctlFile, datFile); // 入库后操作 postProcessor(taskNo); return 0; } /** * 前置处理 * * @param taskNo * @return */ public abstract boolean preProcessor(String taskNo); /** * 后置处理 * * @param taskNo * @return */ public abstract boolean postProcessor(String taskNo); }

    跟业务进行讨论之后ctl信号文件中添加数据增量或全量标志

    修改T_USER_20200630.ctl文件 添加 数据类型 为增量的标志 type=ADD

    columnList=[user_name]%@#%[sex]%@#%[note]%@#% split=%@#% tableName=T_USER rows=2 type=ADD

    修改myfile_20200630.ctl文件 添加 数据类型 为全量的标志 type=ALL

    columnList=[name]%@#%[age]%@#%[language]%@#%[text]%@#%[email]%@#% split=%@#% tableName=MYFILE rows=3 type=ALL

    文件数据解析导入数据库类修改 FileToDataBase.java

    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.Arrays; import java.util.List; import java.util.stream.Collectors; /** * 文件数据解析导入数据库 * * @author mawuhui * @since 2020-06-30 18:21 */ @Component public class FileToDataBase { @Autowired JdbcTemplate jdbcTemplate; /** * @param ctlFile * @param datFile * @return */ public int execute(List<String> ctlFile, List<String> datFile) { String[] sql = buildSql(ctlFile, datFile); jdbcTemplate.batchUpdate(sql); return 0; } /** * @param ctlFile * @param datFile * @return */ public String[] buildSql(List<String> ctlFile, List<String> datFile) { String[] s = new String[datFile.size()]; String tableName = ctlFile.get(2).split("=")[1]; String split = ctlFile.get(1).split("=")[1]; // 获取数据文件类型: 全量提供ALL, 增量提供ADD; // 全量提供无变化,增量提供,数据实际导入到临时表中,需要修改TABLENAME String type = ctlFile.get(4).split("=")[1]; if (type.equals("ADD")) { tableName = tableName + "_TEMP"; } // 组装insert语句 StringBuilder builder1 = new StringBuilder(); builder1.append("insert into ").append(tableName).append("("); String columns = ctlFile.get(0).split("=")[1].replace("%@#%", "").replace("][", ","); builder1.append(columns.substring(1, columns.length() - 1)); builder1.append(") values("); int i = 0; for (String line : datFile) { List<String> temp = Arrays.asList(line.split(split)); String dat = temp.stream().map(item -> "'" + item + "'").collect(Collectors.joining(",")); s[i] = builder1.toString() + dat + ")"; i++; } return s; } }

    具体功能业务使用子实现类,只用编写前置处理和后置处理

    目前两个实现类

    package com.xiaoma.fileimport.task; import com.xiaoma.fileimport.common.AbsBatTaskRun; import org.springframework.stereotype.Component; /** * 在定义组件的bean name 时 定义为任务编号 taskNo * @author mawuhui * @since 2020-07-01 13:34 */ @Component(value = "myfile") public class MyFile extends AbsBatTaskRun { @Override public boolean preProcessor(String taskNo) { jdbcTemplate.execute("truncate table " + taskNo); return true; } @Override public boolean postProcessor(String taskNo) { return true; } } package com.xiaoma.fileimport.task; import com.xiaoma.fileimport.common.AbsBatTaskRun; import org.springframework.stereotype.Component; /** * 在定义组件的bean name 时 定义为任务编号 taskNo * @author mawuhui * @since 2020-07-01 14:03 */ @Component(value = "T_USER") public class TUser extends AbsBatTaskRun { @Override public boolean preProcessor(String taskNo) { jdbcTemplate.execute(String.format("truncate table %s_TEMP", taskNo)); return true; } @Override public boolean postProcessor(String taskNo) { jdbcTemplate.execute("call updateTUser"); return true; } }

    使用工厂模式获取子实现类,main方法执行处以后不用进行修改了

    工厂模式配合spring容器bean获取具体的子实现类

    package com.xiaoma.fileimport.common; import org.springframework.beans.BeansException; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.stereotype.Component; /** * @author mawuhui * @since 2020-07-01 13:45 */ @Component public class TaskFactory implements ApplicationContextAware { @Autowired private ApplicationContext applicationContext; /** * 整合了spring 所以直接使用了spring ioc容器的中获取 * * @param taskNo * @return */ public BatTaskRun getBatTaskRun(String taskNo) { return (BatTaskRun) applicationContext.getBean(taskNo); } @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { if (this.applicationContext == null) { this.applicationContext = applicationContext; } } }

    最终main方法修改子实现类获取方式

    package com.xiaoma.fileimport; import com.xiaoma.fileimport.common.BatTaskRun; import com.xiaoma.fileimport.common.TaskFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.CommandLineRunner; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class FileImportApplication implements CommandLineRunner { @Autowired TaskFactory taskFactory; public static void main(String[] args) { //参数写一个就是使用当前日期,当提供测试文件日期不为当前日期的时候,传入日期为参数即可 //SpringApplication.run(FileImportApplication.class, new String[]{"myfile"}); SpringApplication.run(FileImportApplication.class, new String[]{"T_USER", "20200630"}); } /** * 文件导入数据库执行入口 * 通过配置根据传入的文件导入编号找到对应的文件名 * * @param args * @throws Exception */ @Override public void run(String... args) throws Exception { String taskNo = args[0]; // 获取方式修改为工厂获取模式 BatTaskRun batTaskRun = taskFactory.getBatTaskRun(taskNo); if (args.length == 2) { String taskDate = args[1]; batTaskRun.execute(taskNo, taskDate); } else { batTaskRun.execute(taskNo); } } }

    修改后项目结构

    测试

    修改T_USER_20200630.dat 文件内容

    mawuhui4%@#%0%@#%js%@#% mawuhui3%@#%0%@#%redis%@#%

    数据库中原有数据 执行FileImportApplication中main方法后,查看数据库数据

    总结

    抽取公共的行为生成接口;行为部分公共逻辑实现生成抽象类;具体子类中实现实际的完整业务功能;调用方不关心具体实现,具体逻辑,只关注结果和返回值,所以使用工厂模式有效屏蔽调用方对后台逻辑功能的感知;目前功能拓展方便,添加新的文件的时候只需要在task目录下新增类继承AbsBatTaskRun 编写前置处理和后置处理即可,不对原有代码进行修改就不会产生新的bug,调用方只用给定需要的任务编号即可,即可正确的解析文件入库;

    预告

    以上重构针对给定格式的文件,需求总是多变的,下一节小马又将面对新的需求数据文件格式为其他格式的;

    Processed: 0.015, SQL: 9