# 精尽 MyBatis 源码分析 —— SQL 初始化(下)之 SqlSource # 1. 概述 本文接 [《精尽 MyBatis 源码分析 —— SQL 初始化(上)之 SqlNode》](http://svip.iocoder.cn/MyBatis/scripting-1) 一文,来分享 SQL 初始化的下半部分,SqlSource 相关的内容。 # 2. SqlSource `org.apache.ibatis.mapping.SqlSource` ,SQL 来源接口。它代表从 Mapper XML 或方法注解上,读取的一条 SQL 内容。代码如下: ``` // SqlSource.java /** * Represents the content of a mapped statement read from an XML file or an annotation. * It creates the SQL that will be passed to the database out of the input parameter received from the user. */ public interface SqlSource { /** * 根据传入的参数对象,返回 BoundSql 对象 * * @param parameterObject 参数对象 * @return BoundSql 对象 */ BoundSql getBoundSql(Object parameterObject); } ``` SqlSource 有多个实现类,如下图所示:[![类图](20-mybatis-SQL 初始化(下)之 SqlSource.assets/07.png)](http://static.iocoder.cn/images/MyBatis/2020_02_22/07.png)类图 # 3. SqlSourceBuilder `org.apache.ibatis.builder.SqlSourceBuilder` ,继承 BaseBuilder 抽象类,SqlSource 构建器,负责将 SQL 语句中的 `#{}` 替换成相应的 `?` 占位符,并获取该 `?` 占位符对应的 `org.apache.ibatis.mapping.ParameterMapping` 对象。 ## 3.1 构造方法 ``` // SqlSourceBuilder.java private static final String parameterProperties = "javaType,jdbcType,mode,numericScale,resultMap,typeHandler,jdbcTypeName"; public SqlSourceBuilder(Configuration configuration) { super(configuration); } ``` - 为什么 `parameterProperties` 属性是这个值,答案在 [《MyBatis 文档 —— Mapper XML 文件 —— 参数(Parameters)》](http://www.mybatis.org/mybatis-3/zh/sqlmap-xml.html#Parameters) ## 3.2 parse ``` // SqlSourceBuilder.java /** * 执行解析原始 SQL ,成为 SqlSource 对象 * * @param originalSql 原始 SQL * @param parameterType 参数类型 * @param additionalParameters 附加参数集合。可能是空集合,也可能是 {@link org.apache.ibatis.scripting.xmltags.DynamicContext#bindings} 集合 * @return SqlSource 对象 */ public SqlSource parse(String originalSql, Class parameterType, Map additionalParameters) { // <1> 创建 ParameterMappingTokenHandler 对象 ParameterMappingTokenHandler handler = new ParameterMappingTokenHandler(configuration, parameterType, additionalParameters); // <2> 创建 GenericTokenParser 对象 GenericTokenParser parser = new GenericTokenParser("#{", "}", handler); // <3> 执行解析 String sql = parser.parse(originalSql); // <4> 创建 StaticSqlSource 对象 return new StaticSqlSource(configuration, sql, handler.getParameterMappings()); } ``` - `<2>` 处,创建 GenericTokenParser 对象。注意,传入的参数是 `#{` 和 `}` 对。 - `<1>` 处,创建 ParameterMappingTokenHandler 对象。 - `<3>` 处,调用 `GenericTokenParser#parse(String originalSql)` 方法,执行解析。如果匹配到 `#{` + `}` 对后,会调用 ParameterMappingTokenHandler 对应的 `#handleToken(String content)` 方法。详细解析,见 [「3.3 ParameterMappingTokenHandler」](https://svip.iocoder.cn/MyBatis/scripting-2/#) 。 - `<4>` 处,创建 StaticSqlSource 对象。关于 StaticSqlSource 类,详细解析,见 [「4.1 StaticSqlSource」](https://svip.iocoder.cn/MyBatis/scripting-2/#) 。 ## 3.3 ParameterMappingTokenHandler ParameterMappingTokenHandler ,实现 TokenHandler 接口,继承 BaseBuilder 抽象类,负责将匹配到的 `#{` 和 `}` 对,替换成相应的 `?` 占位符,并获取该 `?` 占位符对应的 `org.apache.ibatis.mapping.ParameterMapping` 对象。 ### 3.3.1 构造方法 > ParameterMappingTokenHandler 是 SqlSourceBuilder 的内部私有静态类。 ``` // SqlSourceBuilder.java /** * ParameterMapping 数组 */ private List parameterMappings = new ArrayList<>(); /** * 参数类型 */ private Class parameterType; /** * additionalParameters 参数的对应的 MetaObject 对象 */ private MetaObject metaParameters; public ParameterMappingTokenHandler(Configuration configuration, Class parameterType, Map additionalParameters) { super(configuration); this.parameterType = parameterType; // 创建 additionalParameters 参数的对应的 MetaObject 对象 this.metaParameters = configuration.newMetaObject(additionalParameters); } ``` ### 3.3.2 handleToken ``` // SqlSourceBuilder.java @Override public String handleToken(String content) { // <1> 构建 ParameterMapping 对象,并添加到 parameterMappings 中 parameterMappings.add(buildParameterMapping(content)); // <2> 返回 ? 占位符 return "?"; } ``` - `<1>` 处,调用 `#buildParameterMapping(String content)` 方法,构建 ParameterMapping 对象,并添加到 `parameterMappings` 中。详细解析,见 [「3.3.3 buildParameterMapping」](https://svip.iocoder.cn/MyBatis/scripting-2/#) 。 - `<2>` 处,返回 `?` 占位符。 - 如上两个步骤,就是 ParameterMappingTokenHandler 的核心。 ### 3.3.3 buildParameterMapping `#buildParameterMapping(String content)` 方法,构建 ParameterMapping 对象。代码如下: ``` // SqlSourceBuilder.java private ParameterMapping buildParameterMapping(String content) { // <1> 解析成 Map 集合 Map propertiesMap = parseParameterMapping(content); // <2> 获得属性的名字和类型 String property = propertiesMap.get("property"); // 名字 Class propertyType; // 类型 if (metaParameters.hasGetter(property)) { // issue #448 get type from additional params propertyType = metaParameters.getGetterType(property); } else if (typeHandlerRegistry.hasTypeHandler(parameterType)) { propertyType = parameterType; } else if (JdbcType.CURSOR.name().equals(propertiesMap.get("jdbcType"))) { propertyType = java.sql.ResultSet.class; } else if (property == null || Map.class.isAssignableFrom(parameterType)) { propertyType = Object.class; } else { MetaClass metaClass = MetaClass.forClass(parameterType, configuration.getReflectorFactory()); if (metaClass.hasGetter(property)) { propertyType = metaClass.getGetterType(property); } else { propertyType = Object.class; } } // <3> 创建 ParameterMapping.Builder 对象 ParameterMapping.Builder builder = new ParameterMapping.Builder(configuration, property, propertyType); // <3.1> 初始化 ParameterMapping.Builder 对象的属性 Class javaType = propertyType; String typeHandlerAlias = null; for (Map.Entry entry : propertiesMap.entrySet()) { String name = entry.getKey(); String value = entry.getValue(); if ("javaType".equals(name)) { javaType = resolveClass(value); builder.javaType(javaType); } else if ("jdbcType".equals(name)) { builder.jdbcType(resolveJdbcType(value)); } else if ("mode".equals(name)) { builder.mode(resolveParameterMode(value)); } else if ("numericScale".equals(name)) { builder.numericScale(Integer.valueOf(value)); } else if ("resultMap".equals(name)) { builder.resultMapId(value); } else if ("typeHandler".equals(name)) { typeHandlerAlias = value; } else if ("jdbcTypeName".equals(name)) { builder.jdbcTypeName(value); } else if ("property".equals(name)) { // Do Nothing } else if ("expression".equals(name)) { throw new BuilderException("Expression based parameters are not supported yet"); } else { throw new BuilderException("An invalid property '" + name + "' was found in mapping #{" + content + "}. Valid properties are " + parameterProperties); } } // <3.2> 如果 typeHandlerAlias 非空,则获得对应的 TypeHandler 对象,并设置到 ParameterMapping.Builder 对象中 if (typeHandlerAlias != null) { builder.typeHandler(resolveTypeHandler(javaType, typeHandlerAlias)); } // <3.3> 创建 ParameterMapping 对象 return builder.build(); } ``` - `<1>` 处,调用 `#parseParameterMapping(String content)` 方法,解析成 Map 集合。代码如下: ``` // SqlSourceBuilder.java private Map parseParameterMapping(String content) { try { return new ParameterExpression(content); } catch (BuilderException ex) { throw ex; } catch (Exception ex) { throw new BuilderException("Parsing error was found in mapping #{" + content + "}. Check syntax #{property|(expression), var1=value1, var2=value2, ...} ", ex); } } ``` - [`org.apache.ibatis.builder.ParameterExpression`](https://github.com/YunaiV/mybatis-3/blob/master/src/main/java/org/apache/ibatis/builder/ParameterExpression.java) 类,继承 HashMap 类,负责参数表达式。感兴趣的胖友,可以自己看看。😈 艿艿暂时没细看。 - 假设 `content = "#{age,javaType=int,jdbcType=NUMERIC,typeHandler=MyTypeHandler}"` 的结果如下图:[![示例](20-mybatis-SQL 初始化(下)之 SqlSource.assets/01.png)](http://static.iocoder.cn/images/MyBatis/2020_02_25/01.png)示例 - `<2>` 处,获得**属性**的名字和类型。 - ``` <3> ``` 处,创建 ParameterMapping.Builder 对象。 - `<3.1>` 处,初始化 ParameterMapping.Builder 对象的属性。 - `<3.2>` 处,如果 `typeHandlerAlias` 非空,则获得对应的 TypeHandler 对象,并设置到 ParameterMapping.Builder 对象中。 - `<3.3>` 处,创建 ParameterMapping 对象。 - 关于 ParameterMapping 类,胖友可以跳到 [「5.1 ParameterMapping」](https://svip.iocoder.cn/MyBatis/scripting-2/#) 中看看。 # 4. SqlSource 的实现类 ## 4.1 StaticSqlSource `org.apache.ibatis.builder.StaticSqlSource` ,实现 SqlSource 接口,静态的 SqlSource 实现类。代码如下: ``` // StaticSqlSource.java public class StaticSqlSource implements SqlSource { /** * 静态的 SQL */ private final String sql; /** * ParameterMapping 集合 */ private final List parameterMappings; private final Configuration configuration; public StaticSqlSource(Configuration configuration, String sql) { this(configuration, sql, null); } public StaticSqlSource(Configuration configuration, String sql, List 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 的静态,是相对于 DynamicSqlSource 和 RawSqlSource 来说呢。实际上,`StaticSqlSource.sql` 属性,上面还是可能包括 `?` 占位符。 - `#getBoundSql((Object parameterObject)` 方法,创建 BoundSql 对象。通过 `parameterMappings` 和 `parameterObject` 属性,可以设置 `sql` 上的每个占位符的值。例如:[![示例](20-mybatis-SQL 初始化(下)之 SqlSource.assets/02.png)](http://static.iocoder.cn/images/MyBatis/2020_02_25/02.png)示例 - 另外,我们在回过头看看 SqlSourceBuilder 类,它创建的也是 StaticSqlSource 对象。 ------ 下面,我们来看看下图的两段代码,胖友看看是否发现了什么规律:[![示例](20-mybatis-SQL 初始化(下)之 SqlSource.assets/03.png)](http://static.iocoder.cn/images/MyBatis/2020_02_25/03.png)示例 - 如果**是**动态 SQL 的情况下,则创建 DynamicSqlSource 对象。 - 如果**非**动态 SQL 的情况下,则创建 RawSqlSource 对象。 下面,我们在「4.2」和「4.3」中,看看两者的区别。 ## 4.2 DynamicSqlSource `org.apache.ibatis.scripting.xmltags.DynamicSqlSource` ,实现 SqlSource 接口,动态的 SqlSource 实现类。代码如下: ``` // DynamicSqlSource.java public class DynamicSqlSource implements SqlSource { private final Configuration configuration; /** * 根 SqlNode 对象 */ private final SqlNode rootSqlNode; public DynamicSqlSource(Configuration configuration, SqlNode rootSqlNode) { this.configuration = configuration; this.rootSqlNode = rootSqlNode; } @Override public BoundSql getBoundSql(Object parameterObject) { // <1> 应用 rootSqlNode DynamicContext context = new DynamicContext(configuration, parameterObject); rootSqlNode.apply(context); // <2> 创建 SqlSourceBuilder 对象 SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration); // <2> 解析出 SqlSource 对象 Class parameterType = parameterObject == null ? Object.class : parameterObject.getClass(); SqlSource sqlSource = sqlSourceParser.parse(context.getSql(), parameterType, context.getBindings()); // <3> 获得 BoundSql 对象 BoundSql boundSql = sqlSource.getBoundSql(parameterObject); // <4> 添加附加参数到 BoundSql 对象中 for (Map.Entry entry : context.getBindings().entrySet()) { boundSql.setAdditionalParameter(entry.getKey(), entry.getValue()); } // <5> 返回 BoundSql 对象 return boundSql; } } ``` - 适用于使用了 OGNL 表达式,或者使用了 `${}` 表达式的 SQL ,所以它是**动态**的,需要在每次执行 `#getBoundSql(Object parameterObject)` 方法,根据参数,生成对应的 SQL 。 - `<1>` 处,创建 DynamicContext 对象,并执行 `DynamicContext#apply(DynamicContext context)` 方法,应用 `rootSqlNode` ,相当于生成**动态** SQL 。 - ``` <2> ``` 处,创建 SqlSourceBuilder 对象,并执行 ``` SqlSourceBuilder#parse(String originalSql, Class parameterType, Map additionalParameters) ``` 方法,解析出 SqlSource 对象。 注意 : - 返回的 SqlSource 对象,类型是 **StaticSqlSource** 类。 - 这个过程,会将 `#{}` 对,转换成对应的 `?` 占位符,并获取该占位符对应的 ParameterMapping 对象。 - `<3>` 处,调用 `StaticSqlSource#getBoundSql(Object parameterObject)` 方法,获得 BoundSql 对象。 - `<4>` 处,从 `context.bindings` 中,添加附加参数到 BoundSql 对象中。为什么要这么做?胖友回看下 [《精尽 MyBatis 源码分析 —— SQL 初始化(上)之 SqlNode》](http://svip.iocoder.cn/MyBatis/scripting-1) 的 [「6.7 ChooseSqlNode」](https://svip.iocoder.cn/MyBatis/scripting-2/#) 就明白了。 - `<5>` 处,返回 BoundSql 对象。 ## 4.3 RawSqlSource `org.apache.ibatis.scripting.xmltags.RawSqlSource` ,实现 SqlSource 接口,**原始**的 SqlSource 实现类。代码如下: ``` // RawSqlSource.java public class RawSqlSource implements SqlSource { /** * SqlSource 对象 */ private final SqlSource sqlSource; public RawSqlSource(Configuration configuration, SqlNode rootSqlNode, Class parameterType) { // <1> 获得 Sql this(configuration, getSql(configuration, rootSqlNode), parameterType); } public RawSqlSource(Configuration configuration, String sql, Class parameterType) { // <2> 创建 SqlSourceBuilder 对象 SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration); Class clazz = parameterType == null ? Object.class : parameterType; // <2> 获得 SqlSource 对象 sqlSource = sqlSourceParser.parse(sql, clazz, new HashMap<>()); } private static String getSql(Configuration configuration, SqlNode rootSqlNode) { // 创建 DynamicContext 对象 DynamicContext context = new DynamicContext(configuration, null); // 解析出 SqlSource 对象 rootSqlNode.apply(context); // 获得 sql return context.getSql(); } @Override public BoundSql getBoundSql(Object parameterObject) { // 获得 BoundSql 对象 return sqlSource.getBoundSql(parameterObject); } } ``` - 适用于仅使用 `#{}` 表达式,或者不使用任何表达式的情况,所以它是**静态**的,仅需要在构造方法中,直接生成对应的 SQL 。 - 在构造方法中: - `<1>` 处,调用 `#getSql(Configuration configuration, SqlNode rootSqlNode)` 方法,获得 SQL 。 - `<2>` 处,创建 SqlSourceBuilder 对象,并执行 `SqlSourceBuilder#parse(String originalSql, Class parameterType, Map additionalParameters)` 方法,解析出 SqlSource 对象。 - 对应到 DynamicSqlSource ,就是 `<1>` + `<2>` 了。 - 在 ``` #getBoundSql(Object parameterObject) ``` 方法中: - `<3>` 处,调用 `StaticSqlSource#getBoundSql(Object parameterObject)` 方法,获得 BoundSql 对象。 - 对应到 DynamicSqlSource ,就是 `<1>` + `<2>` 了。 这样,RawSqlSource 和 DynamicSqlSource 的区别,是不是就清晰了。 ## 4.4 ProviderSqlSource `org.apache.ibatis.builder.annotation.ProviderSqlSource` ,实现 SqlSource 接口,基于方法上的 `@ProviderXXX` 注解的 SqlSource 实现类。 ### 4.4.1 构造方法 ``` // ProviderSqlSource.java private final Configuration configuration; private final SqlSourceBuilder sqlSourceParser; /** * `@ProviderXXX` 注解的对应的类 */ private final Class providerType; /** * `@ProviderXXX` 注解的对应的方法 */ private Method providerMethod; /** * `@ProviderXXX` 注解的对应的方法的参数名数组 */ private String[] providerMethodArgumentNames; /** * `@ProviderXXX` 注解的对应的方法的参数类型数组 */ private Class[] providerMethodParameterTypes; /** * 若 {@link #providerMethodParameterTypes} 参数有 ProviderContext 类型的,创建 ProviderContext 对象 */ private ProviderContext providerContext; /** * {@link #providerMethodParameterTypes} 参数中,ProviderContext 类型的参数,在数组中的位置 */ private Integer providerContextIndex; /** * @deprecated Please use the {@link #ProviderSqlSource(Configuration, Object, Class, Method)} instead of this. */ @Deprecated public ProviderSqlSource(Configuration configuration, Object provider) { this(configuration, provider, null, null); } /** * @since 3.4.5 */ public ProviderSqlSource(Configuration configuration, Object provider, Class mapperType, Method mapperMethod) { String providerMethodName; try { this.configuration = configuration; // 创建 SqlSourceBuilder 对象 this.sqlSourceParser = new SqlSourceBuilder(configuration); // 获得 @ProviderXXX 注解的对应的类 this.providerType = (Class) provider.getClass().getMethod("type").invoke(provider); // 获得 @ProviderXXX 注解的对应的方法相关的信息 providerMethodName = (String) provider.getClass().getMethod("method").invoke(provider); for (Method m : this.providerType.getMethods()) { if (providerMethodName.equals(m.getName()) && CharSequence.class.isAssignableFrom(m.getReturnType())) { if (providerMethod != null) { throw new BuilderException("Error creating SqlSource for SqlProvider. Method '" + providerMethodName + "' is found multiple in SqlProvider '" + this.providerType.getName() + "'. Sql provider method can not overload."); } this.providerMethod = m; this.providerMethodArgumentNames = new ParamNameResolver(configuration, m).getNames(); this.providerMethodParameterTypes = m.getParameterTypes(); } } } catch (BuilderException e) { throw e; } catch (Exception e) { throw new BuilderException("Error creating SqlSource for SqlProvider. Cause: " + e, e); } if (this.providerMethod == null) { throw new BuilderException("Error creating SqlSource for SqlProvider. Method '" + providerMethodName + "' not found in SqlProvider '" + this.providerType.getName() + "'."); } // 初始化 providerContext 和 providerContextIndex 属性 for (int i = 0; i < this.providerMethodParameterTypes.length; i++) { Class parameterType = this.providerMethodParameterTypes[i]; if (parameterType == ProviderContext.class) { if (this.providerContext != null) { throw new BuilderException("Error creating SqlSource for SqlProvider. ProviderContext found multiple in SqlProvider method (" + this.providerType.getName() + "." + providerMethod.getName() + "). ProviderContext can not define multiple in SqlProvider method argument."); } this.providerContext = new ProviderContext(mapperType, mapperMethod); this.providerContextIndex = i; } } } ``` - 参数比较多,但是灰常简单,胖友耐心的瞅瞅。 ### 4.4.2 getBoundSql ``` // ProviderSqlSource.java @Override public BoundSql getBoundSql(Object parameterObject) { // <1> 创建 SqlSource 对象 SqlSource sqlSource = createSqlSource(parameterObject); // <2> 获得 BoundSql 对象 return sqlSource.getBoundSql(parameterObject); } ``` - `<1>` 处,调用 `#createSqlSource(Object parameterObject)` 方法,创建 SqlSource 对象。因为它是通过 `@ProviderXXX` 注解的指定类的指定方法,动态生成 SQL 。所以,从思路上,和 DynamicSqlSource 是有点接近的。详细解析,见 [「4.4.3 createSqlSource」](https://svip.iocoder.cn/MyBatis/scripting-2/#) 。 - `<2>` 处,调用 `SqlSource#getBoundSql(Object parameterObject)` 方法,获得 BoundSql 对象。 ### 4.4.3 createSqlSource `#createSqlSource(Object parameterObject)` 方法,创建 SqlSource 对象。代码如下: ``` // ProviderSqlSource.java private SqlSource createSqlSource(Object parameterObject) { try { // <1> 获得 SQL int bindParameterCount = providerMethodParameterTypes.length - (providerContext == null ? 0 : 1); String sql; if (providerMethodParameterTypes.length == 0) { sql = invokeProviderMethod(); } else if (bindParameterCount == 0) { sql = invokeProviderMethod(providerContext); } else if (bindParameterCount == 1 && (parameterObject == null || providerMethodParameterTypes[(providerContextIndex == null || providerContextIndex == 1) ? 0 : 1].isAssignableFrom(parameterObject.getClass()))) { sql = invokeProviderMethod(extractProviderMethodArguments(parameterObject)); // <1.1> } else if (parameterObject instanceof Map) { @SuppressWarnings("unchecked") Map params = (Map) parameterObject; sql = invokeProviderMethod(extractProviderMethodArguments(params, providerMethodArgumentNames)); <1.2> } else { throw new BuilderException("Error invoking SqlProvider method (" + providerType.getName() + "." + providerMethod.getName() + "). Cannot invoke a method that holds " + (bindParameterCount == 1 ? "named argument(@Param)" : "multiple arguments") + " using a specifying parameterObject. In this case, please specify a 'java.util.Map' object."); } // <2> 获得参数 Class parameterType = parameterObject == null ? Object.class : parameterObject.getClass(); // <3> 替换掉 SQL 上的属性 // <4> 解析出 SqlSource 对象 return sqlSourceParser.parse(replacePlaceholder(sql), parameterType, new HashMap<>()); } catch (BuilderException e) { throw e; } catch (Exception e) { throw new BuilderException("Error invoking SqlProvider method (" + providerType.getName() + "." + providerMethod.getName() + "). Cause: " + e, e); } } ``` - `<1>` 处,获得 SQL 。 - `<1.1>` 处,调用 `#extractProviderMethodArguments(Object parameterObject)` 方法,获得方法参数。代码如下: ``` // ProviderSqlSource.java private Object[] extractProviderMethodArguments(Object parameterObject) { if (providerContext != null) { Object[] args = new Object[2]; args[providerContextIndex == 0 ? 1 : 0] = parameterObject; args[providerContextIndex] = providerContext; return args; } else { return new Object[]{parameterObject}; } } ``` ``` * 逻辑比较简单,胖友思考下。 * `<1.2>` 处,调用 `#extractProviderMethodArguments(Map params, String[] argumentNames)` 方法,获得方法参数。代码如下: // ProviderSqlSource.javaprivate Object[] extractProviderMethodArguments(Map params, String[] argumentNames) { Object[] args = new Object[argumentNames.length]; for (int i = 0; i < args.length; i++) { if (providerContextIndex != null && providerContextIndex == i) { args[i] = providerContext; } else { args[i] = params.get(argumentNames[i]); } } return args;} * 逻辑比较简单,胖友思考下。 * 上面两个方法,无法理解的胖友,可以看看 `org.apache.ibatis.submitted.sqlprovider.Mapper` 和 `org.apache.ibatis.submitted.sqlprovider.OurSqlBuilder` 类。 * 调用 `#invokeProviderMethod(Object... args)` 方法,执行方法,生成 SQL 。代码如下: // ProviderSqlSource.javaprivate String invokeProviderMethod(Object... args) throws Exception { Object targetObject = null; // 获得对象 if (!Modifier.isStatic(providerMethod.getModifiers())) { targetObject = providerType.newInstance(); } // 反射调用方法 CharSequence sql = (CharSequence) providerMethod.invoke(targetObject, args); return sql != null ? sql.toString() : null;} * 反射调用方法。 ``` - `<2>` 处,获得参数类型。 - `<3>` 处,调用 `#replacePlaceholder(String sql)` 方法,替换掉 SQL 上的属性。代码如下: ``` // ProviderSqlSource.java private String replacePlaceholder(String sql) { return PropertyParser.parse(sql, configuration.getVariables()); } ``` - `<4>` 处,调用 `SqlSourceBuilder#parse(String originalSql, Class parameterType, Map additionalParameters)` 方法,解析出 SqlSource 对象。 - 代码比较长,胖友回过头自己再细看。😈 不过一般来说,MyBatis 注解使用较少,所以胖友也可以不用细看。 ### 4.4.4 ProviderContext `org.apache.ibatis.builder.annotation.ProviderContext` ,ProviderSqlSource 的上下文。代码如下: ``` // ProviderContext.java public final class ProviderContext { /** * Mapper 接口 */ private final Class mapperType; /** * Mapper 的方法 */ private final Method mapperMethod; /** * Constructor. * * @param mapperType A mapper interface type that specified provider * @param mapperMethod A mapper method that specified provider */ ProviderContext(Class mapperType, Method mapperMethod) { this.mapperType = mapperType; this.mapperMethod = mapperMethod; } public Class getMapperType() { return mapperType; } public Method getMapperMethod() { return mapperMethod; } } ``` # 5. BoundSql `org.apache.ibatis.mapping.BoundSql` ,一次可执行的 SQL 封装。代码如下: ``` // BoundSql.java public class BoundSql { /** * SQL 语句 */ private final String sql; /** * ParameterMapping 数组 */ private final List parameterMappings; /** * 参数对象 */ private final Object parameterObject; /** * 附加的参数集合 */ private final Map additionalParameters; /** * {@link #additionalParameters} 的 MetaObject 对象 */ private final MetaObject metaParameters; public BoundSql(Configuration configuration, String sql, List parameterMappings, Object parameterObject) { this.sql = sql; this.parameterMappings = parameterMappings; this.parameterObject = parameterObject; this.additionalParameters = new HashMap<>(); this.metaParameters = configuration.newMetaObject(additionalParameters); } public String getSql() { return sql; } public List getParameterMappings() { return parameterMappings; } public Object getParameterObject() { return parameterObject; } public boolean hasAdditionalParameter(String name) { String paramName = new PropertyTokenizer(name).getName(); return additionalParameters.containsKey(paramName); } public void setAdditionalParameter(String name, Object value) { metaParameters.setValue(name, value); } public Object getAdditionalParameter(String name) { return metaParameters.getValue(name); } } ``` ## 5.1 ParameterMapping `org.apache.ibatis.mapping.ParameterMapping` ,参数映射。代码如下: ``` // ParameterMapping.java private Configuration configuration; /** * 属性的名字 */ private String property; /** * 参数类型。 * * 目前只需要关注 ParameterMode.IN 的情况,另外的 OUT、INOUT 是在存储过程中使用,暂时无视 */ private ParameterMode mode; /** * Java 类型 */ private Class javaType = Object.class; /** * JDBC 类型 */ private JdbcType jdbcType; /** * 对于数值类型,还有一个小数保留位数的设置,来确定小数点后保留的位数 */ private Integer numericScale; /** * TypeHandler 对象 * * {@link Builder#resolveTypeHandler()} */ private TypeHandler typeHandler; /** * 貌似只在 ParameterMode 在 OUT、INOUT 是在存储过程中使用 */ private String resultMapId; /** * 貌似只在 ParameterMode 在 OUT、INOUT 是在存储过程中使用 */ private String jdbcTypeName; /** * 表达式。 * * ps:目前暂时不支持 */ private String expression; public static class Builder { // ... 省略代码 } ``` - 参数比较简单,胖友自己看看注释。可以忽略 ParameterMode 属性为 `OUT` 和 `INOUT` 是在存储过程中使用的情况。 - 完整的该类,可点击 [ParameterMapping](https://github.com/YunaiV/mybatis-3/blob/master/src/main/java/org/apache/ibatis/mapping/ParameterMapping.java) 查看。 - 关于 ParameterMode 属性为 `OUT` 和 `INOUT` 是在存储过程中使用的情况,可以看看 [《Mybatis调用MySQL存储过程》](https://blog.csdn.net/u010046908/article/details/69944959) 。当然,也可以不看,因为很少使用存储过程了。 ## 5.2 ParameterMode `org.apache.ibatis.mapping.ParameterMode` ,参数类型。代码如下: ``` // ParameterMode.java public enum ParameterMode { /** * 输入 */ IN, /** * 输出 */ OUT, /** * IN + OUT */ INOUT } ``` - 只需要关注 `IN` 的情况。 - 另外,MyBatis 存储过程相关的源码,本系列会直接忽略。嘿嘿。 # 7. ParameterHandler `org.apache.ibatis.executor.parameter.ParameterHandler` ,参数处理器接口。代码如下: ``` // ParameterHandler.java /** * A parameter handler sets the parameters of the {@code PreparedStatement} */ public interface ParameterHandler { /** * @return 参数对象 */ Object getParameterObject(); /** * 设置 PreparedStatement 的占位符参数 * * @param ps PreparedStatement 对象 * @throws SQLException 发生 SQL 异常时 */ void setParameters(PreparedStatement ps) throws SQLException; } ``` ## 7.1 DefaultParameterHandler `org.apache.ibatis.scripting.default.DefaultParameterHandler` ,实现 ParameterHandler 接口,默认 ParameterHandler 实现类。 ### 7.1.1 构造方法 ``` // DefaultParameterHandler.java private final TypeHandlerRegistry typeHandlerRegistry; /** * MappedStatement 对象 */ private final MappedStatement mappedStatement; /** * 参数对象 */ private final Object parameterObject; /** * BoundSql 对象 */ private final BoundSql boundSql; private final Configuration configuration; public DefaultParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) { this.mappedStatement = mappedStatement; this.configuration = mappedStatement.getConfiguration(); this.typeHandlerRegistry = mappedStatement.getConfiguration().getTypeHandlerRegistry(); this.parameterObject = parameterObject; this.boundSql = boundSql; } ``` ### 7.1.2 setParameters `#setParameters(PreparedStatement ps)` 方法,代码如下: ``` // DefaultParameterHandler.java @Override public void setParameters(PreparedStatement ps) { ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId()); // <1> 遍历 ParameterMapping 数组 List parameterMappings = boundSql.getParameterMappings(); if (parameterMappings != null) { for (int i = 0; i < parameterMappings.size(); i++) { // <2> 获得 ParameterMapping 对象 ParameterMapping parameterMapping = parameterMappings.get(i); if (parameterMapping.getMode() != ParameterMode.OUT) { // <3> 获得值 Object value; String propertyName = parameterMapping.getProperty(); if (boundSql.hasAdditionalParameter(propertyName)) { // issue #448 ask first for additional params value = boundSql.getAdditionalParameter(propertyName); } else if (parameterObject == null) { value = null; } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) { value = parameterObject; } else { MetaObject metaObject = configuration.newMetaObject(parameterObject); value = metaObject.getValue(propertyName); } // <4> 获得 typeHandler、jdbcType 属性 TypeHandler typeHandler = parameterMapping.getTypeHandler(); JdbcType jdbcType = parameterMapping.getJdbcType(); if (value == null && jdbcType == null) { jdbcType = configuration.getJdbcTypeForNull(); } // <5> 设置 ? 占位符的参数 try { typeHandler.setParameter(ps, i + 1, value, jdbcType); } catch (TypeException | SQLException e) { throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e); } } } } } ``` - `<1>` 处,遍历 ParameterMapping 数组。 - `<2>` 处,获得 ParameterMapping 对象。 - `<3>` 处,获得值。有多种情况,胖友可以细看下。 - `<4>` 处,获得 `typeHandler`、`jdbcType` 属性。 - 【重要】`<5>` 处,调用 `TypeHandler#setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType)` 方法,设置指定位置的 `?` 占位符的参数。 # 666. 彩蛋 hoho,刚开始写的有点懵逼。现在清晰多了。哈哈哈哈。 参考和推荐如下文章: - 祖大俊 [《Mybatis3.4.x技术内幕(十七):Mybatis之动态Sql设计原本(上)》](https://my.oschina.net/zudajun/blog/735553) - 祖大俊 [《Mybatis3.4.x技术内幕(十八):Mybatis之动态Sql设计原本(下)》](https://my.oschina.net/zudajun/blog/735731) - 徐郡明 [《MyBatis 技术内幕》](https://item.jd.com/12125531.html) 的 [「3.2 SqlNode&SqlSource」](https://svip.iocoder.cn/MyBatis/scripting-2/#) 小节