mybatis组件SqlSource的种类

SqlSource是mybatis重要的组件,是对你写的sql语句的简单封装。

 

public interface SqlSource {
  
  BoundSql getBoundSql(Object parameterObject);

}

这个接口有很多种实现:

 

 VelocitySqlSource这个实现类是一个测试。实际上mybatis根本就不会使用这个实现类。

那么在mybatis内部是在哪一步创建SqlSource的呢?那就是在解析我们写的一些sql标签时生成的,在解析sql标签的时候会生成MappedStatement对象,这个对象包含了你写的sql标签所有的内容,也包括sql语句的基本信息,sql语句的基本信息就封装成了SqlSource对象。源码在这个方法:org.apache.ibatis.builder.xml.XMLStatementBuilder#parseStatementNode。

创建SqlSource是由LanguageDriver对象来创建的,这个对象其实也可以在我们写的sql标签中配置,只是我们实际开发不会去自定义这个对象。

 

public class MyLanguageDriver implements LanguageDriver {
    @Override
    public ParameterHandler createParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
        return null;
    }

    @Override
    public SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType) {
        return null;
    }

    @Override
    public SqlSource createSqlSource(Configuration configuration, String script, Class<?> parameterType) {
        return null;
    }
}

 

那么mybatis默认生成的就是XMLLanguageDriver。那么这个对象创建的SqlSource就两种。一种是DynamicSqlSource,还有一种是RawSqlSource。

 public SqlSource parseScriptNode() {
    MixedSqlNode rootSqlNode = parseDynamicTags(context);
    SqlSource sqlSource;
    if (isDynamic) {
      sqlSource = new DynamicSqlSource(configuration, rootSqlNode);
    } else {
      sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType);
    }
    return sqlSource;
  }

 

DynamicSqlSource很好理解,那就是你的sql是动态的,那么就会生成这个对象。如果不是动态的就生成RawSqlSource。

动态sql就比如你的sql语句的where是用<where>标签生成的,包括<if test> 标签,还包括$符号这种sql语句的拼接。然后除了这些情况就会生成RawSqlSource。

但是RawSqlSource其实还包了一层SqlSource对象。

 

我可以告诉大家,他里面的SqlSource最终生成的就是StaticSqlSource对象。而且这个StaticSqlSource中的sql已经是带?的sql了,也就是将你的#{}替换成了?

public class StaticSqlSource implements SqlSource {
  /**
   * 带?的sql
   */
  private final String sql;

 

那么现在我们已经了解到了三个SqlSource的实现。离最后的真相还差一个ProviderSqlSource。这个SqlSource很有意思,想要生成的SqlSource是他。那么你的sql写法也很有意思,不能通过传统的sql标签来实现,也不能用@Slelect 或者其他注解来实现。而是有一种专门的写法来实现。

首先写一个类,这个类中有个方法来返回一个sql,这个sql当然跟你的业务有关。

public class FatherSqlProvider {

    /**
     * @param username sql执行所需要的参数
     * @return
     */
    public String findByUsername(String username) {
        return "select * from father where username = " + "#{username}";
    }
}

 

然后在你的mapper接口层使用他

@SelectProvider(type= FatherSqlProvider.class,method = "findByUsername")
    List<Father> selectByUsername(String username);

 

那么这种写法最终生成的SqlSource就是ProviderSqlSource。我来debug源码来揭露真相。

源码入口在这个方法了:org.apache.ibatis.builder.annotation.MapperAnnotationBuilder#parse,在这个方法后面会对你的mapper接口的方法进行遍历,也就是解析mapper方法上的相关注解。但是mybatis很可惜的是不支持你在mapper方法上自定义注解,但是可以通过拦截器来实现。好,扯多了,看源码:

/**
       * 这里就是遍历dao接口中所有的方法,然后获取方法上的注解进行解析
       * mybatis是支持用注解的方式对MySQL进行crud的。但是实际开发都用映射文件
       */
      for (Method method : type.getMethods()) {
        if (!canHaveStatement(method)) {
          continue;
        }
        if (getAnnotationWrapper(method, false, Select.class, SelectProvider.class).isPresent()
            && method.getAnnotation(ResultMap.class) == null) {
          parseResultMap(method);
        }
        try {
          //最终会在这里创建SqlSource对象
          parseStatement(method);
        } catch (IncompleteElementException e) {
          configuration.addIncompleteMethod(new MethodResolver(this, method));
        }
      }

 

继续跟进parseStatement方法

 

真相大白:

好了,到这结束,说实话,暂时没啥用。尤其是这种sql写法,谁要是开发这么写,就等着被领导夸奖吧。

 

posted @ 2025-03-23 10:45  诸葛匹夫  阅读(112)  评论(0)    收藏  举报