写点什么

用建造者模式实现一个防 SQL 注入的 ORM 框架

用户头像
Tom弹架构
关注
发布于: 刚刚

本文节选自《设计模式就该这样学》

1 建造者模式的链式写法

以构建一门课程为例,一个完整的课程由 PPT 课件、回放视频、课堂笔记、课后作业组成,但是这些内容的设置顺序可以随意调整,我们用建造者模式来代入理解一下。首先创建一个产品类 Course。



@Datapublic class Course {
private String name; private String ppt; private String video; private String note;
private String homework;
@Override public String toString() { return "CourseBuilder{" + "name='" + name + '\'' + ", ppt='" + ppt + '\'' + ", video='" + video + '\'' + ", note='" + note + '\'' + ", homework='" + homework + '\'' + '}'; }}
复制代码


然后创建建造者类 CourseBuilder,将复杂的创建过程封装起来,创建步骤由用户决定。



public class CourseBuilder {
private Course course = new Course();
public CourseBuilder addName(String name){ course.setName(name); return this; }
public CourseBuilder addPpt(String ppt){ course.setPpt(ppt); return this; }
public CourseBuilder addVideo(String video){ course.setVideo(video); return this; }
public CourseBuilder addNote(String note){ course.setNote(note); return this; }
public CourseBuilder addHomework(String homework){ course.setHomework(homework); return this; }
public Course builder(){ return course; }
}
复制代码


最后编写客户端测试代码。




public static void main(String[] args) { CourseBuilder builder = new CourseBuilder() .addName("设计模式") .addPPT("【PPT课件】") .addVideo("【回放视频】") .addNote("【课堂笔记】") .addHomework("【课后作业】");
System.out.println(builder.build());}
复制代码


这样的写法是不是很眼熟?后面分析建造者模式在框架源码中的应用时大家就会明白。再来看一下类图的变化,如下图所示。


2 使用静态内部类实现建造者模式

事实上,在平常的编码中,我们通常都会忽略对象的复杂性,优先考虑使用工厂模式创建对象,而不是建造者模式。因为工厂模式和建造者模式的作用都是创建一个产品对象,而工厂模式的结构更加简洁直接(没有 Builder 和 Director),因此更常使用。一般情况下,我们更习惯使用静态内部类的方式实现建造者模式,即一个产品类内部自动带有一个具体建造者,由它负责该产品的组装创建,不再需要 Builder 和 Director,这样,产品表示与创建之间的联系更加紧密,结构更加紧凑,同时使得建造者模式的形式更加简洁。如果采用静态内部类形式实现建造者模式,则前面的案例可以改写如下。




@Datapublic class Course { private String name; private String ppt; private String video; private String note;
private String homework;
@Override public String toString() { return "Course{" + "name='" + name + '\'' + ", ppt='" + ppt + '\'' + ", video='" + video + '\'' + ", note='" + note + '\'' + ", homework='" + homework + '\'' + '}'; }
public static class Builder {
private Course course = new Course();
public Builder addName(String name){ course.setName(name); return this; }
public Builder addPpt(String ppt){ course.setPpt(ppt); return this; }
public Builder addVideo(String video){ course.setVideo(video); return this; }
public Builder addNote(String note){ course.setNote(note); return this; }
public Builder addHomework(String homework){ course.setHomework(homework); return this; }
public Course builder(){ return course; }
}}
复制代码


客户端测试代码如下。




public static void main(String[] args) { Course course = new Course.Builder() .addName("设计模式") .addPpt("【PPT课件】") .addVideo("【录播视频】") .builder();
System.out.println(course); }
复制代码


这样,代码也会看上去更加简洁,不会让人感觉到多了一个类。

3 使用建造者模式动态构建 SQL 语句

下面来看一个实战案例,这个案例参考了开源框架 JPA 的 SQL 构造模式。我们在构造 SQL 查询条件的时候,需要根据不同的条件来拼接 SQL 字符串。如果查询条件复杂,则 SQL 拼接的过程也会变得非常复杂,从而给代码维护带来非常大的困难。因此,我们用建造者类 QueryRuleSqlBuilder 将复杂的 SQL 构造过程进行封装,用 QueryRule 对象专门保存 SQL 查询时的条件,最后根据查询条件,自动生成 SQL 语句。首先创建 QueryRule 类,代码如下。



