本文是用一个示例来说明模板设计模式的使用。
功能背景说明:
需要通过一个接口,生成一份完整的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 aliasList = null;
private int aliasIndex = 0;
private Map 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 = genericSqlModel.getVclList();
if (vclList == null || vclList.isEmpty()) {
throw new APIException(CodeMsg.DATA_PARSE_ERROR);
}
// 获取跟表
doParseMain(vclList,dbname);
List children = vclList.getChildren();
if (children != null && children.size() > 0) {
parseChildren(children, vclList.getTablename(),dbname);
}
}
/**
* 解析子节点
*/
private void parseChildren(List children, String parentTableName,String dbname) {
if (children == null) {
return;
}
for (Children child : children) {
doParseChildren(child, parentTableName,dbname);
//处理子节点
List 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 dbClassMap = new HashMap<>();
static {
dbClassMap.put(DbConstant.MYSQL,MySqlParse.class);
dbClassMap.put(DbConstant.Hive,PrestoParse.class);
}
public static T createParser(Class classT) {
try {
return (T) classT.newInstance();
} catch (InstantiationException e) {
throw new APIException(CodeMsg.DATA_ERROR);
} catch (IllegalAccessException e) {
}
return null;
}
public static 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);