策略模式+工厂模式+枚举类优化代码

背景

业务需要将多数据源数据导入如下图:

解释一下,比如Mysql表中有nameage字段,现在业务需要,要将 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);
    }
}

原理就是:

  1. 将策略的bean存进枚举类。
    MYSQL("001", "mysql", "mySqlDataSource")
  2. 工厂类使用静态方法(类加载顺序,启动时),将枚举类的bean名存进map
  3. 将策略算法使用@component注解,意思让spring容器管理Bean。
  4. 使用时,通过(code/枚举)(看前端怎么传,后端怎么接收)找到服务名。
  5. 拥有服务名,通过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();
    }

因个人水平有限,我在这方面有很多不明白的地方,如有错误希望得到大家斧正。

posted @   帅气的涛啊  阅读(132)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
点击右上角即可分享
微信分享提示

目录