为了账号安全,请及时绑定邮箱和手机立即绑定

MyBatis 基石之 SqlSource

标签:
Java SSM

简介

在上一篇 SqlNode 中通过 DynamicContext 连串联 SqlNode 间 SQL 的拼接,其实这个功能是通过 SqlSource 来完成的。通过 SqlSource 接口的 getBoundSql() 方法传入的参数获取 BoundSql,BoundSql 里面有完整的 SQL 语句以及参数列表和实参。SqlSource 表示从 XML 文件或注释中读取的映射语句的内容。它创建的 SQL 将根据从用户接收到的输入参数传递给数据库。

分类

  1. DynamicSqlSource:针对动态 SQL${} 占位符的 SQL
  2. RawSqlSource:针对 #{}占位符的 SQL
  3. ProviderSqlSource:针对 @*Provider 注解 提供的 SQL
  4. StaticSqlSource:仅包含有 ?占位符的 SQL

类图

SqlSource 类图

StaticSqlSource

/**
 * {@link StaticSqlSource} 实例里面的 SQL 语句仅包含 ? 占位符。
 *
 * @author wenhai
 * @date 2021/7/20
 * @see SqlSource
 * @see RawSqlSource
 * @see StaticSqlSource
 * @see DynamicSqlSource
 * @see ProviderSqlSource
 */
public class StaticSqlSourceDemo {
    public static void main(String[] args) {
        Configuration configuration = new Configuration();
        String sql = "SELECT * FROM user WHERE id = #{id}";
        SqlSource staticSqlSource = new StaticSqlSource(configuration, sql);
        BoundSql boundSql = staticSqlSource.getBoundSql(5L);
        System.out.println(boundSql.getSql());
    }
}

运行上述程序控制台输出 SELECT * FROM user WHERE id = #{id} ,不做任何处理。

public class StaticSqlSource implements SqlSource {
  // SQL 语句
  private final String sql;
  // 参数映射列表
  private final List<ParameterMapping> parameterMappings;
  // 全局 Configuration 对象
  private final Configuration configuration;

  public StaticSqlSource(Configuration configuration, String sql) {
    this(configuration, sql, null);
  }

  public StaticSqlSource(Configuration configuration, String sql, List<ParameterMapping> parameterMappings) {
    this.sql = sql;
    this.parameterMappings = parameterMappings;
    this.configuration = configuration;
  }

  @Override
  public BoundSql getBoundSql(Object parameterObject) {
  	// 直接构建 BoundSql 对象返回
    return new BoundSql(configuration, sql, parameterMappings, parameterObject);
  }

}

从 StaticSqlSource#getBoundSql 方法中可以看出在获取 BoundSql 对象时不会对原 SQL 语句进行任何处理。

DynamicSqlSource

/**
 * {@link DynamicSqlSource} 包含动态 SQL 和 ${} 占位符
 *
 * @author wenhai
 * @date 2021/7/20
 * @see SqlSource
 * @see RawSqlSource
 * @see StaticSqlSource
 * @see DynamicSqlSource
 * @see ProviderSqlSource
 */
public class DynamicSqlSourceDemo {
    public static void main(String[] args) {
        Configuration configuration = new Configuration();
        // 实参对象
        Map<String, Object> paraMap = new HashMap<>();
        List<String> list = new ArrayList<>();
        list.add("wenhai");
        list.add("wenhai2");
        paraMap.put("list", list);
        paraMap.put("id", 5);
        SqlNode staticTextSqlNode = new StaticTextSqlNode("SELECT * FROM user WHERE");
        SqlNode textSqlNode = new TextSqlNode(" id = ${id} AND name IN");
        String collection = "list";
        String item = "item";
        String index = "index";
        String open = "(";
        String close = ")";
        String separator = ",";
        ForEachSqlNode forEachSqlNode = new ForEachSqlNode(configuration, new StaticTextSqlNode("#{item}"), collection, index, item, open, close, separator);
        SqlNode mixedSqlNode = new MixedSqlNode(Arrays.asList(staticTextSqlNode, textSqlNode, forEachSqlNode));
        SqlSource sqlSource = new DynamicSqlSource(configuration, mixedSqlNode);
        BoundSql boundSql = sqlSource.getBoundSql(paraMap);
        System.out.println(boundSql.getSql());
    }
}

运行上述程序控制台输出 SELECT * FROM user WHERE id = 5 AND name IN ( ? , ? )

public class DynamicSqlSource implements SqlSource {

  private final Configuration configuration;
  private final SqlNode rootSqlNode;

  public DynamicSqlSource(Configuration configuration, SqlNode rootSqlNode) {
    this.configuration = configuration;
    this.rootSqlNode = rootSqlNode;
  }

  @Override
  public BoundSql getBoundSql(Object parameterObject) {
  	// 构建 DynamicContext 对象来处理 SqlNode
    DynamicContext context = new DynamicContext(configuration, parameterObject);
    rootSqlNode.apply(context);
    SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
    Class<?> parameterType = parameterObject == null ? Object.class : parameterObject.getClass();
    // 通过 SqlSourceBuilder#parse 方法来处理通过 DynamicContext 拼接过的 SQL
    // 主要处理 #{} 占位符替换成 ? 占位符和获取 ParameterMapping 列表
    // 构建 StaticSqlSource 对象
    SqlSource sqlSource = sqlSourceParser.parse(context.getSql(), parameterType, context.getBindings());
    BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
    // 设置参数比如 foreach 标签的里面的额外参数等
    context.getBindings().forEach(boundSql::setAdditionalParameter);
    return boundSql;
  }

}

