自学内容网 自学内容网

【MyBatis源码】SQL 语句构建器AbstractSQL

介绍

当我们需要使用Statement对象执行SQL时,SQL语句会嵌入Java代码中。SQL语句比较复杂时,我们可能会在代码中对SQL语句进行拼接,查询条件不固定时,还需要根据不同条件拼接不同的SQL语句。在MyBatis中已经为我们提供了这类开发工具类。
MyBatis 中的 AbstractSQL 类是 MyBatis 提供的一个用于构建动态 SQL 语句的工具类。它的主要用途是帮助开发者更灵活、优雅地拼接复杂的 SQL 语句,尤其是在需要根据条件生成不同的 SQL 语句时,可以减少手动拼接 SQL 字符串的繁琐工作,并提高代码的可读性和维护性。
在日常开发中,尤其是当涉及到复杂查询、更新、插入时,AbstractSQL 可以用于生成动态 SQL。其用途主要包括以下几个方面:

动态条件查询:根据不同的条件拼接 SELECT 语句。
动态插入:根据传入的实体类生成不同的 INSERT 语句。
动态更新:根据条件生成 UPDATE 语句,只更新有值的字段。
动态删除:根据条件拼接 DELETE 语句。
AbstractSQL 的核心思想是通过链式调用的方式构建 SQL 语句,这类似于构建器模式(Builder Pattern)。

org.apache.ibatis.jdbc.SQL

public class SQL extends AbstractSQL<SQL> {

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

SQL类继承了AbstractSQL,在日常开发中一般使用SQL类。

SQL类使用示例

【动态构建 SELECT 查询】

  @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);
  }
// 匿名内部类风格
public String deletePersonSql() {
  return new SQL() {{
    DELETE_FROM("PERSON");
    WHERE("ID = #{id}");
  }}.toString();
}

// Builder / Fluent 风格
public String insertPersonSql() {
  String sql = new SQL()
    .INSERT_INTO("PERSON")
    .VALUES("ID, FIRST_NAME", "#{id}, #{firstName}")
    .VALUES("LAST_NAME", "#{lastName}")
    .toString();
  return sql;
}

// 动态条件(注意参数需要使用 final 修饰,以便匿名内部类对它们进行访问)
public String selectPersonLike(final String id, final String firstName, final String lastName) {
  return new SQL() {{
    SELECT("P.ID, P.USERNAME, P.PASSWORD, P.FIRST_NAME, P.LAST_NAME");
    FROM("PERSON P");
    if (id != null) {
      WHERE("P.ID like #{id}");
    }
    if (firstName != null) {
      WHERE("P.FIRST_NAME like #{firstName}");
    }
    if (lastName != null) {
      WHERE("P.LAST_NAME like #{lastName}");
    }
    ORDER_BY("P.LAST_NAME");
  }}.toString();
}

public String deletePersonSql() {
  return new SQL() {{
    DELETE_FROM("PERSON");
    WHERE("ID = #{id}");
  }}.toString();
}

public String insertPersonSql() {
  return new SQL() {{
    INSERT_INTO("PERSON");
    VALUES("ID, FIRST_NAME", "#{id}, #{firstName}");
    VALUES("LAST_NAME", "#{lastName}");
  }}.toString();
}

public String updatePersonSql() {
  return new SQL() {{
    UPDATE("PERSON");
    SET("FIRST_NAME = #{firstName}");
    WHERE("ID = #{id}");
  }}.toString();
}

相关官方文档:
https://mybatis.org/mybatis-3/zh_CN/statement-builders.html

@SelectProvider搭配动态SQL

@SelectProvider 注解是 MyBatis 提供的一种动态 SQL 语句生成方式,用于将复杂的查询逻辑封装在 Java 方法中,而不是直接在 Mapper 接口的方法上书写固定的 SQL 语句。通过 @SelectProvider 注解,MyBatis 可以根据实际情况动态生成 SQL,这使得 SQL 语句更加灵活和可维护。
@SelectProvider 的基本语法

@SelectProvider(type = SQLProviderClass.class, method = "methodName")
List<ResultType> selectMethod(参数);

type:指定提供 SQL 语句的类(通常称为 Provider 类)。
method:指定 Provider 类中的方法名称,该方法用于动态生成 SQL。
selectMethod:Mapper 接口中的方法,最终会执行 methodName 返回的 SQL 语句。

这个注解和@SELECT的区别在于@SelectProvider 注解可以中参数SQLProviderClass可以搭配SQL动态语句类关联SQL。

  @SelectProvider(type = UserSqlProvider.class, method = "buildSelectSql")
  List<Map<String, Object>> selectUsers(Map<String, Object> params);
public class UserSqlProvider {

  public String buildSelectSql(Map<String, Object> params) {
    return new SQL() {{
      SELECT("*");
      FROM("t_user");
      if (params.get("name") != null) {
        WHERE("name = #{name}");
      }
      if (params.get("age") != null) {
        WHERE("age = #{age}");
      }
      ORDER_BY("id DESC");
    }}.toString();
  }
}

测试类:

  /**
   * 测试@SelectProvider使用
   */
  @Test
  public void test6() throws Exception {
    InputStream resource = Resources.getResourceAsStream(MybatisTest.class.getClassLoader(), "mybatis-config.xml");
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resource);
    Configuration configuration = sqlSessionFactory.getConfiguration();
    // 手动注册mapper
    configuration.addMapper(UserMapper.class);
    SqlSession sqlSession = sqlSessionFactory.openSession();
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    Map<String, Object> params = new HashMap<>();
    params.put("name", "zhangSan");
    params.put("age", 18);
    List<Map<String, Object>> res = mapper.selectUsers(params);
    System.out.println(res);
  }

在这里插入图片描述

AbstractSQL类源码分析

SQL继承至AbstractSQL类,只重写了该类的getSelf()方法,所有的功能由AbstractSQL类完成,AbstractSQL类中维护了一个SQLStatement内部类的实例和一系列前面提到过的构造SQL语句的方法,例如SELECT()、UPDATE()等方法。

  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;
    String offset;
    String limit;
    LimitingRowsStrategy limitingRowsStrategy = LimitingRowsStrategy.NOP;
 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;
      }

SQLStatement内部类用于描述一个SQL语句,该类中通过StatementType确定SQL语句的类型。SQLStatement类中还维护了一系列的ArrayList属性,当调用SELECT()、UPDATE()等方法时,这些方法的参数内容会记录在这些ArrayList对象中。
AbstrastSQL类重写了toString()方法,该方法中会调用SQLStatement对象的sql()方法生成SQL字符串。这里会根据不同的SQL类型进行不同类型的SQL语句拼接。

   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, "", "", ", ");
      limitingRowsStrategy.appendClause(builder, offset, limit);
      return builder.toString();
    }
  @Test
  public void testSelectSQL2() {
    String newSql = new SQL() {{
      SELECT("name,mobile_no,age");
      FROM("t_user A");
      INNER_JOIN("student B on B.name = A.name");
      WHERE("B.name like ?");
      OR();
      WHERE("A.ID = ?");
      ORDER_BY("A.ID DESC");
    }}.toString();
    System.out.println(newSql);
  }

在这里插入图片描述


原文地址:https://blog.csdn.net/Octopus21/article/details/143651957

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!