初始化(四)之SQL初始化(下)
在上一篇文档中详细地讲述了MyBatis在解析
主要包路径:org.apache.ibatis.mapping、org.apache.ibatis.builder
主要涉及到的类:
org.apache.ibatis.builder.SqlSourceBuilder:继承了BaseBuilder抽象类,SqlSource构建器,负责将SQL语句中的#{}替换成相应的?占位符,并获取该?占位符对应的 ParameterMapping对象
org.apache.ibatis.builder.ParameterExpression:继承了HashMap
org.apache.ibatis.mapping.ParameterMapping:保存#{}中配置的属性参数信息
org.apache.ibatis.mapping.SqlSource:SQL 资源接口,用于创建BoundSql对象(包含可执行的SQL语句与参数信息)
org.apache.ibatis.mapping.BoundSql:用于数据库可执行的SQL语句的最终封装对象
org.apache.ibatis.scripting.defaults.DefaultParameterHandler:实现了ParameterHandler接口,用于将入参设置到java.sql.PreparedStatement预编译对象中
用于将入参设置到java.sql.PreparedStatement预编译对象中
我们先来回顾一下org.apache.ibatis.scripting.xmltags.XMLScriptBuilder的parseScriptNode()方法,将 SQL 脚本(XML或者注解中定义的 SQL )解析成 SqlSource 对象
代码如下:
public SqlSource parseScriptNode() {
// 解析 XML 或者注解中定义的 SQL
MixedSqlNode rootSqlNode = parseDynamicTags(context);
SqlSource sqlSource;
if (isDynamic) {
// 动态语句,使用了 ${} 也算
sqlSource = new DynamicSqlSource(configuration, rootSqlNode);
} else {
sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType);
}
return sqlSource;
}
如果是动态 SQL 语句,使用了 MyBatis 的自定义标签(
否则就是静态 SQL 语句,创建 RawSqlSource 对象
SqlSource接口的实现类如下图所示:
SqlSource
SqlSourceBuilder
org.apache.ibatis.builder.SqlSourceBuilder:继承了BaseBuilder抽象类,SqlSource构建器,负责将SQL语句中的#{}替换成相应的?占位符,并获取该?占位符对应的 org.apache.ibatis.mapping.ParameterMapping 对象
构造方法
public class SqlSourceBuilder extends BaseBuilder {
private static final String PARAMETER_PROPERTIES = "javaType,jdbcType,mode,numericScale,resultMap,typeHandler,jdbcTypeName";
public SqlSourceBuilder(Configuration configuration) {
super(configuration);
}
}
其中PARAMETER_PROPERTIES字符串定义了#{}中支持定义哪些属性,在抛异常的时候用到
parse方法
解析原始的SQL(仅包含#{}定义的参数),转换成StaticSqlSource对象
因为在DynamicSqlSource调用该方法前会将MixedSqlNode进行处理,调用其apply方法进行应用,根据DynamicContext上下文对MyBatis的自定义标签或者包含${}的SQL生成的SqlNode进行逻辑处理或者注入值,生成一个SQL(仅包含#{}定义的参数)
代码如下:
/**
* 执行解析原始 SQL ,成为 SqlSource 对象
*
* @param originalSql 原始 SQL
* @param parameterType 参数类型
* @param additionalParameters 上下文的参数集合,包含附加参数集合(通过
* RawSqlSource传入空集合
* DynamicSqlSource传入 {@link org.apache.ibatis.scripting.xmltags.DynamicContext#bindings} 集合
* @return SqlSource 对象
*/
public SqlSource parse(String originalSql, Class> parameterType, Map
// <1> 创建 ParameterMappingTokenHandler 对象
ParameterMappingTokenHandler handler = new ParameterMappingTokenHandler(configuration, parameterType, additionalParameters);
// <2> 创建 GenericTokenParser 对象
GenericTokenParser parser = new GenericTokenParser("#{", "}", handler);
/*
* <3> 执行解析
* 将我们在 SQL 定义的所有占位符 #{content} 都替换成 ?
* 并生成对应的 ParameterMapping 对象保存在 ParameterMappingTokenHandler 中
*/
String sql = parser.parse(originalSql);
// <4> 创建 StaticSqlSource 对象
return new StaticSqlSource(configuration, sql, handler.getParameterMappings());
}
该方法的入参originalSql为原始的SQL,也就是其所有的SqlNode节点已经应用了,也就是都调用了apply方法
包含的${}也已经注入了对应的值,所以这里只剩#{}定义的入参了
创建ParameterMappingTokenHandler处理器对象handler
创建GenericTokenParser对象,用于处理#{}中的内容,通过handler将其转换成?占位符,并创建对应的ParameterMapping对象
执行解析,获取最终的 SQL 语句
创建StaticSqlSource对象
ParameterMappingTokenHandler
org.apache.ibatis.builder.SqlSourceBuilder的内部类,用于解析#{}的内容,创建ParameterMapping对象,并将其替换成?占位符
代码如下:
private static class ParameterMappingTokenHandler extends BaseBuilder implements TokenHandler {
/**
* 我们在 SQL 语句中定义的占位符对应的 ParameterMapping 数组,根据顺序来的
*/
private List
/**
* 参数类型
*/
private Class> parameterType;
/**
* additionalParameters 参数的对应的 MetaObject 对象
*/
private MetaObject metaParameters;
public ParameterMappingTokenHandler(Configuration configuration, Class> parameterType, Map
super(configuration);
this.parameterType = parameterType;
// 创建 additionalParameters 参数的对应的 MetaObject 对象
this.metaParameters = configuration.newMetaObject(additionalParameters);
}
public List
return parameterMappings;
}
@Override
public String handleToken(String content) {
// <1> 构建 ParameterMapping 对象,并添加到 parameterMappings 中
parameterMappings.add(buildParameterMapping(content));
// <2> 返回 ? 占位符
return "?";
}
/**
* 根据内容构建一个 ParameterMapping 对象
*
* @param content 我们在 SQL 语句中定义的占位符
* @return ParameterMapping 对象
*/
private ParameterMapping buildParameterMapping(String content) {
// <1> 将字符串解析成 key-value 键值对保存
// 其中有一个key为"property",value就是对应的属性名称
Map
// <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)) { // 有对应的类型处理器,例如java.lang.string
propertyType = parameterType;
} else if (JdbcType.CURSOR.name().equals(propertiesMap.get("jdbcType"))) { // 设置的 Jdbc Type 是游标
propertyType = java.sql.ResultSet.class;
} else if (property == null || Map.class.isAssignableFrom(parameterType)) { // 是 Map 集合
propertyType = Object.class;
} else { // 类对象
MetaClass metaClass = MetaClass.forClass(parameterType, configuration.getReflectorFactory());
if (metaClass.hasGetter(property)) {
// 通过反射获取到其对应的 Java Type
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;
// 遍历 SQL 配置的占位符信息,例如这样配置:"name = #{name, jdbcType=VARCHAR}"
for (Map.Entry
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 " + PARAMETER_PROPERTIES);
}
}
// <3.2> 如果 TypeHandler 类型处理器的别名非空
if (typeHandlerAlias != null) {
builder.typeHandler(resolveTypeHandler(javaType, typeHandlerAlias));
}
// <3.3> 创建 ParameterMapping 对象
return builder.build();
}
private Map
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);
}
}
}
构造方法:创建additionalParameters对应的MetaObject对象,便于操作上下文的参数集合,包含附加参数集合(通过
handleToken(String content)方法:
调用buildParameterMapping(content)方法,解析#{}的内容创建ParameterMapping对象
直接返回?占位符
buildParameterMapping(content)方法:
将字符串解析成 key-value 键值对,通过org.apache.ibatis.builder.ParameterExpression进行解析,其中有一个key为"property",value就是对应的属性名称
获得属性的名字和类型
创建ParameterMapping.Builder构建者对象,设置参数的名称与Java Type
将上面第1步解析到key-value键值对设置到Builder中
如果TypeHandler类型处理器的别名非空,则尝试获取其对应的类型处理器并设置到Builder中
通过Builder创建ParameterMapping对象,如果没有配置TypeHandler类型处理器,则根据参数Java Type和Jdbc Type从TypeHandlerRegistry注册中心获取并赋值到该对象中
ParameterExpression
org.apache.ibatis.builder.ParameterExpression:继承了HashMap
构造方法:
public class ParameterExpression extends HashMap
private static final long serialVersionUID = -2417552199605158680L;
/**
* 从类的注释中可以看出我们可以这样定义占位符
* 1. #{propertyName, javaType=string, jdbcType=VARCHAR}
* 2. #{(expression), javaType=string, jdbcType=VARCHAR}
*
* @param expression 我们定义的占位符表达式
*/
public ParameterExpression(String expression) {
parse(expression);
}
}
在构造函数中调用其parse(String expression)方法
private void parse(String expression) {
// 跳过前面的非法字符(ASCII 小于33),目的是去除空格,还有非法的字符,可以参照 ASCII 字符代码表看看
int p = skipWS(expression, 0);
if (expression.charAt(p) == '(') {
// 属于第二种方式,我在官方没有看到介绍,这里也不做介绍了
expression(expression, p + 1);
} else {
// 将整个字符串转换成 key-value 保存至 Map.Entry
property(expression, p);
}
}
先出去前面的空格或者非法字符,然后调用property(String expression, int left)方法
// #{propertyName, javaType=string, jdbcType=VARCHAR}
private void property(String expression, int left) {
if (left < expression.length()) {
// 获取到逗号或者冒号第一个位置,也就是分隔符
int right = skipUntil(expression, left, ",:");
// 从内容中截取第一个逗号前面的字符串,也上面第 1 种方式的 "name"
put("property", trimmedStr(expression, left, right));
// 解析字符串一个逗号后面的字符串,也就是该属性的相关配置
jdbcTypeOpt(expression, right);
}
}
如果left开始位置小于字符串的长度,那么开始解析
调用skipUntil方法,获取从left开始,或者:第一个位置,也就是分隔符的位置
这里第一次进入的话就会先获取第一个,的位置,那么调用trimmedStr方法截取前面的字符串,也就是属性名称,然后存放一个键值对(key为property,value为属性名称)
调用jdbcTypeOpt(String expression, int p)方法,继续解析后面的字符串,也就是该属性的相关配置
private void jdbcTypeOpt(String expression, int p) {
p = skipWS(expression, p);
if (p < expression.length()) {
if (expression.charAt(p) == ':') { // 属于上面第 2 种方式,不做分析
jdbcType(expression, p + 1);
} else if (expression.charAt(p) == ',') {
// 将第一个 , 后面的字符串解析成 key-value 保存
option(expression, p + 1);
} else {
throw new BuilderException("Parsing error in {" + expression + "} in position " + p);
}
}
}
如果p(第一个,的位置)后面还有字符串
则调用option(String expression, int p)方法将一个,后面的字符串解析成key-value键值对保存
/**
* 将字符串生成转换成key-value的形式
* 例如 expression = "name, jdbcType = VARCHAR, javaType = string" 设置 p = 6
* 这样将会往 Map 中保存两个键值对:"jdbcType"->"VARCHAR" "javaType"->"string"
*
* @param expression 字符串
* @param p 字符串从哪个位置转换
*/
private void option(String expression, int p) {
int left = skipWS(expression, p);
if (left < expression.length()) {
// 获取 = 的位置
int right = skipUntil(expression, left, "=");
// 截取 = 前面的字符串,对应的 key
String name = trimmedStr(expression, left, right);
left = right + 1;
// 获取 , 的位置
right = skipUntil(expression, left, ",");
// 截取 = 到 , 之间的字符串,也就是对应的 value
String value = trimmedStr(expression, left, right);
// 将 key-value 保存
put(name, value);
// 继续遍历后面的字符串
option(expression, right + 1);
}
}
逐步解析,将字符串解析成key-value键值对保存,这里保存的都是属性的相关配置,例如JdbcType配置
ParameterMapping
org.apache.ibatis.mapping.ParameterMapping:保存#{}中配置的属性参数信息,一个普通的实体类,代码如下:
/**
* SQL 语句中 ? 占位符对应的对象
*
* @author Clinton Begin
*/
public class ParameterMapping {
/**
* 全局配置对象
*/
private Configuration configuration;
/**
* 属性名称
*/
private String property;
/**
* 参数模式
*/
private ParameterMode mode;
/**
* 属性的 Java Type
* 一般可以直接通过入参对象知道,但是如果入参是 Map,需要显式指定,以确保使用正确的类型处理器
*/
private Class> javaType = Object.class;
/**
* 属性的 Jdbc Type
*/
private JdbcType jdbcType;
/**
* 对于数值类型,指定小数点后保留的位数
*/
private Integer numericScale;
/**
* 类型处理器
*/
private TypeHandler> typeHandler;
/**
* 如果 {@link mode} 为 OUT 或者 INOUT,且{@link jdbcType} 为 CURSOR(也就是 Oracle 的 REFCURSOR)
* 必须指定一个 resultMap 引用来将结果集 ResultMap 映射到参数的类型上
*/
private String resultMapId;
/**
* Jdbc Type 名称
*/
private String jdbcTypeName;
private String expression;
private ParameterMapping() {
}
}
SqlSource
org.apache.ibatis.mapping.SqlSource:SQL 资源接口,用于创建BoundSql对象(包含可执行的SQL语句与参数信息),代码如下:
/**
* 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.
*
* @author Clinton Begin
*/
public interface SqlSource {
/**
* 根据传入的参数对象,返回 BoundSql 对象
*
* @param parameterObject 参数对象
* @return BoundSql 对象
*/
BoundSql getBoundSql(Object parameterObject);
}
StaticSqlSource
org.apache.ibatis.builder.StaticSqlSource:实现 SqlSource 接口,静态的 SqlSource 实现类,代码如下:
public class StaticSqlSource implements SqlSource {
/**
* 解析后的 SQL 语句,数据库能执行
*/
private final String sql;
/**
* 上面 SQL 语句中占位符对应的 ParameterMapping 参数集合
*/
private final List
/**
* 全局配置对象
*/
private final Configuration configuration;
public StaticSqlSource(Configuration configuration, String sql) {
this(configuration, sql, null);
}
public StaticSqlSource(Configuration configuration, String sql, List
this.sql = sql;
this.parameterMappings = parameterMappings;
this.configuration = configuration;
}
@Override
public BoundSql getBoundSql(Object parameterObject) {
return new BoundSql(configuration, sql, parameterMappings, parameterObject);
}
}
在SqlSourceBuilder构建的SqlSource类型就是StaticSqlSource,用于获取最终的静态 SQL 语句
RawSqlSource
org.apache.ibatis.scripting.defaults.RawSqlSource:实现了SqlSource接口,静态SQL语句对应的SqlSource对象,用于创建静态 SQL 资源,代码如下:
public class RawSqlSource implements SqlSource {
private final SqlSource sqlSource;
public RawSqlSource(Configuration configuration, SqlNode rootSqlNode, Class> parameterType) {
/*
* 因为静态的 SQL 语句可以直接拿来解析,不需要根据入参就可以应用
* 所以调用 getSql 方法获取静态的 SQL 语句
*/
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;
// 通过 SqlSourceBuilder 将这个静态的 SQL 进行转换,变量替换成 ? 占位符,并生成对应的 ParameterMapping 集合
sqlSource = sqlSourceParser.parse(sql, clazz, new HashMap<>());
}
private static String getSql(Configuration configuration, SqlNode rootSqlNode) {
DynamicContext context = new DynamicContext(configuration, null);
// 调用 StaticTextSqlNode 将 SQL 语句拼接起来
rootSqlNode.apply(context);
return context.getSql();
}
@Override
public BoundSql getBoundSql(Object parameterObject) {
return sqlSource.getBoundSql(parameterObject);
}
}
在构造函数中我们可以看到,会先调用getSql方法直接创建SqlSource
因为静态的 SQL 语句,不需要根据入参来进行逻辑上的判断处理,所以这里在构造函数中就先初始化好 SqlSource,后续需要调用Mapper接口执行SQL的时候就减少了一定的时间
getSql方法:
创建一个上下文对象DynamicContext,入参信息为null
调用StaticTextSqlNode的apply方法,将所有的SQL拼接在一起
返回拼接好的SQL语句
构造方法:
创建SqlSourceBuilder构建对象sqlSourceParser
调用sqlSourceParser的parse方法对该SQL语句进行转换,#{}全部替换成?占位符,并创建对应的ParameterMapping对象
第2步返回的StaticSqlSource对象设置到自己的sqlSource属性中
getBoundSql方法:直接通过StaticSqlSource创建BoundSql对象
DynamicSqlSource
org.apache.ibatis.scripting.defaults.DynamicSqlSource:实现了SqlSource接口,动态SQL语句对应的SqlSource对象,用于创建静态 SQL 资源,代码如下:
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> 创建本次解析的动态 SQL 语句的上下文
DynamicContext context = new DynamicContext(configuration, parameterObject);
// <2> 根据上下文应用整个 SqlNode
rootSqlNode.apply(context);
// <3> 创建 SqlSourceBuilder 对象
SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
Class> parameterType = parameterObject == null ? Object.class : parameterObject.getClass();
// <4> 通过 SqlSourceBuilder 将应用后的 SQL 进行转换,变量替换成 ? 占位符,并生成对应的 ParameterMapping 集合
SqlSource sqlSource = sqlSourceParser.parse(context.getSql(), parameterType, context.getBindings());
// <5> 创建 BoundSql 对象
BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
// <6> 添加附加参数到 BoundSql 对象中,因为上一步创建的`BoundSql`对象时候传入的仅是入参信息,没有添加附加参数
context.getBindings().forEach(boundSql::setAdditionalParameter);
return boundSql;
}
}
在构造函数中仅仅是赋值,不像RawSqlSource的构造函数一样直接可创建对应的SqlSource对象,因为动态SQL语句需要根据入参信息,来解析SqlNode节点,所以这里在getBoundSql方法中每次都会创建StaticSqlSource对象
getBoundSql方法:
创建本次解析的动态 SQL 语句的上下文,设置入参信息
根据上下文应用整个 SqlNode,内部包含的所有SqlNode都会被应用,最终解析后的SQL会保存上下文中
创建 SqlSourceBuilder 构建对象sqlSourceParser
调用sqlSourceParser的parse方法对第2步解析后的SQL语句进行转换,#{}全部替换成?占位符,并创建对应的ParameterMapping对象
通过第4步返回的StaticSqlSource对象创建BoundSql对象
添加附加参数到BoundSql对象中,因为上一步创建的BoundSql对象时候传入的仅是入参信息,没有添加附加参数(通过
BoundSql
org.apache.ibatis.mapping.BoundSql:用于数据库可执行的SQL语句的最终封装对象,一个普通的实体类,代码如下:
public class BoundSql {
/**
* SQL 语句
*/
private final String sql;
/**
* 占位符 ? 对应的入参信息
*/
private final List
/**
* 入参对象
*/
private final Object parameterObject;
/**
* 附加参数集合
*/
private final Map
/**
* 附加参数的 MetaObject 对象,便于操作
*/
private final MetaObject metaParameters;
public BoundSql(Configuration configuration, String sql, List
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
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);
}
}
DefaultParameterHandler
org.apache.ibatis.scripting.defaults.DefaultParameterHandler:实现了ParameterHandler接口,默认实现类,仅提供这个实现类,用于将入参设置到java.sql.PreparedStatement预编译对象中
回看到org.apache.ibatis.scripting.xmltags.XMLLanguageDriver语言驱动类中,实现了createParameterHandler方法,返回的参数处理器就是该对象
代码如下:
public class DefaultParameterHandler implements ParameterHandler {
private final TypeHandlerRegistry typeHandlerRegistry;
/**
* MappedStatement 对象
*/
private final MappedStatement mappedStatement;
/**
* 入参
*/
private final Object parameterObject;
/**
* BoundSql 对象,实际的 SQL 语句
*/
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;
}
@Override
public Object getParameterObject() {
return parameterObject;
}
@Override
public void setParameters(PreparedStatement ps) {
ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
// 获取 SQL 的参数信息 ParameterMapping 对象
List
if (parameterMappings != null) {
// 遍历所有参数
for (int i = 0; i < parameterMappings.size(); i++) {
ParameterMapping parameterMapping = parameterMappings.get(i);
/*
* OUT 表示参数仅作为出参,非 OUT 也就是需要作为入参
*/
if (parameterMapping.getMode() != ParameterMode.OUT) {
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) {
// 入参为 null 则该属性也定义为 null
value = null;
} else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
// 有类型处理器,则直接获取入参对象
value = parameterObject;
} else {
// 创建入参对应的 MetaObject 对象并获取该属性的值
MetaObject metaObject = configuration.newMetaObject(parameterObject);
value = metaObject.getValue(propertyName);
}
// 获取定义的参数类型处理器
TypeHandler typeHandler = parameterMapping.getTypeHandler();
// 获取定义的 Jdbc Type
JdbcType jdbcType = parameterMapping.getJdbcType();
if (value == null && jdbcType == null) {
// 如果没有则设置成 'OTHER'
jdbcType = configuration.getJdbcTypeForNull();
}
try {
// 通过定义的 TypeHandler 参数类型处理器将 value 设置到对应的占位符
typeHandler.setParameter(ps, i + 1, value, jdbcType);
} catch (TypeException | SQLException e) {
throw new TypeException(
"Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
}
}
}
}
}
}
往PreparedStatement中设置参数的大致逻辑如下:
获取SQL的参数信息ParameterMapping对象的集合,然后对其遍历
如果参数的模式不为ParameterMode.OUT(默认为ParameterMode.IN),也就是说需要作为入参,那么开始接下来的赋值
获取该参数对应的属性名称,并通过其获取到对应的值
获取到TypeHandler类型处理器(在ParameterMapping构建的时候会创建对应的TypeHandler)
获取到Jdbc Type
通过TypeHandler类型处理器,根据参数位置和Jdbc Type将属性值设置到PreparedStatement中
这样就完成对PreparedStatement的赋值,然后通过它执行SQL语句
总结
在MyBatis初始化的过程中,会将XML映射文件中的
通过SqlSource这个对象根据入参可以获取到对应的BoundSql对象,BoundSql对象中包含了数据库需要执行的SQL语句、ParameterMapping参数信息、入参对象和附加的参数(通过
亚马逊测评 www.yisuping.com