模板设计模式的使用

本文是用一个示例来说明模板设计模式的使用。

功能背景说明:

需要通过一个接口,生成一份完整的sql执行语句。满足单表查询,多表查询。

思路:

接口接收的参数中包含数据库类型,表名称,需要查询的字段名,主表,以及各表的关联类型与关联字段,查询条件。通过数据库类型来确定使用哪种方言拼接。同时,拼接的主体流程与格式都是固定的,只是涉及到不同数据库时,部分表达不一。我们把固定的流程写在一个抽象基类中,需要个性化的放在子类中实现。 这样即使后续需要扩展的时候,直接继承抽象基类,实现下个性的方法即可。

类结构说明:

代码示例:

ISqlParse:暴露给使用方的方法,只需调用这个方法,返回最终的sql。
public interface ISqlParse {

    SqlParseResult parse(GenericSqlModel genericSqlModel);

}
AbstractSqlParse:抽象基类,定义了解析流程与解析过程中产生的各种中间数据,最终sql拼接方法。 (这个类中部分使用常量定义的字段已改成直接定义,方法中定义了解析骨架,abstract 修饰的方法需要在子类中实现)

public abstract class AbstractSqlParse implements ISqlParse {

    private String selectColumn = "";
    private String mainFrom = "";
    private String joinSql = "";
    private String whereSql = "";
    private String groupBySql = "";

    /**
     * 别名
     */
    private List<String> aliasList = null;
    private int aliasIndex = 0;
    private Map<String, String> aliasCache;


    @Override
    public SqlParseResult parse(GenericSqlModel genericSqlModel) {

        init();

        if (genericSqlModel == null) {
            return new SqlParseResult();
        }

        parseMain(genericSqlModel);

        parseSelect(genericSqlModel.getIndexObj());

        parseWhere(genericSqlModel.getSizerObj());

        return buildSql();
    }

    private void init() {
        String str = "a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,b,q";
        aliasList = new ArrayList(Arrays.asList(str.split(",")));
        aliasCache = new HashMap<>();
    }

    /**
     * 获取表对应的别名
     *
     * @param tableName
     * @return
     */
    protected String getAlias(String tableName) {
        String alias = this.aliasCache.get(tableName);
        if (StringUtils.isEmpty(alias)) {
            alias = this.aliasList.remove(0);
            if (StringUtils.isEmpty(alias)) {
                alias = getAlias(tableName);
            }

            this.aliasCache.put(tableName, alias);
        }

        return alias;
    }

    /**
     * 解析主节点
     */
    private void parseMain(GenericSqlModel genericSqlModel)  {

        //主表查询只有一个,所以只要处理第一个节点
        if (genericSqlModel == null) {
            throw new APIException(CodeMsg.DATA_PARSE_ERROR);
        }

        String dbname = genericSqlModel.getDbName();
        List<VclList> vclList = genericSqlModel.getVclList();

        if (vclList == null || vclList.isEmpty()) {
            throw new APIException(CodeMsg.DATA_PARSE_ERROR);
        }

        // 获取跟表
        doParseMain(vclList,dbname);

        List<Children> children = vclList.getChildren();
        if (children != null && children.size() > 0) {
            parseChildren(children, vclList.getTablename(),dbname);
        }
    }

    /**
     * 解析子节点
     */
    private void parseChildren(List<Children> children, String parentTableName,String dbname) {
        if (children == null) {
            return;
        }
        for (Children child : children) {
            doParseChildren(child, parentTableName,dbname);

            //处理子节点
            List<Children> childrenNode = child.getChildren();
            if (childrenNode != null && childrenNode.size() > 0) {
                parseChildren(childrenNode, child.getTablename(),dbname);
            }
        }
    }

    private void parseSelect(IndexObj indexObj) {
        doParseSelect(indexObj);
    }

    private void parseWhere(SizerObj sizerObj) {
        doParseWhere(sizerObj);
    }

    /**
     * 累计拼接列
     * @param column
     */
    protected void appendSelectColumn(String column) {
        if (StringUtils.isEmpty(column)) {
            return;
        }

        this.selectColumn +=column.trim() + ",";
    }

    protected void appendMainTable(String tableName) {
        if (StringUtils.isEmpty(tableName)) {
            return;
        }
        String tablenames = tableName.trim();
        this.mainFrom = tablenames;
    }

