Mybatis获取SQL(1)-BoundSql中获取SQL
wptr33 2025-01-10 17:38 59 浏览
1.SimpleStatementHandler获取sql语句
Mybatis中SqlSession执行Sql语句过程中,实际上是通过Executor来执行Sql
Executor执行Sql的方法中,又是通过StatementHandler来实现
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对象时进行赋值的
而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的实例
DynamicSqlSource的getBoundSql方法中通过SqlSourceBuilder的parse创建一个SqlSource对象,然后从新建的对象中获得BoundSql
RawSqlSource是一个装饰类,它装饰SqlSource对象也是通过SqlSourceBuilder的parse方法创建的
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进行赋值
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方法
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对象
相关推荐
- oracle数据导入导出_oracle数据导入导出工具
-
关于oracle的数据导入导出,这个功能的使用场景,一般是换服务环境,把原先的oracle数据导入到另外一台oracle数据库,或者导出备份使用。只不过oracle的导入导出命令不好记忆,稍稍有点复杂...
- 继续学习Python中的while true/break语句
-
上次讲到if语句的用法,大家在微信公众号问了小编很多问题,那么小编在这几种解决一下,1.else和elif是子模块,不能单独使用2.一个if语句中可以包括很多个elif语句,但结尾只能有一个else解...
- python continue和break的区别_python中break语句和continue语句的区别
-
python中循环语句经常会使用continue和break,那么这2者的区别是?continue是跳出本次循环,进行下一次循环;break是跳出整个循环;例如:...
- 简单学Python——关键字6——break和continue
-
Python退出循环,有break语句和continue语句两种实现方式。break语句和continue语句的区别:break语句作用是终止循环。continue语句作用是跳出本轮循环,继续下一次循...
- 2-1,0基础学Python之 break退出循环、 continue继续循环 多重循
-
用for循环或者while循环时,如果要在循环体内直接退出循环,可以使用break语句。比如计算1至100的整数和,我们用while来实现:sum=0x=1whileTrue...
- Python 中 break 和 continue 傻傻分不清
-
大家好啊,我是大田。今天分享一下break和continue在代码中的执行效果是什么,进一步区分出二者的区别。一、continue例1:当小明3岁时不打印年龄,其余年龄正常循环打印。可以看...
- python中的流程控制语句:continue、break 和 return使用方法
-
Python中,continue、break和return是控制流程的关键语句,用于在循环或函数中提前退出或跳过某些操作。它们的用途和区别如下:1.continue(跳过当前循环的剩余部分,进...
- L017:continue和break - 教程文案
-
continue和break在Python中,continue和break是用于控制循环(如for和while)执行流程的关键字,它们的作用如下:1.continue:跳过当前迭代,...
- 作为前端开发者,你都经历过怎样的面试?
-
已经裸辞1个月了,最近开始投简历找工作,遇到各种各样的面试,今天分享一下。其实在职的时候也做过面试官,面试官时,感觉自己问的问题很难区分候选人的能力,最好的办法就是看看候选人的github上的代码仓库...
- 面试被问 const 是否不可变?这样回答才显功底
-
作为前端开发者,我在学习ES6特性时,总被const的"善变"搞得一头雾水——为什么用const声明的数组还能push元素?为什么基本类型赋值就会报错?直到翻遍MDN文档、对着内存图反...
- 2023金九银十必看前端面试题!2w字精品!
-
导文2023金九银十必看前端面试题!金九银十黄金期来了想要跳槽的小伙伴快来看啊CSS1.请解释CSS的盒模型是什么,并描述其组成部分。答案:CSS的盒模型是用于布局和定位元素的概念。它由内容区域...
- 前端面试总结_前端面试题整理
-
记得当时大二的时候,看到实验室的学长学姐忙于各种春招,有些收获了大厂offer,有些还在苦苦面试,其实那时候的心里还蛮忐忑的,不知道自己大三的时候会是什么样的一个水平,所以从19年的寒假放完,大二下学...
- 由浅入深,66条JavaScript面试知识点(七)
-
作者:JakeZhang转发链接:https://juejin.im/post/5ef8377f6fb9a07e693a6061目录由浅入深,66条JavaScript面试知识点(一)由浅入深,66...
- 2024前端面试真题之—VUE篇_前端面试题vue2020及答案
-
添加图片注释,不超过140字(可选)1.vue的生命周期有哪些及每个生命周期做了什么?beforeCreate是newVue()之后触发的第一个钩子,在当前阶段data、methods、com...
- 今年最常见的前端面试题,你会做几道?
-
在面试或招聘前端开发人员时,期望、现实和需求之间总是存在着巨大差距。面试其实是一个交流想法的地方,挑战人们的思考方式,并客观地分析给定的问题。可以通过面试了解人们如何做出决策,了解一个人对技术和解决问...
- 一周热门
- 最近发表
- 标签列表
-
- git pull (33)
- git fetch (35)
- mysql insert (35)
- mysql distinct (37)
- concat_ws (36)
- java continue (36)
- jenkins官网 (37)
- mysql 子查询 (37)
- python元组 (33)
- mybatis 分页 (35)
- vba split (37)
- redis watch (34)
- python list sort (37)
- nvarchar2 (34)
- mysql not null (36)
- hmset (35)
- python telnet (35)
- python readlines() 方法 (36)
- munmap (35)
- docker network create (35)
- redis 集合 (37)
- python sftp (37)
- setpriority (34)
- c语言 switch (34)
- git commit (34)
