Mybatis常用工具类-SQL类

文章目录

  • 前言
  • 一、JDBC
    • 1.Connection
    • 2.Statement类
    • 3.ResultSet类
    • 4.DatabaseMetaData类
    • 5.JDBC事务
  • 二、使用SQL类生成语句
    • 1.无工具类写SQL语句
    • 2.使用工具类
    • 3.SQL工具类的方法及作用
    • 4.源码分析


前言

从本章开始将会从Mybatis源码分析,一步一步分析Mybatis,坐到深入浅出。
注:本系列文章主要参考书籍《Mybatis3源码深度解析》
源码地址:https://github.com/jiangrongbo/mybatis-book


一、JDBC

MyBatis框架对JDBC做了轻量级的封装,作为Java开发人员,我们对JDBC肯定不会陌生,但是要看懂MyBatis的源码,还需要熟练掌握JDBC API的使用。

使用JDBC操作数据源大致需要以下几个步骤:
(1)与数据源建立连接。
(2)执行SQL语句。
(3)检索SQL执行结果。
(4)关闭连接。

1.Connection

一个Connection对象表示通过JDBC驱动与数据源建立的连接,这里的数据源可以是关系型数据库管理系统(DBMS)、文件系统或者其他通过JDBC驱动访问的数据。使用JDBC API的应用程序可能需要维护多个Connection对象,一个Connection对象可能访问多个数据源,也可能访问单个数据源。

接下来看看两个例子:
例子一是通过DriverManager对象获取连接