import java.io.Serializable;import java.util.ArrayList;import java.util.List;
/** * QueryRule,主要功能用于构造查询条件 * * @author Tom */public final class QueryRule implements Serializable{ private static final long serialVersionUID = 1L; public static final int ASC_ORDER = 101; public static final int DESC_ORDER = 102; public static final int LIKE = 1; public static final int IN = 2; public static final int NOTIN = 3; public static final int BETWEEN = 4; public static final int EQ = 5; public static final int NOTEQ = 6; public static final int GT = 7; public static final int GE = 8; public static final int LT = 9; public static final int LE = 10; public static final int ISNULL = 11; public static final int ISNOTNULL = 12; public static final int ISEMPTY = 13; public static final int ISNOTEMPTY = 14; public static final int AND = 201; public static final int OR = 202; private List<Rule> ruleList = new ArrayList<Rule>(); private List<QueryRule> queryRuleList = new ArrayList<QueryRule>(); private String propertyName;
private QueryRule() {}
private QueryRule(String propertyName) { this.propertyName = propertyName; }
public static QueryRule getInstance() { return new QueryRule(); } /** * 添加升序规则 * @param propertyName * @return */ public QueryRule addAscOrder(String propertyName) { this.ruleList.add(new Rule(ASC_ORDER, propertyName)); return this; }
/** * 添加降序规则 * @param propertyName * @return */ public QueryRule addDescOrder(String propertyName) { this.ruleList.add(new Rule(DESC_ORDER, propertyName)); return this; }
public QueryRule andIsNull(String propertyName) { this.ruleList.add(new Rule(ISNULL, propertyName).setAndOr(AND)); return this; }
public QueryRule andIsNotNull(String propertyName) { this.ruleList.add(new Rule(ISNOTNULL, propertyName).setAndOr(AND)); return this; }
public QueryRule andIsEmpty(String propertyName) { this.ruleList.add(new Rule(ISEMPTY, propertyName).setAndOr(AND)); return this; }
public QueryRule andIsNotEmpty(String propertyName) { this.ruleList.add(new Rule(ISNOTEMPTY, propertyName).setAndOr(AND)); return this; }
public QueryRule andLike(String propertyName, Object value) { this.ruleList.add(new Rule(LIKE, propertyName, new Object[] { value }).setAndOr(AND)); return this; }
public QueryRule andEqual(String propertyName, Object value) { this.ruleList.add(new Rule(EQ, propertyName, new Object[] { value }).setAndOr(AND)); return this; }
public QueryRule andBetween(String propertyName, Object... values) { this.ruleList.add(new Rule(BETWEEN, propertyName, values).setAndOr(AND)); return this; }
public QueryRule andIn(String propertyName, List<Object> values) { this.ruleList.add(new Rule(IN, propertyName, new Object[] { values }).setAndOr(AND)); return this; }
public QueryRule andIn(String propertyName, Object... values) { this.ruleList.add(new Rule(IN, propertyName, values).setAndOr(AND)); return this; } public QueryRule andNotIn(String propertyName, List<Object> values) { this.ruleList.add(new Rule(NOTIN, propertyName, new Object[] { values }).setAndOr(AND)); return this; }
//此处省略部分代码
public List<Rule> getRuleList() { return this.ruleList; }
public List<QueryRule> getQueryRuleList() { return this.queryRuleList; }
public String getPropertyName() { return this.propertyName; }
protected class Rule implements Serializable { private static final long serialVersionUID = 1L; private int type; //规则的类型 private String property_name; private Object[] values; private int andOr = AND;
public Rule(int paramInt, String paramString) { this.property_name = paramString; this.type = paramInt; }
public Rule(int paramInt, String paramString, Object[] paramArrayOfObject) { this.property_name = paramString; this.values = paramArrayOfObject; this.type = paramInt; } public Rule setAndOr(int andOr){ this.andOr = andOr; return this; } public int getAndOr(){ return this.andOr; }
public Object[] getValues() { return this.values; }
public int getType() { return this.type; }
public String getPropertyName() { return this.property_name; } }}
复制代码


