通过读取文件导入数据库功能学习23种设计模式
第一次重构代码
目前代码写的很随性,导致以后业务增加时拓展起来繁杂,所以我们将已有逻辑进行第一重构:
抽取公共的行为生成接口
package com
.xiaoma
.fileimport
.common
;
public interface BatTaskRun {
int execute(String taskNo
);
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
;
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
) {
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
);
List
<String> ctlFile
= reader
.readTxtFile(String
.format("E:\\project\\%s.ctl", fileName
));
List
<String> datFile
= reader
.readTxtFile(String
.format("E:\\project\\%s.dat", fileName
));
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
);
fileToDataBase
.execute(ctlFile
, datFile
);
postProcessor(taskNo
);
return 0;
}
public abstract boolean preProcessor(String taskNo
);
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
;
@Component
public class FileToDataBase {
@Autowired
JdbcTemplate jdbcTemplate
;
public int execute(List
<String> ctlFile
, List
<String> datFile
) {
String
[] sql
= buildSql(ctlFile
, datFile
);
jdbcTemplate
.batchUpdate(sql
);
return 0;
}
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];
String type
= ctlFile
.get(4).split("=")[1];
if (type
.equals("ADD")) {
tableName
= tableName
+ "_TEMP";
}
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
;
@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
;
@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
;
@Component
public class TaskFactory implements ApplicationContextAware {
@Autowired
private ApplicationContext applicationContext
;
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[]{"T_USER", "20200630"});
}
@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,调用方只用给定需要的任务编号即可,即可正确的解析文件入库;
预告
以上重构针对给定格式的文件,需求总是多变的,下一节小马又将面对新的需求数据文件格式为其他格式的;