mybatis-sqlsource2

时间:2022-05-19 02:56:15

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 有多个实现类,如下图所示:mybatis-sqlsource2

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);
}

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<String, Object> 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」 。
  • <4> 处,创建 StaticSqlSource 对象。关于 StaticSqlSource 类,详细解析,见 「4.1 StaticSqlSource」 。

3.3 ParameterMappingTokenHandler

ParameterMappingTokenHandler ,实现 TokenHandler 接口,继承 BaseBuilder 抽象类,负责将匹配到的 #{ 和 } 对,替换成相应的 ? 占位符,并获取该 ? 占位符对应的 org.apache.ibatis.mapping.ParameterMapping 对象。

3.3.1 构造方法

ParameterMappingTokenHandler 是 SqlSourceBuilder 的内部私有静态类。

// SqlSourceBuilder.java

/**
* ParameterMapping 数组
*/
private List<ParameterMapping> parameterMappings = new ArrayList<>();
/**
* 参数类型
*/
private Class<?> parameterType;
/**
* additionalParameters 参数的对应的 MetaObject 对象
*/
private MetaObject metaParameters;

public ParameterMappingTokenHandler(Configuration configuration, Class<?> parameterType, Map<String, Object> 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」 。
  • <2> 处,返回 ? 占位符。
  • 如上两个步骤,就是 ParameterMappingTokenHandler 的核心。

3.3.3 buildParameterMapping

#buildParameterMapping(String content) 方法,构建 ParameterMapping 对象。代码如下:

// SqlSourceBuilder.java

private ParameterMapping buildParameterMapping(String content) {
// <1> 解析成 Map 集合
Map<String, String> 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<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)) {
// 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<String, String> 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 类,继承 HashMap 类,负责参数表达式。感兴趣的胖友,可以自己看看。?? 艿艿暂时没细看。
    • 假设 content = "#{age,javaType=int,jdbcType=NUMERIC,typeHandler=MyTypeHandler}" 的结果如下图:mybatis-sqlsource2
  • <2> 处,获得属性的名字和类型。
  • <3> 处,创建 ParameterMapping.Builder 对象。
    • <3.1> 处,初始化 ParameterMapping.Builder 对象的属性。
    • <3.2> 处,如果 typeHandlerAlias 非空,则获得对应的 TypeHandler 对象,并设置到 ParameterMapping.Builder 对象中。
    • <3.3> 处,创建 ParameterMapping 对象。
    • 关于 ParameterMapping 类,胖友可以跳到 「5.1 ParameterMapping」 中看看。

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<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) {
// 创建 BoundSql 对象
return new BoundSql(configuration, sql, parameterMappings, parameterObject);
}

}
  • StaticSqlSource 的静态,是相对于 DynamicSqlSource 和 RawSqlSource 来说呢。实际上,StaticSqlSource.sql 属性,上面还是可能包括 ? 占位符。
  • #getBoundSql((Object parameterObject) 方法,创建 BoundSql 对象。通过 parameterMappings 和 parameterObject 属性,可以设置 sql 上的每个占位符的值。例如:mybatis-sqlsource2
  • 另外,我们在回过头看看 SqlSourceBuilder 类,它创建的也是 StaticSqlSource 对象。

下面,我们来看看下图的两段代码,胖友看看是否发现了什么规律:mybatis-sqlsource2

  • 如果是动态 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<String, Object> 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<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 实现类。代码如下:

// 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<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 构造方法

// 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」 。
  • <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<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.");
}
// <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<String, Object> params, String[] argumentNames)` 方法,获得方法参数。代码如下:
// ProviderSqlSource.java

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 。代码如下:
// ProviderSqlSource.java

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;
}
* 反射调用方法。 
  • <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<String, Object> 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<ParameterMapping> parameterMappings;
/**
* 参数对象
*/
private final Object parameterObject;
/**
* 附加的参数集合
*/
private final Map<String, Object> additionalParameters;
/**
* {@link #additionalParameters} 的 MetaObject 对象
*/
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 ,参数映射。代码如下:

// 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 查看。
  • 关于 ParameterMode 属性为 OUT 和 INOUT 是在存储过程中使用的情况,可以看看 《Mybatis调用MySQL存储过程》 。当然,也可以不看,因为很少使用存储过程了。

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<ParameterMapping> 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> 处,获得 typeHandlerjdbcType 属性。
  • 【重要】<5> 处,调用 TypeHandler#setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) 方法,设置指定位置的 ? 占位符的参数。

相关文章