然后创建 QueryRuleSqlBuilder 类。



package com.tom.vip.pattern.builder.sql;

/** * 根据QueryRule自动构建SQL语句 * @author Tom * */public class QueryRuleSqlBuilder { private int CURR_INDEX = 0; //记录参数所在的位置 private List<String> properties; //保存列名列表 private List<Object> values; //保存参数值列表 private List<Order> orders; //保存排序规则列表 private String whereSql = ""; private String orderSql = ""; private Object [] valueArr = new Object[]{}; private Map<Object,Object> valueMap = new HashMap<Object,Object>(); /** * 获得查询条件 * @return */ private String getWhereSql(){ return this.whereSql; } /** * 获得排序条件 * @return */ private String getOrderSql(){ return this.orderSql; } /** * 获得参数值列表 * @return */ public Object [] getValues(){ return this.valueArr; } /** * 获得参数列表 * @return */ private Map<Object,Object> getValueMap(){ return this.valueMap; } /** * 创建SQL构造器 * @param queryRule */ public QueryRuleSqlBuilder(QueryRule queryRule) { CURR_INDEX = 0; properties = new ArrayList<String>(); values = new ArrayList<Object>(); orders = new ArrayList<Order>(); for (QueryRule.Rule rule : queryRule.getRuleList()) { switch (rule.getType()) { case QueryRule.BETWEEN: processBetween(rule); break; case QueryRule.EQ: processEqual(rule); break; case QueryRule.LIKE: processLike(rule); break; case QueryRule.NOTEQ: processNotEqual(rule); break; case QueryRule.GT: processGreaterThen(rule); break; case QueryRule.GE: processGreaterEqual(rule); break; case QueryRule.LT: processLessThen(rule); break; case QueryRule.LE: processLessEqual(rule); break; case QueryRule.IN: processIN(rule); break; case QueryRule.NOTIN: processNotIN(rule); break; case QueryRule.ISNULL: processIsNull(rule); break; case QueryRule.ISNOTNULL: processIsNotNull(rule); break; case QueryRule.ISEMPTY: processIsEmpty(rule); break; case QueryRule.ISNOTEMPTY: processIsNotEmpty(rule); break; case QueryRule.ASC_ORDER: processOrder(rule); break; case QueryRule.DESC_ORDER: processOrder(rule); break; default: throw new IllegalArgumentException("type"+rule.getType()+"not supported."); } } //拼装where语句 appendWhereSql(); //拼装排序语句 appendOrderSql(); //拼装参数值 appendValues(); } /** * 去掉order * * @param sql * @return */ private String removeOrders(String sql) { Pattern p = Pattern.compile("order\\s*by[\\w|\\W|\\s|\\S]*", Pattern.CASE_INSENSITIVE); Matcher m = p.matcher(sql); StringBuffer sb = new StringBuffer(); while (m.find()) { m.appendReplacement(sb, ""); } m.appendTail(sb); return sb.toString(); } /** * 去掉select * * @param sql * @return */ private String removeSelect(String sql) { if(sql.toLowerCase().matches("from\\s+")){ int beginPos = sql.toLowerCase().indexOf("from"); return sql.substring(beginPos); }else{ return sql; } } /** * 处理like * @param rule */ private void processLike(QueryRule.Rule rule) { if (ArrayUtils.isEmpty(rule.getValues())) { return; } Object obj = rule.getValues()[0];
if (obj != null) { String value = obj.toString(); if (!StringUtils.isEmpty(value)) { value = value.replace('*', '%'); obj = value; } } add(rule.getAndOr(),rule.getPropertyName(),"like","%"+rule.getValues()[0]+"%"); }
/** * 处理between * @param rule */ private void processBetween(QueryRule.Rule rule) { if ((ArrayUtils.isEmpty(rule.getValues())) || (rule.getValues().length < 2)) { return; } add(rule.getAndOr(),rule.getPropertyName(),"","between",rule.getValues()[0],"and"); add(0,"","","",rule.getValues()[1],""); }
//此处省略部分代码 /** * 加入SQL查询规则队列 * @param andOr and或者or * @param key 列名 * @param split 列名与值之间的间隔 * @param value 值 */ private void add(int andOr,String key,String split ,Object value){ add(andOr,key,split,"",value,""); } /** * 加入SQL查询规则队列 * @param andOr and或者or * @param key 列名 * @param split 列名与值之间的间隔 * @param prefix 值前缀 * @param value 值 * @param suffix 值后缀 */ private void add(int andOr,String key,String split ,String prefix,Object value,String suffix){ String andOrStr = (0 == andOr ? "" :(QueryRule.AND == andOr ? " and " : " or ")); properties.add(CURR_INDEX, andOrStr + key + " " + split + prefix + (null != value ? " ? " : " ") + suffix); if(null != value){ values.add(CURR_INDEX,value); CURR_INDEX ++; } } /** * 拼装where语句 */ private void appendWhereSql(){ StringBuffer whereSql = new StringBuffer(); for (String p : properties) { whereSql.append(p); } this.whereSql = removeSelect(removeOrders(whereSql.toString())); } /** * 拼装排序语句 */ private void appendOrderSql(){ StringBuffer orderSql = new StringBuffer(); for (int i = 0 ; i < orders.size(); i ++) { if(i > 0 && i < orders.size()){ orderSql.append(","); } orderSql.append(orders.get(i).toString()); } this.orderSql = removeSelect(removeOrders(orderSql.toString())); } /** * 拼装参数值 */ private void appendValues(){ Object [] val = new Object[values.size()]; for (int i = 0; i < values.size(); i ++) { val[i] = values.get(i); valueMap.put(i, values.get(i)); } this.valueArr = val; }
public String builder(String tableName){ String ws = removeFirstAnd(this.getWhereSql()); String whereSql = ("".equals(ws) ? ws : (" where " + ws)); String sql = "select * from " + tableName + whereSql; Object [] values = this.getValues(); String orderSql = this.getOrderSql(); orderSql = (StringUtils.isEmpty(orderSql) ? " " : (" order by " + orderSql)); sql += orderSql; return sql; }

