MyBatis之SqlBuilder类源码分析

Mybatis中的SqlBuilder类是用来处理java程序动态拼接SQL操作用的,把我们从以前需要注意空格或者or,and,where等SQL关键字处理中解脱出来,这个类设计的比较精巧,而且不依赖其他的类或者包,很适合移植到自己的项目中去,所以分离出来对其源码进行解读和改造。

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,inner join,having,where等等这些。

  • Parts:就是相对应的上面的那些List对象,比如select,join等等,传递你需要拼接的实际sql内容。

  • Open,close:就是此语句开始和结尾的闭合字符,比如select,form,join等这个肯定都是“”,而where和having这个肯定就是“(”和“)”

  • Conjunction:多个关键字语句中间的连接串,比如说select,from,group  by,order by这些的多个语句块都是“, ”连接的,比如select   a.name,a.pid ,order by a.id,b.id等。

而join多个表肯定是有多个join,left out join 后面肯定也会是left out join,where 多个肯定是 and 连接的(这里不考虑or,因为已经有Or这个关键方法来表示)。

其实从参数就可以看出此方法的设计意图了。首先builder拼接关键字,然后拼接闭合(开头)字符,在拼接第二个开始,判断是否是AND 和OR关键字,这两个关键字在SqlBuilder上面会有定义成静态变量

private static final String AND = ") \nAND ("; 
private static final String OR = ") \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、这个类里面的方法都不是同步的,而是操作了一个线程安全的变量,这样可以避免多线程在调用这个类的不同方法时被迫同步的情况。

点击学习 MyBatis 教程,了解更多的 MyBatis 知识!

与肝胆人共事,无字句处读书。——周恩来
0 不喜欢
说说我的看法 -
全部评论(
没有评论
关于
本网站专注于 Java、数据库(MySQL、Oracle)、Linux、软件架构及大数据等多领域技术知识分享。涵盖丰富的原创与精选技术文章,助力技术传播与交流。无论是技术新手渴望入门,还是资深开发者寻求进阶,这里都能为您提供深度见解与实用经验,让复杂编码变得轻松易懂,携手共赴技术提升新高度。如有侵权,请来信告知:hxstrive@outlook.com
公众号