跳到主要内容
  1. 所有文章/
  2. 《Mybatis3源码深度解析》笔记/

Mybatis源码-常用工具类

·📄 4095 字·🍵 9 分钟

开始讲Mybatis相关常用工具类的介绍。每个都是在源码中有应用到的。

SQL #

作用:方便动态的生成语句。

public class SQLExample {

    @Test
    public void testSelectSQL() {
        String orgSql = "SELECT P.ID, P.USERNAME, P.PASSWORD, P.FULL_NAME, P.LAST_NAME, P.CREATED_ON, P.UPDATED_ON\n" +
                        "FROM PERSON P, ACCOUNT A\n" +
                        "INNER JOIN DEPARTMENT D on D.ID = P.DEPARTMENT_ID\n" +
                        "INNER JOIN COMPANY C on D.COMPANY_ID = C.ID\n" +
                        "WHERE (P.ID = A.ID AND P.FIRST_NAME like ?) \n" +
                        "OR (P.LAST_NAME like ?)\n" +
                        "GROUP BY P.ID\n" +
                        "HAVING (P.LAST_NAME like ?) \n" +
                        "OR (P.FIRST_NAME like ?)\n" +
                        "ORDER BY P.ID, P.FULL_NAME";

        String newSql =  new SQL() {{
                    SELECT("P.ID, P.USERNAME, P.PASSWORD, P.FULL_NAME");
                    SELECT("P.LAST_NAME, P.CREATED_ON, P.UPDATED_ON");
                    FROM("PERSON P");
                    FROM("ACCOUNT A");
                    INNER_JOIN("DEPARTMENT D on D.ID = P.DEPARTMENT_ID");
                    INNER_JOIN("COMPANY C on D.COMPANY_ID = C.ID");
                    WHERE("P.ID = A.ID");
                    WHERE("P.FIRST_NAME like ?");
                    OR();
                    WHERE("P.LAST_NAME like ?");
                    GROUP_BY("P.ID");
                    HAVING("P.LAST_NAME like ?");
                    OR();
                    HAVING("P.FIRST_NAME like ?");
                    ORDER_BY("P.ID");
                    ORDER_BY("P.FULL_NAME");
                }}.toString();

        assertEquals(orgSql, newSql);
    }

    @Test
    public void testDynamicSQL() {
        selectPerson(null,null,null);
    }

    public String selectPerson(final String id, final String firstName, final String lastName) {
        return new SQL() {{
            SELECT("P.ID, P.USERNAME, P.PASSWORD");
            SELECT("P.FIRST_NAME, P.LAST_NAME");
            FROM("PERSON P");
            if (id != null) {
                WHERE("P.ID = #{id}");
            }
            if (firstName != null) {
                WHERE("P.FIRST_NAME = #{firstName}");
            }
            if (lastName != null) {
                WHERE("P.LAST_NAME = #{lastName}");
            }
            ORDER_BY("P.LAST_NAME");
        }}.toString();
    }

    @Test
    public  void testInsertSql() {
        String insertSql = new SQL().
            INSERT_INTO("PERSON").
            VALUES("ID, FIRST_NAME", "#{id}, #{firstName}").
            VALUES("LAST_NAME", "#{lastName}").toString();
        System.out.println(insertSql);
    }

    @Test
    public void  testDeleteSql() {
        String deleteSql =  new SQL() {{
            DELETE_FROM("PERSON");
            WHERE("ID = #{id}");
        }}.toString();
        System.out.println(deleteSql);
    }

    @Test
    public void testUpdateSql() {
        String updateSql =  new SQL() {{
            UPDATE("PERSON");
            SET("FIRST_NAME = #{firstName}");
            WHERE("ID = #{id}");
        }}.toString();
        System.out.println(updateSql);
    }


}

源码分析 #

SQL类继承了AbstractSQL,主要的实现逻辑在抽象类中,

AbstractSQL类中持有了SQLStatement的实例,看看SQLStatement定义可知,这就是主要的一个SQL拼接实现类。

private static class SQLStatement {
    // SQL语句的类型
    public enum StatementType {
      DELETE, INSERT, SELECT, UPDATE
    }
    // 用于判断SQL的类型。
    StatementType statementType;
    // 用于记录SQL实例SELECT()、UPDATE()等方法调用参数
    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>();
    // 是否包含distinct关键字
    boolean distinct;

    public SQLStatement() {
        // Prevent Synthetic Access
    }
   // .........
}

接下来以SELECT()方法来追踪代码实现:

public T SELECT(String columns) {
    // 设置语句类型为 SELECT
    sql().statementType = SQLStatement.StatementType.SELECT;
    // 然后添加进select列表即可。
    sql().select.add(columns);
    // 返回当前实例
    return getSelf();
}