private String removeFirstAnd(String sql){ if(StringUtils.isEmpty(sql)){return sql;} return sql.trim().toLowerCase().replaceAll("^\\s*and", "") + " "; }
}
复制代码


接着创建 Order 类。



/** * SQL排序组件 * @author Tom */public class Order { private boolean ascending; //升序还是降序 private String propertyName; //哪个字段升序,哪个字段降序 public String toString() { return propertyName + ' ' + (ascending ? "asc" : "desc"); }
/** * Constructor for Order. */ protected Order(String propertyName, boolean ascending) { this.propertyName = propertyName; this.ascending = ascending; }
/** * Ascending order * * @param propertyName * @return Order */ public static Order asc(String propertyName) { return new Order(propertyName, true); }
/** * Descending order * * @param propertyName * @return Order */ public static Order desc(String propertyName) { return new Order(propertyName, false); }
}
复制代码


最后编写客户端测试代码。



public static void main(String[] args) { QueryRule queryRule = QueryRule.getInstance(); queryRule.addAscOrder("age"); queryRule.andEqual("addr","Changsha"); queryRule.andLike("name","Tom"); QueryRuleSqlBuilder builder = new QueryRuleSqlBuilder(queryRule);
System.out.println(builder.builder("t_member"));
System.out.println("Params: " + Arrays.toString(builder.getValues()));

}
复制代码


这样一来,客户端代码就非常清楚,运行结果如下图所示。



本文为“Tom 弹架构”原创,转载请注明出处。技术在于分享,我分享我快乐!

如果本文对您有帮助,欢迎关注和点赞;如果您有任何建议也可留言评论或私信,您的支持是我坚持创作的动力。关注微信公众号『 Tom 弹架构 』可获取更多技术干货!

发布于: 刚刚阅读数: 2
用户头像

Tom弹架构

关注

不只做一个技术者,更要做一个思考者 2021.10.22 加入

畅销书作者,代表作品: 《Spring 5核心原理与30个类手写实战》 《Netty 4核心原理与手写RPC框架实战》 《设计模式就该这样学》

评论

发布
暂无评论
用建造者模式实现一个防SQL注入的ORM框架