mybatis-sqlsource2

Posted siye1989

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了mybatis-sqlsource2相关的知识,希望对你有一定的参考价值。

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

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" 的结果如下图:技术图片
  • <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 上的每个占位符的值。例如:技术图片
  • 另外,我们在回过头看看 SqlSourceBuilder 类,它创建的也是 StaticSqlSource 对象。

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

  • 如果是动态 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) 方法,设置指定位置的 ? 占位符的参数。

以上是关于mybatis-sqlsource2的主要内容,如果未能解决你的问题,请参考以下文章

2mybatis学习——eclipse中导入mybatis的dtd约束使其配置文件有提示

3.3

Mybatis

优化mybatis

1mybatits+springboot分页

权限设计系统——框架集成