1. 概述 本文接 《精尽 MyBatis 源码分析 —— SQL 初始化(上)之 SqlNode》 一文,来分享 SQL 初始化的下半部分,SqlSource 相关的内容。
2. SqlSource org.apache.ibatis.mapping.SqlSource
,SQL 来源接口。它代表从 Mapper XML 或方法注解上,读取的一条 SQL 内容。代码如下:
public interface SqlSource { BoundSql getBoundSql (Object parameterObject) ; }
SqlSource 有多个实现类,如下图所示:类图
3. SqlSourceBuilder org.apache.ibatis.builder.SqlSourceBuilder
,继承 BaseBuilder 抽象类,SqlSource 构建器,负责将 SQL 语句中的 #{}
替换成相应的 ?
占位符,并获取该 ?
占位符对应的 org.apache.ibatis.mapping.ParameterMapping
对象。
3.1 构造方法 private static final String parameterProperties = "javaType,jdbcType,mode,numericScale,resultMap,typeHandler,jdbcTypeName" ;public SqlSourceBuilder (Configuration configuration) { super (configuration); }
3.2 parse public SqlSource parse (String originalSql, Class<?> parameterType, Map<String, Object> additionalParameters) { ParameterMappingTokenHandler handler = new ParameterMappingTokenHandler(configuration, parameterType, additionalParameters); GenericTokenParser parser = new GenericTokenParser("#{" , "}" , handler); String sql = parser.parse(originalSql); return new StaticSqlSource(configuration, sql, handler.getParameterMappings()); }
<2>
处,创建 GenericTokenParser 对象。注意,传入的参数是 #{
和 }
对。
<1>
处,创建 ParameterMappingTokenHandler 对象。
<3>
处,调用 GenericTokenParser#parse(String originalSql)
方法,执行解析。如果匹配到 #{
+ }
对后,会调用 ParameterMappingTokenHandler 对应的 #handleToken(String content)
方法。详细解析,见 「3.3 ParameterMappingTokenHandler」 。
<4>
处,创建 StaticSqlSource 对象。关于 StaticSqlSource 类,详细解析,见 「4.1 StaticSqlSource」 。
3.3 ParameterMappingTokenHandler ParameterMappingTokenHandler ,实现 TokenHandler 接口,继承 BaseBuilder 抽象类,负责将匹配到的 #{
和 }
对,替换成相应的 ?
占位符,并获取该 ?
占位符对应的 org.apache.ibatis.mapping.ParameterMapping
对象。
3.3.1 构造方法
ParameterMappingTokenHandler 是 SqlSourceBuilder 的内部私有静态类。
private List<ParameterMapping> parameterMappings = new ArrayList<>();private Class<?> parameterType;private MetaObject metaParameters;public ParameterMappingTokenHandler (Configuration configuration, Class<?> parameterType, Map<String, Object> additionalParameters) { super (configuration); this .parameterType = parameterType; this .metaParameters = configuration.newMetaObject(additionalParameters); }
3.3.2 handleToken @Override public String handleToken (String content) { parameterMappings.add(buildParameterMapping(content)); return "?" ; }
<1>
处,调用 #buildParameterMapping(String content)
方法,构建 ParameterMapping 对象,并添加到 parameterMappings
中。详细解析,见 「3.3.3 buildParameterMapping」 。
<2>
处,返回 ?
占位符。
如上两个步骤,就是 ParameterMappingTokenHandler 的核心。
3.3.3 buildParameterMapping #buildParameterMapping(String content)
方法,构建 ParameterMapping 对象。代码如下:
private ParameterMapping buildParameterMapping (String content) { Map<String, String> propertiesMap = parseParameterMapping(content); String property = propertiesMap.get("property" ); Class<?> propertyType; if (metaParameters.hasGetter(property)) { 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; } } ParameterMapping.Builder builder = new ParameterMapping.Builder(configuration, property, propertyType); Class<?> javaType = propertyType; String typeHandlerAlias = null ; for (Map.Entry<String, String> 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)) { } 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); } } if (typeHandlerAlias != null ) { builder.typeHandler(resolveTypeHandler(javaType, typeHandlerAlias)); } return builder.build(); }
4. SqlSource 的实现类 4.1 StaticSqlSource org.apache.ibatis.builder.StaticSqlSource
,实现 SqlSource 接口,静态的 SqlSource 实现类。代码如下:
public class StaticSqlSource implements SqlSource { private final String sql; private final List<ParameterMapping> parameterMappings; 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) { return new BoundSql(configuration, sql, parameterMappings, parameterObject); } }
StaticSqlSource 的静态,是相对于 DynamicSqlSource 和 RawSqlSource 来说呢。实际上,StaticSqlSource.sql
属性,上面还是可能包括 ?
占位符。
#getBoundSql((Object parameterObject)
方法,创建 BoundSql 对象。通过 parameterMappings
和 parameterObject
属性,可以设置 sql
上的每个占位符的值。例如:示例
另外,我们在回过头看看 SqlSourceBuilder 类,它创建的也是 StaticSqlSource 对象。
下面,我们来看看下图的两段代码,胖友看看是否发现了什么规律:示例
如果是 动态 SQL 的情况下,则创建 DynamicSqlSource 对象。
如果非 动态 SQL 的情况下,则创建 RawSqlSource 对象。
下面,我们在「4.2」和「4.3」中,看看两者的区别。
4.2 DynamicSqlSource org.apache.ibatis.scripting.xmltags.DynamicSqlSource
,实现 SqlSource 接口,动态的 SqlSource 实现类。代码如下:
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 context = new DynamicContext(configuration, parameterObject); rootSqlNode.apply(context); SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration); Class<?> parameterType = parameterObject == null ? Object.class : parameterObject.getClass(); SqlSource sqlSource = sqlSourceParser.parse(context.getSql(), parameterType, context.getBindings()); BoundSql boundSql = sqlSource.getBoundSql(parameterObject); for (Map.Entry<String, Object> entry : context.getBindings().entrySet()) { boundSql.setAdditionalParameter(entry.getKey(), entry.getValue()); } 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<String, Object> additionalParameters)
方法,解析出 SqlSource 对象。注意 :
返回的 SqlSource 对象,类型是 StaticSqlSource 类。
这个过程,会将 #{}
对,转换成对应的 ?
占位符,并获取该占位符对应的 ParameterMapping 对象。
<3>
处,调用 StaticSqlSource#getBoundSql(Object parameterObject)
方法,获得 BoundSql 对象。
<4>
处,从 context.bindings
中,添加附加参数到 BoundSql 对象中。为什么要这么做?胖友回看下 《精尽 MyBatis 源码分析 —— SQL 初始化(上)之 SqlNode》 的 「6.7 ChooseSqlNode」 就明白了。
<5>
处,返回 BoundSql 对象。
4.3 RawSqlSource org.apache.ibatis.scripting.xmltags.RawSqlSource
,实现 SqlSource 接口,原始 的 SqlSource 实现类。代码如下:
public class RawSqlSource implements SqlSource { private final SqlSource sqlSource; public RawSqlSource (Configuration configuration, SqlNode rootSqlNode, Class<?> parameterType) { this (configuration, getSql(configuration, rootSqlNode), parameterType); } public RawSqlSource (Configuration configuration, String sql, Class<?> parameterType) { 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) { return sqlSource.getBoundSql(parameterObject); } }
适用于仅使用 #{}
表达式,或者不使用任何表达式的情况,所以它是静态 的,仅需要在构造方法中,直接生成对应的 SQL 。
在构造方法中:
<1>
处,调用 #getSql(Configuration configuration, SqlNode rootSqlNode)
方法,获得 SQL 。
<2>
处,创建 SqlSourceBuilder 对象,并执行 SqlSourceBuilder#parse(String originalSql, Class<?> parameterType, Map<String, Object> 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 构造方法 private final Configuration configuration;private final SqlSourceBuilder sqlSourceParser;private final Class<?> providerType;private Method providerMethod;private String[] providerMethodArgumentNames;private Class<?>[] providerMethodParameterTypes;private ProviderContext providerContext;private Integer providerContextIndex;@Deprecated public ProviderSqlSource (Configuration configuration, Object provider) { this (configuration, provider, null , null ); } public ProviderSqlSource (Configuration configuration, Object provider, Class<?> mapperType, Method mapperMethod) { String providerMethodName; try { this .configuration = configuration; this .sqlSourceParser = new SqlSourceBuilder(configuration); this .providerType = (Class<?>) provider.getClass().getMethod("type" ).invoke(provider); 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() + "'." ); } 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 @Override public BoundSql getBoundSql (Object parameterObject) { SqlSource sqlSource = createSqlSource(parameterObject); return sqlSource.getBoundSql(parameterObject); }
<1>
处,调用 #createSqlSource(Object parameterObject)
方法,创建 SqlSource 对象。因为它是通过 @ProviderXXX
注解的指定类的指定方法,动态生成 SQL 。所以,从思路上,和 DynamicSqlSource 是有点接近的。详细解析,见 「4.4.3 createSqlSource」 。
<2>
处,调用 SqlSource#getBoundSql(Object parameterObject)
方法,获得 BoundSql 对象。
4.4.3 createSqlSource #createSqlSource(Object parameterObject)
方法,创建 SqlSource 对象。代码如下:
private SqlSource createSqlSource (Object parameterObject) { try { 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)); } else if (parameterObject instanceof Map) { @SuppressWarnings ("unchecked" ) Map<String, Object> params = (Map<String, Object>) 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." ); } Class<?> parameterType = parameterObject == null ? Object.class : parameterObject.getClass(); 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.2>` 处,调用 `#extractProviderMethodArguments(Map<String, Object> params, String[] argumentNames)` 方法,获得方法参数。代码如下:
private Object[] extractProviderMethodArguments(Map<String, Object> 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 。代码如下:
private 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 ; }
* 反射调用方法。
<4>
处,调用 SqlSourceBuilder#parse(String originalSql, Class<?> parameterType, Map<String, Object> additionalParameters)
方法,解析出 SqlSource 对象。
代码比较长,胖友回过头自己再细看。😈 不过一般来说,MyBatis 注解使用较少,所以胖友也可以不用细看。
4.4.4 ProviderContext org.apache.ibatis.builder.annotation.ProviderContext
,ProviderSqlSource 的上下文。代码如下:
public final class ProviderContext { private final Class<?> mapperType; private final Method mapperMethod; 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 封装。代码如下:
public class BoundSql { private final String sql; private final List<ParameterMapping> parameterMappings; private final Object parameterObject; private final Map<String, Object> additionalParameters; private final MetaObject metaParameters; public BoundSql (Configuration configuration, String sql, List<ParameterMapping> 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<ParameterMapping> 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
,参数映射。代码如下:
private Configuration configuration;private String property;private ParameterMode mode;private Class<?> javaType = Object.class;private JdbcType jdbcType;private Integer numericScale;private TypeHandler<?> typeHandler;private String resultMapId;private String jdbcTypeName;private String expression;public static class Builder { }
参数比较简单,胖友自己看看注释。可以忽略 ParameterMode 属性为 OUT
和 INOUT
是在存储过程中使用的情况。
完整的该类,可点击 ParameterMapping 查看。
关于 ParameterMode 属性为 OUT
和 INOUT
是在存储过程中使用的情况,可以看看 《Mybatis调用MySQL存储过程》 。当然,也可以不看,因为很少使用存储过程了。
5.2 ParameterMode org.apache.ibatis.mapping.ParameterMode
,参数类型。代码如下:
public enum ParameterMode { IN, OUT, INOUT }
只需要关注 IN
的情况。
另外,MyBatis 存储过程相关的源码,本系列会直接忽略。嘿嘿。
7. ParameterHandler org.apache.ibatis.executor.parameter.ParameterHandler
,参数处理器接口。代码如下:
public interface ParameterHandler { Object getParameterObject () ; void setParameters (PreparedStatement ps) throws SQLException ; }
7.1 DefaultParameterHandler org.apache.ibatis.scripting.default.DefaultParameterHandler
,实现 ParameterHandler 接口,默认 ParameterHandler 实现类。
7.1.1 构造方法 private final TypeHandlerRegistry typeHandlerRegistry;private final MappedStatement mappedStatement;private final Object parameterObject;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)
方法,代码如下:
@Override public void setParameters (PreparedStatement ps) { ErrorContext.instance().activity("setting parameters" ).object(mappedStatement.getParameterMap().getId()); List<ParameterMapping> parameterMappings = boundSql.getParameterMappings(); if (parameterMappings != null ) { for (int i = 0 ; i < parameterMappings.size(); i++) { ParameterMapping parameterMapping = parameterMappings.get(i); if (parameterMapping.getMode() != ParameterMode.OUT) { Object value; String propertyName = parameterMapping.getProperty(); if (boundSql.hasAdditionalParameter(propertyName)) { 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); } TypeHandler typeHandler = parameterMapping.getTypeHandler(); JdbcType jdbcType = parameterMapping.getJdbcType(); if (value == null && jdbcType == null ) { jdbcType = configuration.getJdbcTypeForNull(); } 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,刚开始写的有点懵逼。现在清晰多了。哈哈哈哈。
参考和推荐如下文章: