策略模式+工厂模式+枚举类优化代码
背景
业务需要将多数据源数据导入如下图:
解释一下,比如Mysql表中有name
、age
字段,现在业务需要,要将 name
字段所有记录转存到ES ,也会对name
字段进行字段名无意义化(后台可以查到配置对应关系)。
设计
需要考虑可扩展性
应对将来加入不同类型的数据源。
策略模式
要点:策略模式输入同一个参数可以选择不同的算法。
抓住同一参数、不同算法俩个关键字。
多数据源,查询数据的方式不同,可以算不同算法。
同一参数:
查询数据、转存需要的参数:数据源信息,EsIndexName,配置(映射 name-> text1)
数据源信息:
{
"id": 70,
"dbType": "Depot",
"des": "depot数据源123123",
"delFlag": false,
"name": "123depot123",
"schema": "/root/prf",
"host": "1231**2.3123.123",
"port": 3**0,
"username": "",
"password": "",
"jdbcParams": "",
"updater": "",
"loginType": "NONE",
"dbName": ""
}
数据源信息,无论是es,depot,mysql,pg,hive,都是相同的,其实数据源信息不同,你也可以自己进行抽象。通过。
EsIndexName:字符串,通过
映射:相同
总结完成,可以使用策略模式。
实现
以下实现目前未包含ES存储功能
数据库信息:
@Data
public class DataSourceMeta {
String jdbc;
String tableName;
String userName;
String password;
}
public interface DataConvert {
/**
* 根据数据源信息,映射字典,返回查询记录
* @param dataSourceMeta 数据源信息
* @param map 映射字典
* @return 查询结果
*/
List<String> query(DataSourceMeta dataSourceMeta, Map<String,String> map);
}
对关系数据库抽象一层,比如sql转换等,在本项目中是可以复用的。
public abstract class AbstractRelationDataBase implements DataConvert {
public HikariDataSource getHikariDataSource(DataSourceMeta dataSourceMeta) {
HikariConfig hikariConfig = new HikariConfig();
hikariConfig.setJdbcUrl(dataSourceMeta.getJdbc());
hikariConfig.setUsername(dataSourceMeta.getUserName());
hikariConfig.setPassword(dataSourceMeta.getPassword());
hikariConfig.setMaximumPoolSize(3);
hikariConfig.setMinimumIdle(2);
return new HikariDataSource(hikariConfig);
}
public static String getPreQuerySql(String tableName, Map<String, String> queryParam, String extraConditions) {
StringBuilder builder = new StringBuilder();
builder.append("SELECT ");
boolean firstIndex = true;
for (Map.Entry<String, String> entry : queryParam.entrySet()) {
if (!firstIndex) {
builder.append(",");
}
builder.append(entry.getKey()).append(" ").append(entry.getValue()).append(" ");
}
builder.append(" FROM ").append(tableName);
if (extraConditions != null) {
builder.append(" ").append(extraConditions);
}
return builder.toString();
}
}
MySqlDataSource
实现自己的主逻辑
@Component
public class MySqlDataSource extends AbstractRelationDataBase{
@Override
public List<String> query(DataSourceMeta dataSourceMeta, Map<String,String> params) {
HikariDataSource hikariDataSource = getHikariDataSource(dataSourceMeta);
Connection conn = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
List<String> queryResult = new ArrayList<>();
Timestamp start = new Timestamp(System.currentTimeMillis());
try {
conn = hikariDataSource.getConnection();
pstmt = conn.prepareStatement(getPreQuerySql(dataSourceMeta.getTableName(), params, "limit 1000"));
rs = pstmt.executeQuery();
ResultSetMetaData metaData = rs.getMetaData();
StringBuilder sb = new StringBuilder();
while (rs.next()) {
int columnCount = metaData.getColumnCount();
for (int i = 1; i <= columnCount; i++) {
sb.append("\"").append(metaData.getColumnLabel(i)).append("\":\"");
//JDBC中获取到的columnType的值为8,等价于Types.DOUBLE,在getObject的时候按照这种方式去处理,就会发现字符串:“¥123,456.00”无法转换为double因此报错。
sb.append("money".equalsIgnoreCase(metaData.getColumnTypeName(i)) ? rs.getString(i) : rs.getObject(i)).append("\"");
sb.append(i == columnCount ? "}" : ",");
}
sb.insert(0, "{");
queryResult.add(sb.toString());
sb.delete(0, sb.length());
}
} catch (SQLException e) {
throw new RuntimeException("数据库查询出错,出错信息:" + e.getMessage(), e);
} finally {
try {
if (rs != null) {
rs.close();
}
if (pstmt != null) {
pstmt.close();
}
if (conn != null) {
conn.close();
}
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
return queryResult;
}
}
目前代码结构:
假如果你是关系数据库,那么你就可以继承AbstractRelationDataBase,如果你是Es之类非关系数据库,直接实现DataConvert接口。
到现在为止,其实已经可以实现,数据转换的功能(未存入ES)。
每次根据if-else,判断使用哪个策略。那是不是会有很多if-else,策略模式消除了大部分代码,有出现小部分if-else。
我们使用工厂模式+枚举类消除。
@AllArgsConstructor
@Getter
public enum DataBaseEnums {
/**
* 注意service名为小写
*/
MYSQL("001", "mysql", "mySqlDataSource"),
PG("002", "postgresql", "postgreDataSource");
String code;
String type;
String service;
}
public class DataBaseFactory {
// code,服务名
private static final Map<String, String> map = new HashMap<>();
static {
for (DataBaseEnums value : DataBaseEnums.values()) {
map.put(value.getCode(), value.getService());
}
}
public static DataConvert getStrategy(String code) {
String beanName = map.get(code);
if (ObjectUtils.isEmpty(beanName)) {
//log.error("渠道类型:{}错误,请检查!", code);
return null;
}
return (DataConvert) SpringBeanUtils.getBean(beanName);
}
}
实现ApplicationContextAware ,拥有获取spring上下文的能力。
@Component
public class SpringBeanUtils implements ApplicationContextAware {
private static ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
if(SpringBeanUtils.applicationContext == null) {
SpringBeanUtils.applicationContext = applicationContext;
}
}
//获取applicationContext
public static ApplicationContext getApplicationContext() {
return applicationContext;
}
//通过name获取Bean
public static Object getBean(String name){
return getApplicationContext().getBean(name);
}
//通过class获取Bean
public static <T> T getBean(Class<T> clazz){
return getApplicationContext().getBean(clazz);
}
//通过name,以及Clazz返回指定的Bean
public static <T> T getBean(String name,Class<T> clazz){
return getApplicationContext().getBean(name, clazz);
}
}
原理就是:
- 将策略的bean存进枚举类。
MYSQL("001", "mysql", "mySqlDataSource")
- 工厂类使用静态方法(类加载顺序,启动时),将枚举类的bean名存进map
- 将策略算法使用@component注解,意思让spring容器管理Bean。
- 使用时,通过(code/枚举)(看前端怎么传,后端怎么接收)找到服务名。
- 拥有服务名,通过SpringBeanUtils获取Bean,然后进行操作。
@GetMapping("/2")
String test2() {
//----------- 模拟前端参数
DataSourceMeta dataSourceMeta = new DataSourceMeta();
dataSourceMeta.setJdbc("jdbc:mysql://000.000.000.000:3306/test10W");
dataSourceMeta.setUserName("root");
dataSourceMeta.setPassword("⬛⬛⬛⬛"); //请刮开查看密码
dataSourceMeta.setTableName("test10W");
HashMap<String, String> map = new HashMap<>();
map.put("name", "aaaa");
//------------
// DataBaseEnums.MYSQL.getCode() 其实也可以优化掉
DataConvert strategy = DataBaseFactory.getStrategy(DataBaseEnums.MYSQL.getCode());
return strategy.query(dataSourceMeta, map).toString();
}
因个人水平有限,我在这方面有很多不明白的地方,如有错误希望得到大家斧正。
本文来自博客园,作者:帅气的涛啊,转载请注明原文链接:https://www.cnblogs.com/handsometaoa/p/17059041.html
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!