MyBatis的SqlBuilder源码详解
mybatis的这个类比较精巧,适合被“拿来用”,还是稍微分析下,也许能有点收获。
mybatis中的sqlbuilder是用来处理java程序动态拼接sql操作的,把我们从以前需要注意空格或者or,and,where等关键字处理中解脱出来,这个类设计的比较精巧,而且不依赖其他的类或者包,很适合移植到自己的项目中去,所以分离出来对其源码进行解读和改造。
首先,它用一个threadlocal对象来存储sql对象(表达sql的实体对象),这个东西我觉得表明:你可以以函数工具的方式操作它,同时,你也可以用你的dao来继承这个类,并不用担心线程安全的问题。
这个类里面定义了一个私有静态类sql,这个类有一个statementtype的枚举对象,如下:
public enum statementtype { delete, insert, select, update}分别表示增删改查操作。
然后有以下类型语句集合,存储不同sql语句段,如下
list<string> sets = new arraylist<string>(); list<string> select = new arraylist<string>(); list<string> tables = new arraylist<string>(); list<string> join = new arraylist<string>(); list<string> innerjoin = new arraylist<string>(); list<string> outerjoin = new arraylist<string>(); list<string> leftouterjoin = new arraylist<string>(); list<string> rightouterjoin = new arraylist<string>(); list<string> where = new arraylist<string>(); list<string> having = new arraylist<string>(); list<string> groupby = new arraylist<string>(); list<string> orderby = new arraylist<string>(); list<string> lastlist = new arraylist<string>(); list<string> columns = new arraylist<string>(); list<string> values = new arraylist<string>(); boolean distinct;包含了几乎所有的sql关键字
你肯定想象的到,会有selectsql,insertsql,deletesql,updatesql这四个方法,分别为你返回对应的sql语句。这几个等会会详细讲解
还有一个sql()方法,通过statementtype枚举来返回你想要的xxxxsql()。
最后还有个比较重要的方法,sqlclause,这个方法通过你传入的语句/类型,为你构造sql,代码如下:
private void sqlclause(stringbuilder builder, string keyword, list<string> parts, string open, string close, string conjunction) { if (!parts.isempty()) { if (builder.length() > 0) builder.append("\n"); builder.append(keyword); builder.append(" "); builder.append(open); string last = "________"; for (int i = 0, n = parts.size(); i < n; i++) { string part = parts.get(i); if (i > 0 && !part.equals(and) && !part.equals(or) && !last.equals(and) && !last.equals(or)) { builder.append(conjunction); } builder.append(part); last = part; } builder.append(close); } }
这个方法参数意思如下:
- builder:当前待拼接的sql语句。
- keyword:关键字,如select,from,join,innerjoin,having,where等等这些。
- parts:就是相对应的上面的那些list对象,比如select,join等等,传递你需要拼接的实际sql内容。
- open,close:就是此语句开始和结尾的闭合字符,比如select,form,join等这个肯定都是“”,而where和having这个肯定就是“(”和“)”
- conjunction:多个关键字语句中间的连接串,比如说select,from,groupby,orderby这些的多个语句块都是“,”连接的,比如selecta.name,a.pid,orderbya.id,b.id等。
- 而join多个表肯定是有多个join,leftoutjoin后面肯定也会是leftoutjoin,where多个肯定是and连接的(这里不考虑or,因为已经有or这个关键方法来表示)。
其实从参数就可以看出此方法的设计意图了。
首先builder拼接关键字,然后拼接闭合(开头)字符,在拼接第二个开始,判断是
否是and和or关键字,这两个关键字在sqlbuilder上面会有定义成静态变量
privatestaticfinalstringand=")\nand(";
privatestaticfinalstringor=")\nor(";
假如是,则直接拼接,不是,则拼接多项分隔符。
最后,拼接闭合(结尾)字符,返回。
下面我们看看那四个方法(selectsql,insertsql,deletesql,updatesql)是怎样调用他们的,以selectsql为例:如下
private string selectsql() { stringbuilder builder = new stringbuilder(); if (distinct) { sqlclause(builder, "select distinct", select, "", "", ", "); } else { sqlclause(builder, "select", select, "", "", ", "); } sqlclause(builder, "from", tables, "", "", ", "); sqlclause(builder, "join", join, "", "", "join"); sqlclause(builder, "inner join", innerjoin, "", "", "\ninner join "); sqlclause(builder, "outer join", outerjoin, "", "", "\nouter join "); sqlclause(builder, "left outer join", leftouterjoin, "", "", "\nleft outer join "); sqlclause(builder, "right outer join", rightouterjoin, "", "", "\nright outer join "); sqlclause(builder, "where", where, "(", ")", " and "); sqlclause(builder, "group by", groupby, "", "", ", "); sqlclause(builder, "having", having, "(", ")", " and "); sqlclause(builder, "order by", orderby, "", "", ", "); return builder.tostring(); }
这个sql静态类最终是为sqlbuilder来提供服务的,而sqlbuilder则暴露出我们需要的接口,提供传值的入口,我们以调用者的角度来看看sqlbuilder是怎样工作的。
一般来说,我们程序里是这样调用的:
public string selectbypro() { begin(); select("p.id,p.username,p.password"); select("p.createdate,p.modifydate"); from("person p"); from("account a"); inner_join("dept d on d.id=p.id"); inner_join("company c on c.id=d.id"); where("p.id=a.id"); where("p.name like '%afei%'"); or(); where("p.sex = '1'"); group_by("p.id"); having("p.age > 20"); order_by("p.id"); order_by("p.name"); return sql();}
begin的时候会new一个sql()对象放入当前线程变量threadlocal,
在select,from,where,orderby这些操作的时候会调用sql对象的相关list来进行add动作,构造list。最后从当前线程变量中取出sql对象,调用sql方法,返回。
假如值需要构造select语句,那么还有个精简版的selectbuilder可以选择。
一点思考,
- 1,你可以把这些方法或属性放入你的基础dao里面,不用担心线程安全的问题,这是最佳实践。
- 2,当作工具类来用,但是这样影响代码的可读性。
- 3,这个类里面的方法都不是同步的,而是操作了一个线程安全的变量,这样可以避免多线程在调用这个类的不同方法时被迫同步的情况。