本文已参与「新人创作礼」活动,一起开启掘金创作之路。
本章主要记录了mybatis参数处理源码分析
1.前因
mybatis源码也分析过了,插件也分析完了,但是在分析插件的过程中产生一个疑问?
找到源码MySqlDialect类
@Override
public Object processPageParameter(MappedStatement ms, Map<String, Object> paramMap, Page page, BoundSql boundSql, CacheKey pageKey) {
//参数列表
paramMap.put(PAGEPARAMETER_FIRST, page.getStartRow());
paramMap.put(PAGEPARAMETER_SECOND, page.getPageSize());
//处理pageKey
pageKey.update(page.getStartRow());
pageKey.update(page.getPageSize());
//处理参数配置
if (boundSql.getParameterMappings() != null) {
List<ParameterMapping> newParameterMappings = new ArrayList<ParameterMapping>(boundSql.getParameterMappings());
if (page.getStartRow() == 0) {
//在原有的mappinglist中添加key名
newParameterMappings.add(new ParameterMapping.Builder(ms.getConfiguration(), PAGEPARAMETER_SECOND, int.class).build());
} else {
newParameterMappings.add(new ParameterMapping.Builder(ms.getConfiguration(), PAGEPARAMETER_FIRST, long.class).build());
newParameterMappings.add(new ParameterMapping.Builder(ms.getConfiguration(), PAGEPARAMETER_SECOND, int.class).build());
}
MetaObject metaObject = MetaObjectUtil.forObject(boundSql);
//更新
metaObject.setValue("parameterMappings", newParameterMappings);
}
return paramMap;
}
为什么在参数中添加了两个值,在parameterMappings只添加了一个也可以执行成功,这中间是怎么实现的呢?
2.源码分析
2.1 MappedStatement#getBoundSql
//从这里开始 CachingExecutor#query
@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
//处理sql和参数
BoundSql boundSql = ms.getBoundSql(parameterObject);
CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
//MappedStatement#getBoundSql
BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
sqlSource是个接口有四个实现类,要看走哪个实现类,还得再回顾一下xml解析流程
2.2 SqlSource实现类分析流程
- 回顾一下sql xml文件的解析过程
MybatisAutoConfiguration入口类
初始bean sqlSessionFactory调用getObject() -> SqlSessionFactoryBean#buildSqlSessionFactory()-> XMLMapperBuilder#parse ->configurationElement ->buildStatementFromContext XMLStatementBuilder#parseStatementNode(sqlSource在此初始化) -> addMappedStatement 生成一个MappedStatement 即前面调用的ms对象
- 直接找到
XMLStatementBuilder#parseStatementNode
SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
//这里是xml解析 最后到XMLScriptBuilder#parseScriptNode
public SqlSource parseScriptNode() {
MixedSqlNode rootSqlNode = parseDynamicTags(context);
SqlSource sqlSource;
//取决于parseDynamicTags ##
if (isDynamic) {
sqlSource = new DynamicSqlSource(configuration, rootSqlNode);
} else {
sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType);
}
return sqlSource;
}
//parseDynamicTags 省略部分代码
if (child.getNode().getNodeType() == Node.CDATA_SECTION_NODE || child.getNode().getNodeType() == Node.TEXT_NODE) {
String data = child.getStringBody("");
TextSqlNode textSqlNode = new TextSqlNode(data);
//这个为真 isDynamic赋值为true
if (textSqlNode.isDynamic()) {
contents.add(textSqlNode);
isDynamic = true;
} else {
contents.add(new StaticTextSqlNode(data));
}
}
TextSqlNode#isDynamic
public boolean isDynamic() {
DynamicCheckerTokenParser checker = new DynamicCheckerTokenParser();
//创建一个解析器 传入参数为DynamicCheckerTokenParser
GenericTokenParser parser = createParser(checker);
//解析 ###
parser.parse(text);
return checker.isDynamic();
}
private GenericTokenParser createParser(TokenHandler handler) {
//解析${xxx}参数
return new GenericTokenParser("${", "}", handler);
}
2.3 ${xxx}参数解析
GenericTokenParser类
public GenericTokenParser(String openToken, String closeToken, TokenHandler handler) {
this.openToken = openToken;
this.closeToken = closeToken;
this.handler = handler;
}
//后面有详细分析 这里只贴这几行
public String parse(String text) {
//根据下标将符号包裹的字段取出来
expression.append(src, offset, end - offset - 1).append(closeToken);
//在这里处理 ###
builder.append(handler.handleToken(expression.toString()));
}
- 前面传入的handler是
DynamicCheckerTokenParser
//就一个修改为true的操作
@Override
public String handleToken(String content) {
this.isDynamic = true;
return null;
}
- 最后在看下
XMLScriptBuilder#parseDynamicTags中的else
nodeHandlerMap.put("trim", new TrimHandler());
nodeHandlerMap.put("where", new WhereHandler());
nodeHandlerMap.put("set", new SetHandler());
nodeHandlerMap.put("foreach", new ForEachHandler());
nodeHandlerMap.put("if", new IfHandler());
nodeHandlerMap.put("choose", new ChooseHandler());
nodeHandlerMap.put("when", new IfHandler());
nodeHandlerMap.put("otherwise", new OtherwiseHandler());
nodeHandlerMap.put("bind", new BindHandler());
else if (child.getNode().getNodeType() == Node.ELEMENT_NODE) { // issue #628
String nodeName = child.getNode().getNodeName();
NodeHandler handler = nodeHandlerMap.get(nodeName);
if (handler == null) {
throw new BuilderException("Unknown element <" + nodeName + "> in SQL statement.");
}
//从上述的map来分析 只有有动态的关键字 都会走到这里面来
handler.handleNode(child, contents);
//那就返回的是true
isDynamic = true;
}
到这里我们我们基本可以判断出了,只要需要动态的已经就是这个sqlSource
sqlSource = new DynamicSqlSource(configuration, rootSqlNode);
DynamicSqlSource#getBoundSql
@Override
public BoundSql getBoundSql(Object parameterObject) {
//构造方法
DynamicContext context = new DynamicContext(configuration, parameterObject);
//这里应该就是 MixedSqlNode ${}符号的解析 ###
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);
context.getBindings().forEach(boundSql::setAdditionalParameter);
return boundSql;
}
//DynamicContext
public static final String PARAMETER_OBJECT_KEY = "_parameter";
public static final String DATABASE_ID_KEY = "_databaseId";
public DynamicContext(Configuration configuration, Object parameterObject) {
if (parameterObject != null && !(parameterObject instanceof Map)) {
MetaObject metaObject = configuration.newMetaObject(parameterObject);
boolean existsTypeHandler = configuration.getTypeHandlerRegistry().hasTypeHandler(parameterObject.getClass());
bindings = new ContextMap(metaObject, existsTypeHandler);
} else {
bindings = new ContextMap(null, false);
}
//将参数保存到这个map中 key 为_parameter
bindings.put(PARAMETER_OBJECT_KEY, parameterObject);
bindings.put(DATABASE_ID_KEY, configuration.getDatabaseId());
}
MixedSqlNode#apply
@Override
public boolean apply(DynamicContext context) {
//从前面源码得知到TextSqlNode#apply
contents.forEach(node -> node.apply(context));
return true;
}
@Override
public boolean apply(DynamicContext context) {
//这个handler是BindingTokenParser 解析器传值${}
GenericTokenParser parser = createParser(new BindingTokenParser(context, injectionFilter));
//解析 ###
context.appendSql(parser.parse(text));
return true;
}
private GenericTokenParser createParser(TokenHandler handler) {
return new GenericTokenParser("${", "}", handler);
}
根据字符下标取出对应的参数值的代码就不贴了,直接到
//对应符号包含的参数key
builder.append(handler.handleToken(expression.toString()));
BindingTokenParser#handleToken该类是TextSqlNode的内部类
@Override
public String handleToken(String content) { //content传进来的是参数key
Object parameter = context.getBindings().get("_parameter");
if (parameter == null) {
context.getBindings().put("value", null);
} else if (SimpleTypeRegistry.isSimpleType(parameter.getClass())) {
context.getBindings().put("value", parameter);
}
//取值
Object value = OgnlCache.getValue(content, context.getBindings());
String srtValue = value == null ? "" : String.valueOf(value); // issue #274 return "" instead of "null"
checkInjection(srtValue);
//直接返回
return srtValue;
}
这也解释了${}符号的参数从日志打印中来看是直接替换了的,并不是使用的占位符
2.4 #{xxx}参数解析
- 接上面源码继续分析
SqlSourceBuilder#parse
public SqlSource parse(String originalSql, Class<?> parameterType, Map<String, Object> additionalParameters) {
ParameterMappingTokenHandler handler = new ParameterMappingTokenHandler(configuration, parameterType, additionalParameters);
//注意这里的handler
GenericTokenParser parser = new GenericTokenParser("#{", "}", handler);
String sql;
if (configuration.isShrinkWhitespacesInSql()) {
sql = parser.parse(removeExtraWhitespaces(originalSql));
} else {
sql = parser.parse(originalSql);
}
return new StaticSqlSource(configuration, sql, handler.getParameterMappings());
}
- parse代码就不贴了,直接走handler的处理吧
SqlSourceBuilder#ParameterMappingTokenHandler#handleToken
public String handleToken(String content) {
//添加到参数数组 && 处理数据类型
parameterMappings.add(buildParameterMapping(content));
//用 ? 占位
return "?";
}
2.5 sql赋值
到这里为止,使用${}符号的参数已经直接替换进去了,但是#{}的参数还是?占位的,接下来分析下什么时候赋值的
SimpleExecutor#doQuery
stmt = prepareStatement(handler, ms.getStatementLog());
//prepareStatement
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
//连接数据库 后面又篇专门的分析
Connection connection = getConnection(statementLog);
stmt = handler.prepare(connection, transaction.getTimeout());
//处理参数值 ###
handler.parameterize(stmt);
return stmt;
}
//PreparedStatementHandler#parameterize
@Override
public void parameterize(Statement statement) throws SQLException {
//可被插件代理 分析直接走默认实现了
parameterHandler.setParameters((PreparedStatement) statement);
}
DefaultParameterHandler#setParameters
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);
ParameterMapping parameterMapping = parameterMappings.get(i);
if (parameterMapping.getMode() != ParameterMode.OUT) {
Object value;
//······ 省略部分代码
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);
}
}
}
}
}
2.6 TypeAliasRegistry映射类型
private final Map<String, Class<?>> typeAliases = new HashMap<>();
public TypeAliasRegistry() {
registerAlias("string", String.class);
registerAlias("byte", Byte.class);
registerAlias("long", Long.class);
registerAlias("short", Short.class);
registerAlias("int", Integer.class);
registerAlias("integer", Integer.class);
registerAlias("double", Double.class);
registerAlias("float", Float.class);
registerAlias("boolean", Boolean.class);
registerAlias("byte[]", Byte[].class);
registerAlias("long[]", Long[].class);
registerAlias("short[]", Short[].class);
registerAlias("int[]", Integer[].class);
registerAlias("integer[]", Integer[].class);
registerAlias("double[]", Double[].class);
registerAlias("float[]", Float[].class);
registerAlias("boolean[]", Boolean[].class);
registerAlias("_byte", byte.class);
registerAlias("_long", long.class);
registerAlias("_short", short.class);
registerAlias("_int", int.class);
registerAlias("_integer", int.class);
registerAlias("_double", double.class);
registerAlias("_float", float.class);
registerAlias("_boolean", boolean.class);
registerAlias("_byte[]", byte[].class);
registerAlias("_long[]", long[].class);
registerAlias("_short[]", short[].class);
registerAlias("_int[]", int[].class);
registerAlias("_integer[]", int[].class);
registerAlias("_double[]", double[].class);
registerAlias("_float[]", float[].class);
registerAlias("_boolean[]", boolean[].class);
registerAlias("date", Date.class);
registerAlias("decimal", BigDecimal.class);
registerAlias("bigdecimal", BigDecimal.class);
registerAlias("biginteger", BigInteger.class);
registerAlias("object", Object.class);
registerAlias("date[]", Date[].class);
registerAlias("decimal[]", BigDecimal[].class);
registerAlias("bigdecimal[]", BigDecimal[].class);
registerAlias("biginteger[]", BigInteger[].class);
registerAlias("object[]", Object[].class);
registerAlias("map", Map.class);
registerAlias("hashmap", HashMap.class);
registerAlias("list", List.class);
registerAlias("arraylist", ArrayList.class);
registerAlias("collection", Collection.class);
registerAlias("iterator", Iterator.class);
registerAlias("ResultSet", ResultSet.class);
}
//resolveAlias
value = (Class<T>) typeAliases.get(key);
好了,整个分析的过程应该是完结了。 以上就是本章的全部内容了。
上一篇:mybatis第七话 - mybatis插件篇之pagehelper的源码分析 下一篇:mybatis第九话 - 手写实现一个简单的mybatis版本
勿以恶小而为之,勿以善小而不为