通过 DynamicSqlSouce#getBoundSql() 方法获取 BoundSql 对象时对 SqlNode 进行了处理,如果是动态 SQL 以及 含义 ${} 占位符的 SQL 语句根据传入的实参进行拼接和替换,如果是 #{} 占位符进行 ?替换,最后通过 StaticSqlSource 构建 BoundSql。

RawSqlSource

/**
 * {@link RawSqlSource} 不包含动态 SQL 和 ${} 占位符
 *
 * @author wenhai
 * @date 2021/7/20
 * @see SqlSource
 * @see RawSqlSource
 * @see StaticSqlSource
 * @see DynamicSqlSource
 * @see ProviderSqlSource
 */
public class RawSqlSourceDemo {
    public static void main(String[] args) {
        Configuration configuration = new Configuration();
        SqlNode sqlNode = new StaticTextSqlNode("SELECT * FROM user WHERE id = #{id}");
        SqlSource sqlSource = new RawSqlSource(configuration, sqlNode, Long.class);
        System.out.println(sqlSource.getBoundSql(5L).getSql());
    }
}

运行上述程序控制台输出 SELECT * FROM user WHERE id = ? ,如果把 #{} 占位符缓存 ${} 占位符或者把 SqlNode 换成别的动态 SqlNode 会出现啥样子结果呢?

public class RawSqlSource implements SqlSource {
  // 存储构建好的 StaticSqlSource 
  private final SqlSource sqlSource;

  public RawSqlSource(Configuration configuration, SqlNode rootSqlNode, Class<?> parameterType) {
  	// 通过 getSql 方法获取 SQL 语句,此时没有传入实参,所以那些动态 SQL 和 ${} 占位符
  	// 无法处理,只能处理 #{} 占位符的 SqlNode
    this(configuration, getSql(configuration, rootSqlNode), parameterType);
  }

  public RawSqlSource(Configuration configuration, String sql, Class<?> parameterType) {
  	// 通过 SqlSourceBuilder#parse 方法替换 #{} 占位符为 ? 并构建 #{} 占位符的参数映射列表 
    SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
    Class<?> clazz = parameterType == null ? Object.class : parameterType;
    sqlSource = sqlSourceParser.parse(sql, clazz, new HashMap<>());
  }

  private static String getSql(Configuration configuration, SqlNode rootSqlNode) {
    DynamicContext context = new DynamicContext(configuration, null);
    rootSqlNode.apply(context);
    return context.getSql();
  }

  @Override
  public BoundSql getBoundSql(Object parameterObject) {
  	// 直接通过 StaticSqlSource#getBoundSql 获取 BoundSql 实例
    return sqlSource.getBoundSql(parameterObject);
  }

}

通过源码分析可以回答例子里面提出的问题。如果是 ${} 占位符则不处理,动态 SQL 有可能会报错或者处理后的 SQL 语句不完整等。

ProviderSqlSource

/**
 * {@link ProviderSqlSource} @*Provider 注解提供的 SQL
 *
 * @author wenhai
 * @date 2021/7/21
 * @see SqlSource
 * @see RawSqlSource
 * @see StaticSqlSource
 * @see DynamicSqlSource
 * @see ProviderSqlSource
 */

public class ProviderSqlSourceDemo {

    public static void main(String[] args) throws NoSuchMethodException {
        Configuration configuration = new Configuration();
        SelectProvider provider = UserMapper.class.getMethod("select", String.class).getAnnotation(SelectProvider.class);
        SqlSource providerSqlSource = new ProviderSqlSource(configuration, provider, null, null);
        System.out.println(providerSqlSource.getBoundSql("wenhai").getSql());
    }

    public String getSql() {
        return "SELECT * FROM user WHERE name = #{name}";
    }

    interface UserMapper {
        @SelectProvider(type = ProviderSqlSourceDemo.class, method = "getSql")
        List<User> select(String name);
    }
}

运行上述程序控制台输出 SELECT * FROM user WHERE name = ?

	@Override
	public BoundSql getBoundSql(Object parameterObject) {
	  // 通过 @*Provider 注解元信息通过反射调用方法拿到 SQL,
	  // 然后通过 XMLLanguageDriver#createSqlSource 方法解析 SQL 语句
	  // 获取 DynamicSqlSource/RawSqlSource -> StaticSqlSource
	  SqlSource sqlSource = createSqlSource(parameterObject);
	  return sqlSource.getBoundSql(parameterObject);
	}

总结

根据 SQL 来源解析 SQL 获取 SqlNode,根据 SqlNode 获取对应的 SqlSource 是 DynamicSqlSource 还是 RawSqlSource。如果是 DynamicSqlSource 根据实参拼接动态 SQL 和处理 ${} 占位符,然后通过 SqlSourceBuilder#parse() 方法转换为 StaticSqlSource,而 RawSqlSource 在实例化的时候就已经通过 SqlSourceBuilder#parse() 方法转换为 StaticSqlSource,不依赖实参所以性能比 DynamicSqlSource 快。ProviderSqlSource 通过解析 SQL 语句之后通过 XMLLanguageDriver#createSqlSource() 方法获取 DynamicSqlSource 或者 RawSqlSource。为后面学习 MappedStatement 和 StatementHandler 打下坚实的基础!

点击查看更多内容
1人点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消