MyBatis3源码深度解析(八)MyBatis常用工具类(一)SQL工具类

第3章 MyBatis常用工具类

3.1 使用SQL类生成语句

在使用JDBC API开发时,当使用Statement对象执行SQL时,SQL语句会嵌入到Java代码中。

如果SQL语句比较复杂,则需要在代码中对SQL语句进行拼接,当条件不固定时,还需要根据条件进行不同的拼接,同时还要注意空格、逗号等的使用。这就使得开发的难度大大增加,且代码的可维护性大大降低。

为了解决这个问题,MyBatis提供了一个SQL工具类。通过使用该工具类,可以方便地在Java代码中动态地构建SQL语句。

3.1.1 SQL工具类示例用法

示例如下:

@Test
public void testSelectSql() {
    String sql = new SQL(){{
        SELECT("id, name, age");
        SELECT("phone, birthday");
        FROM("user");
        WHERE("id = 2");
        OR();
        WHERE("name = 'user1'");
        ORDER_BY("id");
    }}.toString();
    System.out.println(sql);
}

@Test
public void testInsertSql() {
    String sql = new SQL(){{
        INSERT_INTO("user");
        VALUES("name, age, phone, birthday", "'user3', 45, '15789654532', '2015-12-13'");
    }}.toString();
    System.out.println(sql);
}

@Test
public void testUpdateSql() {
    String sql = new SQL(){{
        UPDATE("user");
        SET("name", "玉皇大帝");
        WHERE("id = 1");
    }}.toString();
    System.out.println(sql);
}

@Test
public void testDeleteSql() {
    String sql = new SQL(){{
        DELETE_FROM("user");
        WHERE("id = 1");
    }}.toString();
    System.out.println(sql);
}

控制台输出执行结果:

SELECT id, name, age, phone, birthday
FROM user
WHERE (id = 2) 
OR (name = 'user1')
ORDER BY id

INSERT INTO user
 (name, age, phone, birthday)
VALUES ('user3', 45, '15789654532', '2015-12-13')

UPDATE user
SET name, 玉皇大帝
WHERE (id = 1)

DELETE FROM user
WHERE (id = 1)

如上面的示例所示,SQL工具类可以调用SELECT()FROM()等方法构建SQL语句,这种方式能够有效地避免字符串拼接过程中出现的问题。

使用SQL工具类还可以很方便地在Java代码中根据条件动态地构建SQL语句。示例如下:

public void selectUser(User user) {
    String sql = new SQL(){{
        SELECT("id, name, age");
        SELECT("phone, birthday");
        FROM("user");
        if(user.getId() != null) {
            WHERE("id = #{id}");
        }
        if(user.getName() != null) {
            WHERE("name like #{name}");
        }
        ORDER_BY("id");
    }}.toString();
    System.out.println(sql);
}

@Test
public void testSelectUser() {
    User user = new User();
    user.setId(1);
    user.setName("user");
    selectUser(user);
}

控制台打印执行结果:

