Mybatis获取SQL(1)-BoundSql中获取SQL

Mybatis获取SQL(1)-BoundSql中获取SQL1.

大家好,欢迎来到IT知识分享网。

1.SimpleStatementHandler获取sql语句

Mybatis中SqlSession执行Sql语句过程中,实际上是通过Executor来执行Sql

Executor执行Sql的方法中,又是通过StatementHandler来实现

Mybatis获取SQL(1)-BoundSql中获取SQL

Mybatis获取SQL(1)-BoundSql中获取SQL

StatementHandler在执行数据库语句时,首先会通过BoundSql获得sql语句的字符串对象,然后通过Statement对象执行sql操作,最后通过ResultSetHandler获取返回值

此文主要解析StatementHandler中sql语句的获取过程

public class SimpleStatementHandler extends BaseStatementHandler { public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException { String sql = this.boundSql.getSql(); statement.execute(sql); return this.resultSetHandler.handleResultSets(statement); } ... }

2.BoundSql中获取sql

从SimpleStatementHandler的query方法中了解到:sql语句是通过BoundSql属性的getSql方法获取

BoundSql类中有一个sql的字符串对象,该值在构造方法中进行赋值

public class BoundSql { private final String sql; ... public BoundSql(Configuration configuration, String sql, List<ParameterMapping> parameterMappings, Object parameterObject) { this.sql = sql; ... } public String getSql() { return this.sql; } }

继续查看BoundSql的sql属性在什么时候进行赋值,BoundSql是BaseStatementHandler类中的属性,在BaseStatementHandler的构造方法中传入boundSql参数进行赋值,但如果传入的BoundSql参数为空的话就从MappedStatement对象中通过getBoundSql方法获取

public abstract class BaseStatementHandler implements StatementHandler { protected BoundSql boundSql; protected BaseStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) { this.mappedStatement = mappedStatement; if (boundSql == null) { this.generateKeys(parameterObject); boundSql = mappedStatement.getBoundSql(parameterObject); } this.boundSql = boundSql; ... } ... }

BoundSql参数是在BaseExecutor的query方法中也是通过MappedStatement获取的

public abstract class BaseExecutor implements Executor { public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException { BoundSql boundSql = ms.getBoundSql(parameter); CacheKey key = this.createCacheKey(ms, parameter, rowBounds, boundSql); return this.query(ms, parameter, rowBounds, resultHandler, key, boundSql); } ... }

MappedStatement的getBoundSql方法中,通过SqlSource的getBoundSql方法获取BoundSql。如果方法中传入的parameterObject为空,则从parameterMap中获取ParameterMappings进行替换,整个过程中sql属性的值不变,还是从sqlSource中得到的

public final class MappedStatement { public BoundSql getBoundSql(Object parameterObject) { BoundSql boundSql = this.sqlSource.getBoundSql(parameterObject); List<ParameterMapping> parameterMappings = boundSql.getParameterMappings(); if (parameterMappings == null || parameterMappings.isEmpty()) { boundSql = new BoundSql(this.configuration, boundSql.getSql(), this.parameterMap.getParameterMappings(), parameterObject); } Iterator var4 = boundSql.getParameterMappings().iterator(); while(var4.hasNext()) { ParameterMapping pm = (ParameterMapping)var4.next(); String rmId = pm.getResultMapId(); if (rmId != null) { ResultMap rm = this.configuration.getResultMap(rmId); if (rm != null) { this.hasNestedResultMaps |= rm.hasNestedResultMaps(); } } } return boundSql; } ... }

3.SqlSource获取BoundSql

从上述的解析中可以知道BoundSql中的sql属性是从SqlSource中得到的,继续查看SqlSource中获取BoundSql以及给sql属性赋值的情况

SqlSource是一个接口,只定义了一个getBoundSql方法来获取BoundSql对象

public interface SqlSource { BoundSql getBoundSql(Object var1); }

MappedStatement中的SqlSource属性是在MapperBuilderAssistant的addMappedStatement方法中创建MappedStatement对象时进行赋值的

Mybatis获取SQL(1)-BoundSql中获取SQL

而MapperBuilderAssistant的addMappedStatement方法在XMLStatementBuilder的parseStatementNode方法中解析Statement节点后调用

parseStatementNode方法中可以看到SqlSource的初始化过程,是通过langDriver的createSqlSource方法得到的

首先会根据Statement配置中lang的值得到LanguageDriver,然后通过createSqlSource方法创建SqlSource对象,保存到MappedStatement中

public class XMLStatementBuilder extends BaseBuilder { public void parseStatementNode() { String lang = this.context.getStringAttribute("lang"); LanguageDriver langDriver = this.getLanguageDriver(lang); SqlSource sqlSource = langDriver.createSqlSource(this.configuration, this.context, parameterTypeClass); ... this.builderAssistant.addMappedStatement(id, sqlSource, ...); } ... }

LanguageDriver通过XMLStatementBuilder的getLanguageDriver方法获取,传入的是配置文件中的lang属性,此处以默认对象XMLLanguageDriver进行解析