private SQLStatement sql() {
    return sql;
}

其他应该也是类似的逻辑,再查看toString()方法即可。

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

public String sql(Appendable a) {
    // 就是StringBuilder 的 父接口
    SafeAppendable builder = new SafeAppendable(a);
    if (statementType == null) {
      return null;
    }

    String answer;
    // 根据类型判断调用哪段逻辑
    switch (statementType) {
     // ...
      case SELECT:
        answer = selectSQL(builder);
        break;
      // ...
    }

    return answer;
  }
  
private String selectSQL(SafeAppendable builder) {
  // 是否包含distinct关键字,
  if (distinct) {
    sqlClause(builder, "SELECT DISTINCT", select, "", "", ", ");
  } else {
    sqlClause(builder, "SELECT", select, "", "", ", ");
  }

  sqlClause(builder, "FROM", tables, "", "", ", ");
  // joins 里面也是 批量的sqlClause调用
  joins(builder);
  sqlClause(builder, "WHERE", where, "(", ")", " AND ");
  sqlClause(builder, "GROUP BY", groupBy, "", "", ", ");
  sqlClause(builder, "HAVING", having, "(", ")", " AND ");
  sqlClause(builder, "ORDER BY", orderBy, "", "", ", ");
  return builder.toString();
}

// 核心的字符串拼接方法 sqlClause()
private void sqlClause(SafeAppendable builder, String keyword, List<String> parts, String open, String close,
                           String conjunction) {
  if (!parts.isEmpty()) {
    if (!builder.isEmpty()) {
      builder.append("\n");
    }
    // 拼接SQL关键字
    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);
      // 如果SQL关键字对应的子句内容不为OR或AND,则追加连接关键字
      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);
  }
}
  
 

分析之后可以发现,join生成的语句不能保证和调用的顺序一致。这是这个类的一个缺点。

 @Test
public  void testSelectSql() {
    String insertSql = new SQL() {{
        SELECT("P.ID, P.USERNAME, P.PASSWORD");
        SELECT("P.FIRST_NAME, P.LAST_NAME");
        FROM("PERSON P");
        FROM("USER1 U1");
        FROM("USER2 U2");
        INNER_JOIN("P.id=U1.id");
        JOIN("P.id=U1.id");
        ORDER_BY("P.LAST_NAME");
    }}.toString();
    System.out.println(insertSql);
}
/*
SELECT P.ID, P.USERNAME, P.PASSWORD, P.FIRST_NAME, P.LAST_NAME
FROM PERSON P, USER1 U1, USER2 U2
JOIN P.id=U1.id
INNER JOIN P.id=U1.id
ORDER BY P.LAST_NAME
*/

ScriptRunner #

作用:用于读取脚本文件中的SQL并执行。

 public void testScriptRunner() {
    try {
        Connection connection = DriverManager.getConnection("jdbc:hsqldb:mem:mybatis",
                "sa", "");
        ScriptRunner scriptRunner = new ScriptRunner(connection);
        scriptRunner.runScript(Resources.getResourceAsReader("create-table.sql"));
    } catch (Exception e) {
        e.printStackTrace();
    }
}

源码分析 #

public class ScriptRunner {
  // 语句分隔符
  private static final String DEFAULT_DELIMITER = ";";
  // 持有连接
  private final Connection connection;
  // SQL异常是否中断执行
  private boolean stopOnError;
  // 是否抛出警告
  private boolean throwWarning;
  private boolean autoCommit;
  private boolean sendFullScript;
  // 。。。。
  public ScriptRunner(Connection connection) {
    this.connection = connection;
  }
  
  public void runScript(Reader reader) {
    // 设置事务是否自动提交
    setAutoCommit();
    try {
      // 是否一次性批量执行脚本文件中的所有SQL语句
      if (sendFullScript) {
        // 调用executeFullScript()方法一次性执行脚本文件中的所有SQL语句
        executeFullScript(reader);
      } else {
        // 调用executeLineByLine()方法逐条执行脚本中的SQL语句,默认是走这
        executeLineByLine(reader);
      }
    } finally {
      rollbackConnection();
    }
  }
  
  private void executeLineByLine(Reader reader) {
    StringBuilder command = new StringBuilder();
    try {
      BufferedReader lineReader = new BufferedReader(reader);
      String line;
      while ((line = lineReader.readLine()) != null) {
        // 读一行处理一行
        handleLine(command, line);
      }
      commitConnection();
      checkForMissingLineTerminator(command);
    } catch (Exception e) {
      String message = "Error executing: " + command + ".  Cause: " + e;
      printlnError(message);
      throw new RuntimeSqlException(message, e);
    }
  }
  
