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

通过读取文件导入数据库功能学习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);
        }
    }
}

修改后项目结构

通过小项目学习23种设计模式(四)_第1张图片

测试

修改T_USER_20200630.dat 文件内容

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

数据库中原有数据
通过小项目学习23种设计模式(四)_第2张图片
执行FileImportApplication中main方法后,查看数据库数据
通过小项目学习23种设计模式(四)_第3张图片

总结

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

预告

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

你可能感兴趣的:(通过小项目学习设计模式)