SELECT id, name, age, phone, birthday
FROM user
WHERE (id = #{id} AND name like #{name})
ORDER BY id

3.1.2 SQL工具类方法集锦

SQL工具类中提供的主要方法及作用如下:

源码1:org/apache/ibatis/jdbc/AbstractSQL.java

// 开始一个 SELECT 子句或将内容追加到 SELECT 子句中
// 方法可以被多次调用,参数也会添加到 SELECT 子句
// 参数通常是使用逗号分隔的列名或列的别名列表,也可以是数据库驱动程序接收的任意关键字
SELECT(String)
SELECT(String...)

// 开始一个 SELECT 子句或将内容追加到 SELECT 子句中
// 同时可以插入 DISTINCT 关键字到 SELECT 子句中
// 方法可以被多次调用,参数也会添加到 SELECT 子句中
// 参数通常使用逗号分隔的列名或者列的别名列表,也可以是数据库驱动程序接收的任意关键字
SELECT_DISTINCT(String)
SELECT_DISTINCT(String...)

// 开始一个 SELECT 子句或将内容追加到 SELECT 子句中
// 同时可以插入 DISTINCT 关键字到 SELECT 子句中
// 方法可以被多次调用,参数也会添加到 SELECT 子句中
// 参数通常使用逗号分隔的列名或者列的别名列表,也可以是数据库驱动程序接收的任意关键字
FROM(String table)
FROM(String... tables)

// 根据不同的方法添加对应类型的 JOIN 子句
// 例如 INNER_JOIN() 方法添加 INNER JOIN 子句
// LEFT_OUTER_JOIN() 方法添加 LEFT JOIN 子句
// 参数可以包含由列命和 JOIN ON 条件组合成的标准JOIN
JOIN(String join)
JOIN(String... joins)
INNER_JOIN(String join)
INNER_JOIN(String... joins)
LEFT_OUTER_JOIN(String join)
LEFT_OUTER_JOIN(String... joins)
RIGHT_OUTER_JOIN(String join)
RIGHT_OUTER_JOIN(String... joins)

// 插入新的 WHERE 子句条件,并通过AND关键字连接
// 方法可以多次被调用,每次都由 AND 来连接新条件
// 使用 OR() 方法可以追加 OR 关键字
WHERE(String conditions)
WHERE(String... conditions)

// 使用 OR 来分隔当前的 WHERE 子句条件
// 可以被多次调用,但在一行中多次调用可能生成错误的SOL语句
OR()

// 使用 AND 来分隔当前的 WHERE 子句条件
// 可以被多次调用,但在一行中多次调用可能会生成错误的SQL语句
// 这个方法使用较少,因为 WHERE() 和 HAVING() 方法都会自动追加 AND,只有必要时才会额外调用 AND() 方法
AND()

// 插入新的 GROUP BY 子句,通过逗号连接
// 方法可以被多次调用,每次都会使用逗号连接新的条件
GROUP_BY(String columns)
GROUP_BY(String... columns)

// 插入新的 HAVING 子句条件。由AND关键字连接
// 方法可以被多次调用,每次都由 AND 来连接新的条件
HAVING(String conditions)
HAVING(String... conditions)

// 插入新的 ORDER BY子句元素,由逗号连接
// 可以多次被调用,每次都由逗号连接新的条件
ORDER_BY(String columns)
ORDER_BY(String... columns)

// 开始一个 DELETE 语句并指定表名
// 通常它后面都会跟着 WHERE 语句
DELETE_FROM(String table)

// 开始一个 INSERT 语句并指定表名
// 后面都会跟着一个或者多个 VALUES(),或者INTO_COLUMNS()和INTO_VALUES()
INSERT_INTO(String tableName)

// 针对 UPDATE 语句,插入 SET 子句中
SET(String sets)
SET(String... sets)

// 开始一个 UPDATE 语句并指定需要更新的表名
// 后面都会跟着一个或者多个 SET() 方法,通常会有一个或多个 WHERE() 方法
UPDATE(String table)

// 插入 INSERT 语句中,第一个参数是要插入的列名,第二个参数则是该列的值
VALUES(String columns, String values)

// 追加字段到 INSERT 子句中,该方法必须和 INTO_VALUES() 联合使INTO_COLUMNS(String... columns)

// 追加字段值到 INSERT 子句中,该方法必须和 INTO_COLUMNS() 方法联合使用
INTO_VALUES(String... values)

3.1.3 SQL工具类源码解析

源码2:org/apache/ibatis/jdbc/SQL.java

public class SQL extends AbstractSQL<SQL> {

  @Override
  public SQL getSelf() {
    return this;
  }

}

由 源码2 可知,SQL类继承自AbstractSQL类,只重写了父类的getSelf()方法。

源码3:org/apache/ibatis/jdbc/AbstractSQL.java

public abstract class AbstractSQL<T> {
    private final SQLStatement sql = new SQLStatement();
    public abstract T getSelf();

    private static class SQLStatement {
        // ......
    }
    
    // ......
}

由 源码3 可知,AbstractSQL类负责完成SQL语句的构造工作,其内部维护了一个内部类SQLStatement对象的实例,和一系列上面列举的构造SQL语句的方法。

源码4:org/apache/ibatis/jdbc/AbstractSQL.java

public abstract class AbstractSQL<T> {
    private static class SQLStatement {
        public enum StatementType {
            DELETE,
            INSERT,
            SELECT,
            UPDATE
        }
        StatementType statementType;
        List<String> sets = new ArrayList<>();
        List<String> select = new ArrayList<>();
        List<String> tables = new ArrayList<>();
        List<String> join = new ArrayList<>();
        List<String> innerJoin = new ArrayList<>();
        List<String> outerJoin = new ArrayList<>();
        List<String> leftOuterJoin = new ArrayList<>();
        List<String> rightOuterJoin = new ArrayList<>();
        List<String> where = new ArrayList<>();
        List<String> having = new ArrayList<>();
        List<String> groupBy = new ArrayList<>();
        List<String> orderBy = new ArrayList<>();
        List<String> lastList = new ArrayList<>();
        List<String> columns = new ArrayList<>();
        List<List<String>> valuesList = new ArrayList<>();
        boolean distinct;
        // ......
    }
}

由 源码4 可知,内部类SQLStatement用于描述一个SQL语句。该类中通过StatementType枚举类确定SQL语句的类型,内部维护了一些了ArrayList属性,当调用SELECT()UPDATE()等方法时,这些方法的参数会记录在这些ArrayList对象中。

如在示例中,调用了UPDATE("user");方法:

源码5:org/apache/ibatis/jdbc/AbstractSQL.java

public T UPDATE(String table) {
    sql().statementType = SQLStatement.StatementType.UPDATE;
    sql().tables.add(table);
    return getSelf();
}

由 源码5 可知,UPDATE()方法会将SQLStatement对象的statementType属性设置为UPDATE,并将参数设置到tables集合中。

再例如执行SET("name", "玉皇大帝");

源码6:org/apache/ibatis/jdbc/AbstractSQL.java

public T SET(String... sets) {
    sql().sets.addAll(Arrays.asList(sets));
    return getSelf();
}

由 源码6 可知,SET()方法会把参数设置到sets集合中。

在示例中,调用SQL对象的toString()方法即可打印出构造好的SQL语句,这个toString()方法可以作为研究源码的切入点。

源码7:org/apache/ibatis/jdbc/AbstractSQL.java

@Override
public String toString() {
    StringBuilder sb = new StringBuilder();
    // 调用SQLStatement的sql()方法生成SQL语句
    sql().sql(sb);
    return sb.toString();
}

private final SQLStatement sql = new SQLStatement();
private SQLStatement sql() {
    return sql;
}

由 源码7 可知,toString()方法在AbstractSQL类中实现,这里会调用SQLStatement类的sql()方法生成SQL语句。

源码8:org/apache/ibatis/jdbc/AbstractSQL.java

public String sql(Appendable a) {
    // ......
    // 判断SQL语句的类型,根据不同的SQL语句类型执行不同的方法
    String answer;
    switch (statementType) {
        case DELETE:
            answer = deleteSQL(builder);
            break;
        case INSERT:
            answer = insertSQL(builder);
            break;
        case SELECT:
            answer = selectSQL(builder);
            break;
        case UPDATE:
            answer = updateSQL(builder);
            break;
        default:
            answer = null;
    }
    return answer;
}

由 源码8 可知,sql()方法会判断SQL语句的类型,根据不同的SQL语句类型执行不同的方法。例如SQL语句类型为UPDATE时,调用updateSQL()方法构造UPDATE语句。

源码9:org/apache/ibatis/jdbc/AbstractSQL.java

private String updateSQL(SafeAppendable builder) {
    // 处理UPDATE子句
    sqlClause(builder, "UPDATE", tables, "", "", "");
    joins(builder);
    sqlClause(builder, "SET", sets, "", "", ", ");
    sqlClause(builder, "WHERE", where, "(", ")", " AND ");
    limitingRowsStrategy.appendClause(builder, null, limit);
    return builder.toString();
}

/**
 * SQL语句拼接
 * @param builder SQL字符串构建对象
 * @param keyword SQL关键字
 * @param parts SQL关键字子句内容
 * @param open SQL关键字后开始字符
 * @param close SQL关键字后结束字符
 * @param conjunction SQL连接关键字,通常为 AND 或 OR
 */
private void sqlClause(AbstractSQL.SafeAppendable builder, String keyword, List<String> parts, String open, String close,
                       String conjunction) {
    if (!parts.isEmpty()) {
        if (!builder.isEmpty()) {
            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);
    }
}

由 源码9 可知,updateSQL()方法的第一行是调用sqlClause()方法处理 UPDATE 子句,由其传入的参数可知,关键字keyword是"UPDATE",关键字子集parts是名为tables的集合,这个集合保存了表的名称。

借助Debug,执行完第一行的sqlClause()方法得到的结果是:


接着继续执行第二行代码:joins(builder);

源码10:org/apache/ibatis/jdbc/AbstractSQL.java

private void joins(SafeAppendable builder) {
    sqlClause(builder, "JOIN", join, "", "", "\nJOIN ");
    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 ");
}

由 源码10 可知,joins()方法依旧是调用sqlClause()方法分别构建 JOIN、INNER JOIN、OUTER JOIN、LEFT OUTER JOIN、RIGHT OUTER JOIN 子句。

由于示例中没有这些关键字,所以第二行代码执行完后结果仍然不变。


继续执行第三行代码:sqlClause(builder, "SET", sets, "", "", ", ");,构建 SET 子句,构造完成后的结果如图:


继续执行第四行代码:``sqlClause(builder, “WHERE”, where, “(”, “)”, " AND ");```,构建 WHERE 子句,构造完成后的结果如图:


最后直接调用构造器的toString()方法将SQL语句转换为字符串形式。

由此可见,对于不同SQL语句的类型,selectSQL()insertSQL()updateSQL()deleteSQL()这四个方法都已经最好了预设,逐一检索SQL语句可能会包含的关键字,并调用sqlClause()方法进行构造。

本节完,更多内容请查阅分类专栏:MyBatis3源码深度解析

  • 40
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

灰色孤星A

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值