public class Example01 {
    @Test
    public void testJdbc() {
        // 初始化数据
        DbUtils.initData();
        try {
            // 加载驱动
            Class.forName("org.hsqldb.jdbcDriver");
            // 获取Connection对象
            Connection connection = DriverManager.getConnection("jdbc:hsqldb:mem:mybatis",
                    "sa", "");
            Statement statement = connection.createStatement();
            ResultSet resultSet = statement.executeQuery("select * from user");
            // 遍历ResultSet
            ResultSetMetaData metaData = resultSet.getMetaData();
            int columCount = metaData.getColumnCount();
            while (resultSet.next()) {
                for (int i = 1; i <= columCount; i++) {
                    String columName = metaData.getColumnName(i);
                    String columVal = resultSet.getString(columName);
                    System.out.println(columName + ":" + columVal);
                }
                System.out.println("--------------------------------------");
            }
            // 关闭连接
            IOUtils.closeQuietly(statement);
            IOUtils.closeQuietly(connection);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

例子二通过DataSource对象获取连接

public class Example03 {
    @Test
    public void testJdbc() {
        // 初始化数据
        DbUtils.initData();
        try {
            // 创建DataSource实例
            DataSourceFactory dsf = new UnpooledDataSourceFactory();
            Properties properties = new Properties();
            InputStream configStream = Thread.currentThread().getContextClassLoader().getResourceAsStream("database.properties");
            properties.load(configStream);
            dsf.setProperties(properties);
            DataSource dataSource = dsf.getDataSource();
            // 获取Connection对象
            Connection connection = dataSource.getConnection();
            Statement statement = connection.createStatement();
            ResultSet resultSet = statement.executeQuery("select * from user");
            // 遍历ResultSet
            ResultSetMetaData metaData = resultSet.getMetaData();
            int columCount = metaData.getColumnCount();
            while (resultSet.next()) {
                for (int i = 1; i <= columCount; i++) {
                    String columName = metaData.getColumnName(i);
                    String columVal = resultSet.getString(columName);
                    System.out.println(columName + ":" + columVal);
                }
                System.out.println("----------------------------------------");
            }
            // 关闭连接
            IOUtils.closeQuietly(statement);
            IOUtils.closeQuietly(connection);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

区别就是:DataSource对象是从配置文件中读取数据库信息,这样若是数据库的信息发生变化,就无需改动代码。

2.Statement类

Statement接口中定义了执行SQL语句的方法,这些方法不支持参数输入,PreparedStatement接口中增加了设置SQL参数的方法,CallableStatement接口继承自PreparedStatement,在此基础上增加了调用存储过程以及检索存储过程调用结果的方法。

Statement的主要作用是与数据库进行交互,该接口中定义了一些数据库操作以及检索SQL执行结果相关的方法,具体如下:

Mybatis常用工具类-SQL类_第1张图片
例子:

public class Example06 {
    @Before
    public void initData() throws  Exception {
        // 初始化数据
        Class.forName("org.hsqldb.jdbcDriver");
        // 获取Connection对象
        Connection conn = DriverManager.getConnection("jdbc:hsqldb:mem:mybatis",
                "sa", "");
        // 使用Mybatis的ScriptRunner工具类执行数据库脚本
        ScriptRunner scriptRunner = new ScriptRunner(conn);
        // 不输出sql日志
        scriptRunner.setLogWriter(null);
        scriptRunner.runScript(Resources.getResourceAsReader("create-table.sql"));
        System.out.println("----------------------");
    }

    @Test
    public void testJdbc() {
        try {
            // 创建DataSource实例
            DataSourceFactory dsf = new UnpooledDataSourceFactory();
            Properties properties = new Properties();
            InputStream configStream = Thread.currentThread().getContextClassLoader().getResourceAsStream("database.properties");
            properties.load(configStream);
            dsf.setProperties(properties);
            DataSource dataSource = dsf.getDataSource();
            // 获取Connection对象
            Connection connection = dataSource.getConnection();
            PreparedStatement stmt = connection.prepareStatement("insert into  " +
                    "user(create_time, name, password, phone, nick_name) " +
                    "values(?,?,?,?,?);");
            stmt.setString(1,"2010-10-24 10:20:30");
            stmt.setString(2,"User1");
            stmt.setString(3,"test");
            stmt.setString(4,"18700001111");
            stmt.setString(5,"User1");
            ParameterMetaData pmd = stmt.getParameterMetaData();
            for(int i = 1; i <= pmd.getParameterCount(); i++) {
                String typeName = pmd.getParameterTypeName(i);
                String className = pmd.getParameterClassName(i);
                System.out.println("第" + i + "个参数," + "typeName:" + typeName + ", className:" + className);
            }
            stmt.execute();
            // 关闭连接
            IOUtils.closeQuietly(stmt);
            IOUtils.closeQuietly(connection);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

3.ResultSet类

ResultSet接口是JDBC API中另一个比较重要的组件,提供了检索和操作SQL执行结果相关的方法。

ResultSet对象的类型主要体现在两个方面:
(1)游标可操作的方式。
(2)ResultSet对象的修改对数据库的影响。

ResultSet有3种不同的类型,分别说明如下:

  • TYPE_FORWARD_ONLY:这种类型的ResultSet不可滚动,游标只能向前移动,从第一行到最后一行,不允许向后移动,即只能使用ResultSet接口的next()方法,而不能使用previous()方法,否则会产生错误。
  • TYPE_SCROLL_INSENSITIVE:这种类型的ResultSet是可滚动的,它的游标可以相对于当前位置向前或向后移动,也可以移动到绝对位置。当ResultSet没有关闭时,ResultSet的修改对数据库不敏感,也就是说对ResultSet对象的修改不会影响对应的数据库中的记录。
  • TYPE_SCROLL_SENSITIVE:这种类型的ResultSet是可滚动的,它的游标可以相对于当前位置向前或向后移动,也可以移动到绝对位置。当ResultSet没有关闭时,对ResultSet对象的修改会直接影响数据库中的记录。

4.DatabaseMetaData类

DatabaseMetaData接口是由JDBC驱动程序实现的,用于提供底层数据源相关的信息。该接口主要用于为应用程序或工具确定如何与底层数据源交互。应用程序也可以使用DatabaseMetaData接口提供的方法获取数据源信息。
DatabaseMetaData接口中包含超过150个方法,根据这些方法的类型可以分为以下几类:
(1)获取数据源信息。
(2)确定数据源是否支持某一特性或功能。
(3)获取数据源的限制。
(4)确定数据源包含哪些SQL对象以及这些对象的属性。
(5)获取数据源对事务的支持。

例子:

public class Example08 {

    @Test
    public void testDbMetaData() {
        try {
            Class.forName("org.hsqldb.jdbcDriver");
            // 获取Connection对象
            Connection conn = DriverManager.getConnection("jdbc:hsqldb:mem:mybatis",
                    "sa", "");
            DatabaseMetaData dmd = conn.getMetaData();
            System.out.println("数据库URL:" + dmd.getURL());
            System.out.println("数据库用户名:" + dmd.getUserName());
            System.out.println("数据库产品名:" + dmd.getDatabaseProductName());
            System.out.println("数据库产品版本:" + dmd.getDatabaseProductVersion());
            System.out.println("驱动主版本:" + dmd.getDriverMajorVersion());
            System.out.println("驱动副版本:" + dmd.getDriverMinorVersion());
            System.out.println("数据库供应商用于schema的首选术语:" + dmd.getSchemaTerm());
            System.out.println("数据库供应商用于catalog的首选术语:" + dmd.getCatalogTerm());
            System.out.println("数据库供应商用于procedure的首选术语:" + dmd.getProcedureTerm());
            System.out.println("null值是否高排序:" + dmd.nullsAreSortedHigh());
            System.out.println("null值是否低排序:" + dmd.nullsAreSortedLow());
            System.out.println("数据库是否将表存储在本地文件中:" + dmd.usesLocalFiles());
            System.out.println("数据库是否为每个表使用一个文件:" + dmd.usesLocalFilePerTable());
            System.out.println("数据库SQL关键字:" + dmd.getSQLKeywords());
            IOUtils.closeQuietly(conn);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

5.JDBC事务

学这个之前可以先看一下我的这个文章:Mysql事务

Connection对象的autoCommit属性决定什么时候结束一个事务。启用自动提交后,会在每个SQL语句执行完毕后自动提交事务。当Connection对象创建时,默认情况下,事务自动提交是开启的。Connection接口中提供了一个setAutoCommit()方法,可以禁用事务自动提交。此时,需要显式地调用Connection接口提供commit()方法提交事务,或者调用rollback()方法回滚事务。禁用事务自动提交适用于需要将多个SQL语句作为一个事务提交或者事务由应用服务器管理。

例子:

public class Example09 {

    @Before
    public void initData() throws  Exception {
        // 初始化数据
        Class.forName("org.hsqldb.jdbcDriver");
        // 获取Connection对象
        Connection conn = DriverManager.getConnection("jdbc:hsqldb:mem:mybatis",
                "sa", "");
        // 使用Mybatis的ScriptRunner工具类执行数据库脚本
        ScriptRunner scriptRunner = new ScriptRunner(conn);
        // 不输出sql日志
        scriptRunner.setLogWriter(null);
        scriptRunner.runScript(Resources.getResourceAsReader("create-table.sql"));
        System.out.println("------------------------");
    }

    @Test
    public void testSavePoint() {
        try {
            Class.forName("org.hsqldb.jdbcDriver");
            // 获取Connection对象
            Connection conn = DriverManager.getConnection("jdbc:hsqldb:mem:mybatis",
                    "sa", "");
            String sql1 = "insert into user(create_time, name, password, phone, nick_name) " +
                    "values('2010-10-24 10:20:30','User1','test','18700001111','User1')";
            String sql2 = "insert into user(create_time, name, password, phone, nick_name) " +
                    "values('2010-10-24 10:20:30','User2','test','18700001111','User2')";
            conn.setAutoCommit(false);
            Statement stmt = conn.createStatement();
            stmt.executeUpdate(sql1);
            // 创建保存点
            Savepoint savepoint = conn.setSavepoint("SP1");
            stmt.executeUpdate(sql2);
            // 回滚到保存点
            conn.rollback(savepoint);
            conn.commit();
            ResultSet rs  = conn.createStatement().executeQuery("select * from user ");
            DbUtils.dumpRS(rs);
            IOUtils.closeQuietly(stmt);
            IOUtils.closeQuietly(conn);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

二、使用SQL类生成语句

MyBatis中提供了一个SQL工具类。使用这个工具类,我们可以很方便地在Java代码中动态构建SQL语句。

1.无工具类写SQL语句

使用JDBC API开发过项目的读者应该知道,当我们需要使用Statement对象执行SQL时,SQL语句会嵌入Java代码中。SQL语句比较复杂时,我们可能会在代码中对SQL语句进行拼接,查询条件不固定时,还需要根据不同条件拼接不同的SQL语句,拼接语句时不要忘记添加必要的空格,还要注意去掉列表最后一个列名的逗号。这个过程对于开发人员来说简直就是一场噩梦,而且代码可维护性级低,例如:

 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";

2.使用工具类

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);
    }


}

3.SQL工具类的方法及作用

Mybatis常用工具类-SQL类_第2张图片
Mybatis常用工具类-SQL类_第3张图片

4.源码分析

public class SQL extends AbstractSQL<SQL> {

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

}

分析:

  1. SQL继承至AbstractSQL类,只重写了该类的getSelf()方法
public abstract class AbstractSQL<T> {

  private static final String AND = ") \nAND (";
  private static final String OR = ") \nOR (";

  private final SQLStatement sql = new SQLStatement();

  public abstract T getSelf();

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

  public T SET(String sets) {
    sql().sets.add(sets);
    return getSelf();
  }

  /**
   * @since 3.4.2
   */
  public T SET(String... sets) {
    sql().sets.addAll(Arrays.asList(sets));
    return getSelf();
  }

  public T INSERT_INTO(String tableName) {
    sql().statementType = SQLStatement.StatementType.INSERT;
    sql().tables.add(tableName);
    return getSelf();
  }
  .......
 }

分析:

  1. 所有的功能由AbstractSQL类完成,AbstractSQL类中维护了一个SQLStatement内部类的实例和一系列前面提到过的构造SQL语句的方法,例如SELECT()、UPDATE()等方法。
  private static class SQLStatement {
    // SQL语句的类型
    public enum StatementType {
      DELETE, INSERT, SELECT, UPDATE
    }

    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
    }
    .....
  }

分析:

  1. 当执行SELECT().WHERE()这些方法时,就是SQL关键字加入对应数组,重点我们关注toString方法。
  @Override
  public String toString() {
    StringBuilder sb = new StringBuilder();
    // 调用SQLStatement对象的sql()方法生成SQL语句
    sql().sql(sb);
    return sb.toString();
  }
  
  public String sql(Appendable a) {
      SafeAppendable builder = new SafeAppendable(a);
      if (statementType == null) {
        return null;
      }

      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;
    }
    
   private String selectSQL(SafeAppendable builder) {
      if (distinct) {
        sqlClause(builder, "SELECT DISTINCT", select, "", "", ", ");
      } else {
        sqlClause(builder, "SELECT", select, "", "", ", ");
      }

      sqlClause(builder, "FROM", tables, "", "", ", ");
      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();
    }

分析:
1.通过源码分析,根据SQL类型,将数组中的字符串取出来拼接,大家若感兴趣可以去看源码。

学习记录,不断沉淀,终究会成为一个优秀的程序员,加油!
您的点赞、关注与收藏是我分享博客的最大赞赏!
博主博客地址: https://blog.csdn.net/qq_45701514

你可能感兴趣的:(#,Mybatis,mybatis,sql,java)