  private void handleLine(StringBuilder command, String line) throws SQLException {
    String trimmedLine = line.trim();
    // 判断是否是注释
    if (lineIsComment(trimmedLine)) {
      Matcher matcher = DELIMITER_PATTERN.matcher(trimmedLine);
      if (matcher.find()) {
        delimiter = matcher.group(5);
      }
      println(trimmedLine);
      // 判断本行是否包含分号
    } else if (commandReadyToExecute(trimmedLine)) {
      command.append(line.substring(0, line.lastIndexOf(delimiter)));
      command.append(LINE_SEPARATOR);
      println(command);
      // 调用Statement执行
      executeStatement(command.toString());
      // 执行完清空,下一行准备读取
      command.setLength(0);
    } else if (trimmedLine.length() > 0) {
      command.append(line);
      command.append(LINE_SEPARATOR);
    }
  }
}

SqlRunner #

该类对JDBC做了很好的封装,结合SQL工具类,能够很方便地通过Java代码执行SQL语句并检索SQL执行结果。SqlRunner类提供了几个操作数据库的方法,分别说明如下。

源码分析 #

@Test
public void testSelectOne() throws SQLException {
    SqlRunner sqlRunner = new SqlRunner(connection);
    String qryUserSql = new SQL() {{
        SELECT("*");
        FROM("user");
        WHERE("id = ?");
    }}.toString();
    Map<String, Object> resultMap = sqlRunner.selectOne(qryUserSql, Integer.valueOf(1));
    System.out.println(JSON.toJSONString(resultMap));
}

public Map<String, Object> selectOne(String sql, Object... args) throws SQLException {
  // 实际调用的是 selectAll方法,查询所有再返回第一条,,,,,,
  List<Map<String, Object>> results = selectAll(sql, args);
  if (results.size() != 1) {
    throw new SQLException("Statement returned " + results.size() + " results where exactly one (1) was expected.");
  }
  return results.get(0);
}

public List<Map<String, Object>> selectAll(String sql, Object... args) throws SQLException {
  // 直接就是调用 PreparedStatement 的 setParameters方法罢了。
  PreparedStatement ps = connection.prepareStatement(sql);
  try {
    setParameters(ps, args);
    ResultSet rs = ps.executeQuery();
    return getResults(rs);
  } finally {
    try {
      ps.close();
    } catch (SQLException e) {
      //ignore
    }
  }
}

MetaObject #

MetaObject是MyBatis中的反射工具类,该工具类在MyBatis源码中出现的频率非常高。使用MetaObject工具类,我们可以很优雅地获取和设置对象的属性值。

public class MetaObjectExample {

    @Data
    @AllArgsConstructor
    private static class User {
        List<Order> orders;
        String name;
        Integer age;
    }

    @Data
    @AllArgsConstructor
    private static class Order {
        String orderNo;
        String goodsName;
    }

    @Test
    public void testMetaObject() {
        List<Order> orders = new ArrayList() {
            {
                add(new Order("order20171024010246", "《Mybatis源码深度解析》图书"));
                add(new Order("order20171024010248", "《AngularJS入门与进阶》图书"));
            }
        };
        User user = new User(orders, "江荣波", 3);
        MetaObject metaObject = SystemMetaObject.forObject(user);
        // 获取第一笔订单的商品名称
        System.out.println(metaObject.getValue("orders[0].goodsName"));
        // 获取第二笔订单的商品名称
        System.out.println(metaObject.getValue("orders[1].goodsName"));
        // 为属性设置值
        metaObject.setValue("orders[1].orderNo","order20181113010139");
        // 判断User对象是否有orderNo属性
        System.out.println("是否有orderNo属性且orderNo属性有对应的Getter方法:" + metaObject.hasGetter("orderNo"));
        // 判断User对象是否有name属性
        System.out.println("是否有name属性且orderNo属性有对应的name方法:" + metaObject.hasGetter("name"));

    }
}

/*
《Mybatis源码深度解析》图书
《AngularJS入门与进阶》图书
是否有orderNo属性且orderNo属性有对应的Getter方法:false
是否有name属性且orderNo属性有对应的name方法:true
*/

MetaClass #

MetaClass是MyBatis中的反射工具类,与MetaOjbect不同的是,MetaObject用于获取和设置对象的属性值,而MetaClass则用于获取类相关的信息。例如,我们可以使用MetaClass判断某个类是否有默认构造方法,还可以判断类的属性是否有对应的Getter/Setter方法。接下来我们看一下MetaClass工具类的使用,代码如下:

public class MetaClassExample {

    @Data
    @AllArgsConstructor
    private static class Order {
        String orderNo;
        String goodsName;
    }

