百度360必应搜狗淘宝本站头条
当前位置:网站首页 > IT技术 > 正文

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

wptr33 2025-01-10 17:38 32 浏览

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对象

相关推荐

威信Chronosonic XVX全新旗舰全球首发 设计特点彻底公开

第一眼看到WilsonAudio新推出的ChronosonicXVX音箱,相信大家都会直觉认为它是两年前超级旗舰WAMMMasterChronosonic的缩小版,不过这个推测并不完全正确。C...

C#高精度Timer和Delay以及时间测量

在PCHMI7.0后在工具箱里会多一个MsTimer,以及Delay和Microsecond两个类。...

python教程从基础到精通,第9课—日期与时间

Hello,小伙伴们,祝大家五.一玩得快乐!刚学习完了七大数据类型,今天咱们来学习日期与时间的表示方法。Python标准库中提供了时间和日期的支持:calendar:日历相关;time、datetim...

软件测试|教你轻松玩转Python日期时间

Python基础之日期时间处理...

Go语言中互斥锁与读写锁,你知多少?

简述Golang中的锁机制主要包含互斥锁和读写锁互斥锁互斥锁是传统并发程序对共享资源进行控制访问的主要手段。在Go中主要使用sync.Mutex的结构体表示。一个简单的示例:funcmutex()...

变形金刚动画大电影——经典台词赏析

YOURDAYSARENUMBEREDNOW,DECEPTI-CREEPS你们活不了多久了,霸天虎小子。-{铁皮说的话,体现了铁皮的嫉恶如仇,可是后来铁皮在飞船上遇袭身亡,可谓是出师未捷身先...

Python时间日期模块使用教程(python3日期)

1.时间日期处理概述在日常编程中,时间日期处理是非常常见的需求,比如:记录日志时间...

亚马逊介绍AWS“无服务器”云服务改进:数据库可线上扩充容量等

IT之家11月29日消息,在今天于美国拉斯维加斯展开的亚马逊“AWSre:Invent2023”活动中,亚马逊计算部门资深副总裁PeterDeSantis,介绍了旗下三款云端服务,IT...

2.日期格式 datetime(日期时间显示格式)

fromdatetimeimportdatetime1.获取当前日期和时间now=datetime.now()#2025-05-3110:56:01.4687822.格式化日期...

【科普】时间单位大盘点(时间单位都有哪些?)

时间单位,是7种基本单位之一,长度、时间、质量、物质的量、光照度、电流和(热力学)温度是七种基本单位。本词条中时间单位以时间从大到小列。今天我们来盘点下时间的单位换算...

基于PHP的Laravel框架,盘点Github高星Web管理后台,效率为王!

在Web开发工作中,选择一个高效、稳定的后台管理系统是提高开发效率的关键。虽然PHP在近些年中的热度有所减退,但其上手简单、开源、灵活且被广泛应用的特点,仍然使其在编程语言排行榜中保持前十的位置。这表...

如何使用PHP编写一个简单的留言板?

留言板是一个常见的Web应用程序,允许用户在网站上发布和查看留言。在本文中,我们将使用PHP编写一个简单的留言板,介绍构建过程中的关键步骤和技巧。一、准备工作在开始编写留言板之前,我们需要准备好以下工...

产品经理提需求时要考虑的 15 个隐性需求

虽然世界充满未知的变化,但是有一些大的方向还是可以把握的,本文跟大家谈谈产品经理提需求时要考虑的15个隐性需求,enjoy~俗话说,计划赶不上变化快,无论需求文档做得如何细致,考虑得如何周全,总会...

关于 PHP 启动 MongoDb 找不到指定模块问题

前言:最近有一个小demo,需要通过PHP将用户行为记录储存到MongoDB,再用Spark做协同过滤。由于以前处理跨语言交互是通过消息中间件,这次本地使用MongoDB却弄出了几个问...

PHP程序员老鸟面试经历(php程序员怎么样)

在任何时代找任何工作都有面试这么一说的。特别是高端技术类的工种对技术理论和技术实操能力要求很严格。大部分公司招收技术员工的要求也越来愈高。至于PHP程序员也是如此,我估计大多数PHP老鸟已经不在意所...