public class XMLStatementBuilder extends BaseBuilder { private LanguageDriver getLanguageDriver(String lang) { Class<? extends LanguageDriver> langClass = null; if (lang != null) { langClass = this.resolveClass(lang); } return this.configuration.getLanguageDriver(langClass); } ... }

XMLLanguageDriver的createSqlSource方法中,会创建XMLScriptBuilder对象,然后通过parseScriptNode方法获取SqlSource

public class XMLLanguageDriver implements LanguageDriver { public SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType) { XMLScriptBuilder builder = new XMLScriptBuilder(configuration, script, parameterType); return builder.parseScriptNode(); } ... }

4.XMLScriptBuilder创建SqlSource

XMLScriptBuilder也是继承BaseBuilder类,在构造方法中对属性进行赋值

public class XMLScriptBuilder extends BaseBuilder { private final XNode context; private boolean isDynamic; private final Class<?> parameterType; private final Map<String, XMLScriptBuilder.NodeHandler> nodeHandlerMap; public XMLScriptBuilder(Configuration configuration, XNode context, Class<?> parameterType) { super(configuration); this.nodeHandlerMap = new HashMap(); this.context = context; this.parameterType = parameterType; this.initNodeHandlerMap(); } ... }

parseScriptNode方法中,会根据isDynamic属性来判断SqlSource实例化的类型

如果是则创建DynamicSqlSource的实例,否则创建RawSqlSource实例

public class XMLScriptBuilder extends BaseBuilder { public SqlSource parseScriptNode() { MixedSqlNode rootSqlNode = this.parseDynamicTags(this.context); Object sqlSource; if (this.isDynamic) { sqlSource = new DynamicSqlSource(this.configuration, rootSqlNode); } else { sqlSource = new RawSqlSource(this.configuration, rootSqlNode, this.parameterType); } return (SqlSource)sqlSource; } ... }

在parseScriptNode方法中调用parseDynamicTags方法是会对XNode进行解析,如果有trim、where、set等动态sql的节点,isDynamic为真,创建的是DynamicSqlSource的实例,否则创建RawSqlSource的实例

Mybatis获取SQL(1)-BoundSql中获取SQL

Mybatis获取SQL(1)-BoundSql中获取SQL

DynamicSqlSource的getBoundSql方法中通过SqlSourceBuilder的parse创建一个SqlSource对象,然后从新建的对象中获得BoundSql

RawSqlSource是一个装饰类,它装饰SqlSource对象也是通过SqlSourceBuilder的parse方法创建的

Mybatis获取SQL(1)-BoundSql中获取SQL

Mybatis获取SQL(1)-BoundSql中获取SQL

5.SqlSourceBuilder创建SqlSource

XMLScriptBuilder会创建DynamicSqlSource和RawSqlSource,但是这两个类都是直接或间接的封装了一个SqlSource,这个SqlSource是通过SqlSourceBuilder来创建的

SqlSourceBuilder继承于BaseBuilder类,构造方法中直接调用父类的构造方法

public class SqlSourceBuilder extends BaseBuilder { private static final String PARAMETER_PROPERTIES = "javaType,jdbcType,mode,numericScale,resultMap,typeHandler,jdbcTypeName"; public SqlSourceBuilder(Configuration configuration) { super(configuration); } }

在父类构造方法中对属性进行赋值,主要对参数Configuration进行赋值

Mybatis获取SQL(1)-BoundSql中获取SQL

SqlSourceBuilder的parse方法中,传入originalSql、paramaterType、additionalParameters参数,返回SqlSource对象

public class SqlSourceBuilder extends BaseBuilder { public SqlSource parse(String originalSql, Class<?> parameterType, Map<String, Object> additionalParameters) { SqlSourceBuilder.ParameterMappingTokenHandler handler = new SqlSourceBuilder.ParameterMappingTokenHandler(this.configuration, parameterType, additionalParameters); GenericTokenParser parser = new GenericTokenParser("#{", "}", handler); String sql; if (this.configuration.isShrinkWhitespacesInSql()) { sql = parser.parse(removeExtraWhitespaces(originalSql)); } else { sql = parser.parse(originalSql); } return new StaticSqlSource(this.configuration, sql, handler.getParameterMappings()); } ... }

5.1.创建ParameterMappingTokenHandler对象

ParameterMappingTokenHandler是SqlSourceBuilder的静态内部类,继承BaseBuilder并实现了TokenHandler接口

public class SqlSourceBuilder extends BaseBuilder { ... private static class ParameterMappingTokenHandler extends BaseBuilder implements TokenHandler { 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); } ... } }

TokenHandler中主要有一个handleToken方法

Mybatis获取SQL(1)-BoundSql中获取SQL

5.2.创建GeneticTokenParser对象

GenericTokenParser构造方法中对属性进行进行赋值,传入的参数是”#{“、”}”和之前创建的ParameterMappingTokenHandler实例handler