    @Test
    public void testMetaClass() {
        MetaClass metaClass = MetaClass.forClass(Order.class, new DefaultReflectorFactory());
        // 获取所有有Getter方法的属性名
        String[] getterNames = metaClass.getGetterNames();
        System.out.println(JSON.toJSONString(getterNames));
        // 是否有默认构造方法
        System.out.println("是否有默认构造方法:" + metaClass.hasDefaultConstructor());
        // 某属性是否有对应的Getter/Setter方法
        System.out.println("orderNo属性是否有对应的Getter方法:" + metaClass.hasGetter("orderNo"));
        System.out.println("orderNo属性是否有对应的Setter方法:" + metaClass.hasSetter("orderNo"));

        System.out.println("orderNo属性类型:" + metaClass.getGetterType("orderNo"));

        // 获取属性Getter方法
        Invoker invoker = metaClass.getGetInvoker("orderNo");
        try {
            // 通过Invoker对象调用Getter方法获取属性值
            Object orderNo = invoker.invoke(new Order("order20171024010248","《Mybatis源码深度解析》图书"), null);
            System.out.println(orderNo);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }

    }
}
/*
["orderNo","goodsName"]
是否有默认构造方法:false
orderNo属性是否有对应的Getter方法:true
orderNo属性是否有对应的Setter方法:true
orderNo属性类型:class java.lang.String
order20171024010248
*/

ObjectFactory #

ObjectFactory是MyBatis中的对象工厂,MyBatis每次创建Mapper映射结果对象的新实例时,都会使用一个对象工厂(ObjectFactory)实例来完成。ObjectFactory接口只有一个默认的实现,即DefaultObjectFactory,默认的对象工厂需要做的仅仅是实例化目标类,要么通过默认构造方法,要么在参数映射存在的时候通过参数构造方法来实例化。

@Test
public void testObjectFactory() {
    ObjectFactory objectFactory = new DefaultObjectFactory();
    List<Integer> list = objectFactory.create(List.class);
    Map<String,String> map = objectFactory.create(Map.class);
    list.addAll(Arrays.asList(1, 2, 3));
    map.put("test", "test");
    System.out.println(list);
    System.out.println(map);
}

MyBatis中使用ObjectFactory实例创建Mapper映射结果对象的目的是什么呢?

这是MyBatis提供的一种扩展机制。有些情况下,在得到映射结果之前我们需要处理一些逻辑,或者在执行该类的有参构造方法时,在传入参数之前,要对参数进行一些处理,这时我们可以通过自定义ObjectFactory来实现。

public class CustomobjectFactory extends DefaultobjectFactory{
  @override
  public Object create(Class type){
    if(type.equals(User.class)){
      //实例化User类
      User user =(User) super.create(type);
      user.setUuid(UUID.randomUUID().toString());
      return user;
    }
    return super.create(type);
  }
}

自定义ObjectFactory完成后,还需要在MyBatis主配置文件中通过<objectFactory>标签配置自定义的ObjectFactory,具体如下:

<objectFactory type="com.blog4java.mybatis.objectfactory.CustomobjectFactory">
  <property name="someProperty" value="10"/>
</objectFactory>

ProxyFactory #

ProxyFactory是MyBatis中的代理工厂,主要用于创建动态代理对象,ProxyFactory接口有两个不同的实现,分别为CglibProxyFactory和JavassistProxyFactory。从实现类的名称可以看出,MyBatis支持两种动态代理策略,分别为Cglib和Javassist动态代理。

ProxyFactory主要用于实现MyBatis 的懒加载功能。当开启懒加载后,MyBatis 创建Mapper 映射结果对象后,会通过ProxyFactory创建映射结果对象的代理对象。当我们调用代理对象的Getter方法获取数据时,会执行CglibProxyFactory或JavassistProxyFactory中定义的拦截逻辑,然后执行一次额外的查询。

public class ProxyFactoryExample {

    @Data
    @AllArgsConstructor
    private static class Order {
        private String orderNo;
        private String goodsName;
    }

    @Test
    public void testProxyFactory() {
        // 创建ProxyFactory对象
        ProxyFactory proxyFactory = new JavassistProxyFactory();
        Order order = new Order("gn20170123","《Mybatis源码深度解析》图书");
        ObjectFactory objectFactory = new DefaultObjectFactory();
        // 调用ProxyFactory对象的createProxy()方法创建代理对象
        Object proxyOrder = proxyFactory.createProxy(order
                ,mock(ResultLoaderMap.class)
                ,mock(Configuration.class)
                ,objectFactory
                ,Arrays.asList(String.class,String.class)
                ,Arrays.asList(order.getOrderNo(),order.getGoodsName())
        );
        System.out.println(proxyOrder.getClass());
        System.out.println(((Order)proxyOrder).getGoodsName());
    }
}

/**
class com.blog4java.mybatis.ProxyFactoryExample$Order_$$_jvstc25_0
《Mybatis源码深度解析》图书
**/