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 {@Overridepublic ParameterHandler createParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {return null;}@Overridepublic SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType) {return null;}@Overridepublic 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写法,谁要是开发这么写,就等着被领导夸奖吧。