    /**
     * 累计拼接 关联 字段
     * @param joinSql
     */
    protected void appendJoinSql(String joinSql) {
        if (StringUtils.isEmpty(joinSql)) {
            return;
        }

        this.joinSql += joinSql.trim()+" ";
    }

    protected void appendWhereSql(String where) {
        if (StringUtils.isEmpty(where)) {
            return;
        }

        where = where.replace("WHERE", "")
                .replace("where", "")
                .trim();

        if (StringUtils.isEmpty(this.whereSql)) {
            where = String.format("where %s", where);
        }

        this.whereSql += where + " ";
    }

    protected void appendGroupSql(String groupBySql) {
        if (StringUtils.isEmpty(groupBySql)) {
            return;
        }
        this.groupBySql += groupBySql.trim() + ",";
    }


    protected SqlParseResult buildSql() {
        SqlParseResult sqlParseResult = new SqlParseResult(); 
        // 最终的sql
        String candidateSql = ""; 

        String column = this.selectColumn; 
        if (StringUtils.isEmpty(column)) {
            column = "*";
        }

        column = column.trim();
        if (column.endsWith(",")) {
            column = column.substring(0, column.length() - 1);
        }
        groupBySql = groupBySql.trim();
        if (groupBySql.endsWith(",")) {
            groupBySql = groupBySql.substring(0, groupBySql.length() - 1);
        }
        if(!StringUtils.isBlank(groupBySql)){
            groupBySql = " group by " + groupBySql;
        }
        if(!StringUtils.isBlank(whereSql)){
            //去除最后一个and 或者 or
            whereSql = whereSql.substring(0,whereSql.length()-4) ;
        }
        
        candidateSql = String.format(" select %s from %s %s %s %s ",
                column,
                this.mainFrom,
                this.joinSql,
                this.whereSql,
                this.groupBySql
        );

        sqlParseResult.setSql(candidateSql);
        return sqlParseResult;
    }

    protected abstract void doParseMain(VclList vclList,String dbname) ;

    protected abstract void doParseChildren(Children child, String parentTableName,String dbname) ;

    protected abstract void doParseSelect(IndexObj indexObj) ;

    protected abstract void doParseWhere(SizerObj sizerObj) ;

    protected abstract void doParseDm(SizerObj sizerObj) ;

    protected IMedianSqlDelegate createMedianSqlDelegate(){
        return new IMedianSqlDelegate(){

            @Override
            public String getSql(String cloumn, String fromTable) {
                return "";
            }
        };
    }
}
MySqlParse:mysql的实现子类,同理,其他数据也同样实现这些方法即可。

public class MySqlParse extends AbstractSqlParse {

    @Override
    protected void doParseMain(VclList vclList, String dbname) {
    }
    
    @Override
    protected void doParseChildren(Children child, String parentTableName, String dbname) {
    }
    
    @Override
    protected void doParseSelect(IndexObj indexObj) {
    }
    
    @Override
    protected void doParseWhere(SizerObj sizerObj) {
    }
    
    @Override
    protected void doParseDm(SizerObj sizerObj) {

    }
}
SqlParseFactory:解析类工厂类,这里用了static块来初始化解析器策略,也可以使用注解的方式,在spring容器启动时动态初始化。

public class SqlParseFactory {

    private static final Map<String,Class> dbClassMap = new HashMap<>();
    static {
        dbClassMap.put(DbConstant.MYSQL,MySqlParse.class);
        dbClassMap.put(DbConstant.Hive,PrestoParse.class);
    }

    public static <T extends ISqlParse> T createParser(Class<T> classT) {
        try {
            return (T) classT.newInstance();
        } catch (InstantiationException e) {
            throw new APIException(CodeMsg.DATA_ERROR);
        } catch (IllegalAccessException e) {
        }
        return null;
    }

    public static <T extends ISqlParse> T createParser(String dbType) {
        Class classZ =dbClassMap.get(dbType);
        Objects.requireNonNull(classZ,CodeMsg.UNKNOW_PARSER);

        return (T) createParser(classZ);
    }
}

使用方法:使用时,根据接口传入的dbType和参数,生成对应的解析器,生产最终sql。

      ISqlParse parser = SqlParseFactory.createParser(dbType);
      SqlParseResult sqlParseResult = parser.parse(genericSqlModel);

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值