public class GenericTokenParser { private final String openToken; private final String closeToken; private final TokenHandler handler; public GenericTokenParser(String openToken, String closeToken, TokenHandler handler) { this.openToken = openToken; this.closeToken = closeToken; this.handler = handler; } ... }

5.3.ShrinkWhitespacesInSql处理

GenericTokenParser处理之前会判断通过Configuration的shrinkWhitespacesInSql值来确定是否要调用removeExtraWhitespaces方法进行处理

这个值在Mybatis的setting属性中配置,默认为false

public class XMLConfigBuilder extends BaseBuilder { private void settingsElement(Properties props) { this.configuration.setShrinkWhitespacesInSql(this.booleanValueOf(props.getProperty("shrinkWhitespacesInSql"), false)); ... } ... }

如果shrinkWhitespacesInSql为true,在removeExtraWhitespaces方法中,调用StringTokenizer对字符串进行处理

public class SqlSourceBuilder extends BaseBuilder { public static String removeExtraWhitespaces(String original) { StringTokenizer tokenizer = new StringTokenizer(original); StringBuilder builder = new StringBuilder(); boolean hasMoreTokens = tokenizer.hasMoreTokens(); while(hasMoreTokens) { builder.append(tokenizer.nextToken()); hasMoreTokens = tokenizer.hasMoreTokens(); if (hasMoreTokens) { builder.append(' '); } } return builder.toString(); } ... }

5.4.调用GeneticTokenParser的parse方法

GenericTokenParser的parse方法中,会使用ParameterMappingTokenHandler的handleToken方法来处理带有#{}的字符串

public class GenericTokenParser { public String parse(String text) { if (text != null && !text.isEmpty()) { //如果字符串中没有#{字符,则返回原字符串 int start = text.indexOf(this.openToken); if (start == -1) { return text; } else { char[] src = text.toCharArray(); int offset = 0; StringBuilder builder = new StringBuilder(); StringBuilder expression = null; do { //如果#{前面有转义字符\则去掉,此处的#{不做处理 if (start > 0 && src[start - 1] == '\\') { builder.append(src, offset, start - offset - 1).append(this.openToken); offset = start + this.openToken.length(); } else { if (expression == null) { expression = new StringBuilder(); } else { expression.setLength(0); } builder.append(src, offset, start - offset); offset = start + this.openToken.length(); int end; //查找最近的}字符 for(end = text.indexOf(this.closeToken, offset); end > -1; end = text.indexOf(this.closeToken, offset)) { if (end <= offset || src[end - 1] != '\\') { expression.append(src, offset, end - offset); break; } //expression中方法{ 到 } 的字符 expression.append(src, offset, end - offset - 1).append(this.closeToken); offset = end + this.closeToken.length(); } if (end == -1) { builder.append(src, start, src.length - start); offset = src.length; } else { //使用handler处理expression之后放入到builder中 builder.append(this.handler.handleToken(expression.toString())); offset = end + this.closeToken.length(); } } start = text.indexOf(this.openToken, offset); } while(start > -1); if (offset < src.length) { builder.append(src, offset, src.length - offset); } return builder.toString(); } } else { return ""; } } }

5.5.ParameterMappingHandler处理sql

ParameterMappingTokenHandler的handleToken方法中,通过buildParameterMapping方法处理传入的字符串生成ParameterMapping对象,然后添加到parameterMappings中

而方法直接返回?字符串,也就是说会将sql中的#{}用?进行替换,而括号中的字符会处理后添加到parameterMappings中

 private static class ParameterMappingTokenHandler extends BaseBuilder implements TokenHandler { public String handleToken(String content) { this.parameterMappings.add(this.buildParameterMapping(content)); return "?"; } ... }

builParameterMapping方法中,首先获取propertyType,得到propertyType之后获得对应的类对象,然后创建Builder对象,并通过对应的方法来获得ParameterMapping

6.创建StaticSqlSource

最后创建SqlSource对象,构造方法中对sql,parameterMappings和Configuration属性进行赋值

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, (List)null); } public StaticSqlSource(Configuration configuration, String sql, List<ParameterMapping> parameterMappings) { this.sql = sql; this.parameterMappings = parameterMappings; this.configuration = configuration; } public BoundSql getBoundSql(Object parameterObject) { return new BoundSql(this.configuration, this.sql, this.parameterMappings, parameterObject); } }

7.总结

StatementHandler在执行数据库语句时,SQL语句保存在BoundSql中,而BoundSql通过SqlSource得到。

SqlSource首先会通过XMLScriptBuilder来创建,这里会对动态SQL进行处理

然后通过SqlSourceBuilder来创建,BoundSql中sql语句的生成过程在SqlSourceBuilder创建SqlSource的过程中实现

SqlSourceBuilder创建SqlSource时,会查找sql中“#{}”字符串,如有则在sql中替换为“?”,并且根据括号里面的参数创建ParameterMappings对象

免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://yundeesoft.com/51294.html

(0)

相关推荐

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注

关注微信