MyBatis初始化之 SQL 初始化

MyBatis初始化之 SQL 初始化
2020年11月26日 11:10 拉勾IT培训

在MyBatis初始化过程中,大致会有以下几个步骤:拉勾IT课小编为大家分解

  1. 创建Configuration全局配置对象,会往TypeAliasRegistry别名注册中心添加Mybatis需要用到的相关类,并设置默认的语言驱动类为XMLLanguageDriver

  2. 加载mybatis-config.xml配置文件、Mapper接口中的注解信息和XML映射文件,解析后的配置信息会形成相应的对象并保存到Configuration全局配置对象中

  3. 构建DefaultSqlSessionFactory对象,通过它可以创建DefaultSqlSession对象,MyBatis中SqlSession的默认实现类

因为整个初始化过程涉及到的代码比较多,所以拆分成了四个模块依次对MyBatis的初始化进行分析:

  • 《MyBatis初始化(一)之加载mybatis-config.xml

  • 《MyBatis初始化(二)之加载Mapper接口与XML映射文件》

  • 《MyBatis初始化(三)之SQL初始化(上)》

  • 《MyBatis初始化(四)之SQL初始化(下)》

由于在MyBatis的初始化过程中去解析Mapper接口与XML映射文件涉及到的篇幅比较多,XML映射文件的解析过程也比较复杂,所以才分成了后面三个模块,逐步分析,这样便于理解

初始化(三)之SQL初始化(上)

在前面的MyBatis初始化相关文档中已经大致讲完了MyBatis初始化的整个流程,其中遗漏了一部分,就是在解析节点的过程中,是如何解析SQL语句,如何实现动态SQL语句,最终会生成一个org.apache.ibatis.mapping.SqlSource对象的,对于这烦琐且易出错的过程,我们来看看MyBatis如何实现的?

我们回顾org.apache.ibatis.builder.xml.XMLStatementBuilder的parseStatementNode()解析Statement节点时,通过下面的方法创建对应的SqlSource对象

//创建对应的SqlSource对象,保存了该节点下SQL相关信息

SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);

langDriver是从Configuration全局配置对象中获取的默认实现类,对应的也就是XMLLanguageDriver,在Configuration初始化的时候设置的

public Configuration() {

languageRegistry.setDefaultDriverClass(XMLLanguageDriver.class);

languageRegistry.register(RawLanguageDriver.class);

}

主要包路径:org.apache.ibatis.scripting、org.apache.ibatis.builder、org.apache.ibatis.mapping

主要涉及到的类:

  • org.apache.ibatis.scripting.xmltags.XMLLanguageDriver:语言驱动接口的默认实现,创建ParameterHandler参数处理器对象和SqlSource资源对象

  • org.apache.ibatis.scripting.xmltags.XMLScriptBuilder:继承BaseBuilder抽象类,负责将SQL脚本(XML或者注解中定义的SQL语句)解析成SqlSource(DynamicSqlSource或者RawSqlSource)资源对象

  • org.apache.ibatis.scripting.xmltags.XMLScriptBuilder.NodeHandler:定义在XMLScriptBuilder内部的一个接口,用于处理MyBatis自定义标签(等),生成对应的SqlNode对象,不同的实现类处理不同的标签

  • org.apache.ibatis.scripting.xmltags.DynamicContext:解析动态SQL语句时的上下文,用于解析SQL时,记录动态SQL处理后的SQL语句,内部提供ContextMap对象保存上下文的参数

  • org.apache.ibatis.scripting.xmltags.SqlNode:SQL Node接口,每个XML Node会解析成对应的SQL Node对象,通过上下文可以对动态SQL进行逻辑处理,生成需要的结果

  • org.apache.ibatis.scripting.xmltags.OgnlCache:用于处理Ognl表达式

语言驱动接口的实现类如下图所示:

LanguageDriver

org.apache.ibatis.scripting.LanguageDriver:语言驱动接口,代码如下:

public interface LanguageDriver {

/**

* Creates a {@link ParameterHandler} that passes the actual parameters to the the JDBC statement.

*创建ParameterHandler对象

*

* @param mappedStatement The mapped statement that is being executed

* @param parameterObject The input parameter object (can be null)

* @param boundSqlThe resulting SQL once the dynamic language has been executed.

* @return参数处理器

* @author Frank D. Martinez [mnesarco]

* @see DefaultParameterHandler

*/

ParameterHandler createParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql);

/**

* Creates an {@link SqlSource} that will hold the statement read from a mapper xml file.

* It is called during startup, when the mapped statement is read from a class or an xml file.

*创建SqlSource对象,从Mapper XML配置的Statement标签中,即等。

*

* @param configuration The MyBatis configuration

* @param scriptXNode parsed from a XML file

* @param parameterType input parameter type got from a mapper method or specified in the parameterType xml attribute. Can be null.

* @return SQL资源

*/

SqlSource createSqlSource(Configuration configuration, XNode script, Class> parameterType);

/**

* Creates an {@link SqlSource} that will hold the statement read from an annotation.

* It is called during startup, when the mapped statement is read from a class or an xml file.

*创建SqlSource对象,从方法注解配置,即@Select等。

*

* @param configuration The MyBatis configuration

* @param scriptThe content of the annotation

* @param parameterType input parameter type got from a mapper method or specified in the parameterType xml attribute. Can be null.

* @return SQL资源

*/

SqlSource createSqlSource(Configuration configuration, String script, Class> parameterType);

}

定义了三个方法:

  1. createParameterHandler:获取ParameterHandler参数处理器对象

  2. createSqlSource:创建SqlSource对象,解析Mapper XML配置的Statement标签中,即

  3. createSqlSource:创建SqlSource对象,从方法注解配置,即@Select等

XMLLanguageDriver

org.apache.ibatis.scripting.xmltags.XMLLanguageDriver:语言驱动接口的默认实现,代码如下:

public class XMLLanguageDriver implements LanguageDriver {

@Override

public ParameterHandler createParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {

//创建DefaultParameterHandler对象

return new DefaultParameterHandler(mappedStatement, parameterObject, boundSql);

}

/**

*用于解析XML映射文件中的SQL

*

* @param configuration The MyBatis configuration

* @param scriptXNode parsed from a XML file

* @param parameterType input parameter type got from a mapper method or

*specified in the parameterType xml attribute. Can be

*null.

* @return SQL资源

*/

@Override

public SqlSource createSqlSource(Configuration configuration, XNode script, Class> parameterType) {

//创建XMLScriptBuilder对象,执行解析

XMLScriptBuilder builder = new XMLScriptBuilder(configuration, script, parameterType);

return builder.parseScriptNode();

}

/**

*用于解析注解中的SQL

*

* @param configuration The MyBatis configuration

* @param scriptThe content of the annotation

* @param parameterType input parameter type got from a mapper method or

*specified in the parameterType xml attribute. Can be

*null.

* @return SQL资源

*/

@Override

public SqlSource createSqlSource(Configuration configuration, String script, Class> parameterType) {

// issue #3

//如果是开头,表示是在注解中使用的动态SQL

if (script.startsWith("

//创建XPathParser对象,解析出节点

XPathParser parser = new XPathParser(script, false, configuration.getVariables(), new XMLMapperEntityResolver());

return createSqlSource(configuration, parser.evalNode("/script"), parameterType);

} else {

// issue #127

//变量替换

script = PropertyParser.parse(script, configuration.getVariables());

//创建TextSqlNode对象

TextSqlNode textSqlNode = new TextSqlNode(script);

if (textSqlNode.isDynamic()) { //如果是动态SQL,则创建DynamicSqlSource对象

return new DynamicSqlSource(configuration, textSqlNode);

} else { //如果非动态SQL,则创建RawSqlSource对象

return new RawSqlSource(configuration, script, parameterType);

}

}

}

}

实现了LanguageDriver接口:

  1. 创建DefaultParameterHandler默认参数处理器并返回

  2. 解析XML映射文件中的SQL,通过创建XMLScriptBuilder对象,调用其parseScriptNode()方法解析

  3. 解析注解定义的SQL

    1. 如果是开头,表示是在注解中使用的动态SQL,将其转换成XNode然后调用上述方法,不了解的可以看看MyBatis三种动态SQL配置方式

    2. 先将注解中定义的SQL中包含的变量进行转换,然后创建对应的SqlSource对象

RawLanguageDriver

org.apache.ibatis.scripting.defaults.RawLanguageDriver:继承了XMLLanguageDriver,在的基础上增加了是否为静态SQL语句的校验,也就是判断创建的SqlSource是否为RawSqlSource静态SQL资源

XMLScriptBuilder

org.apache.ibatis.scripting.xmltags.XMLScriptBuilder:继承BaseBuilder抽象类,负责将SQL脚本(XML或者注解中定义的SQL)解析成SqlSource对象

构造方法

public class XMLScriptBuilder extends BaseBuilder {

/**

*当前SQL的XNode对象

*/

private final XNode context;

/**

*是否为动态SQL

*/

private boolean isDynamic;

/**

* SQL的Java入参类型

*/

private final Class> parameterType;

/**

* NodeNodeHandler的映射

*/

private final Map

nodeHandlerMap = new HashMap();

public XMLScriptBuilder(Configuration configuration, XNode context) {

this(configuration, context, null);

}

public XMLScriptBuilder(Configuration configuration, XNode context, Class> parameterType) {

super(configuration);

this.context = context;

this.parameterType = parameterType;

initNodeHandlerMap();

}

private void initNodeHandlerMap() {

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());

}

}

在构造函数中会初始化NodeHandler处理器,分别用于处理不同的MyBatis自定义的XML标签,例如等标签

parseScriptNode方法

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;

}

  1. 通过调用parseDynamicTags(XNode node)方法,将解析SQL成MixedSqlNode对象,主要是将一整个SQL解析成一系列的SqlNode对象

  2. 如果是动态SQL语句,使用了MyBatis自定义的XML标签(等)或者使用了${},则封装成DynamicSqlSource对象

  3. 否则就是静态SQL语句,封装成RawSqlSource对象

parseDynamicTags方法

parseDynamicTags()将SQL脚本(XML或者注解中定义的SQL)解析成MixedSqlNode对象,代码如下:

protected MixedSqlNode parseDynamicTags(XNode node) {

//创建SqlNode数组

List

contents = new ArrayList();

/*

*遍历SQL节点中所有子节点

*这里会对该节点内的所有内容进行处理然后返回NodeList对象

* 1.文本内容会被解析成'#text>'节点,就算一个换行符也会解析成这个

* 2.会被解析成'content#cdata-section>'节点

* 3.其他动态

*/

NodeList children = node.getNode().getChildNodes();

for (int i = 0; i

//当前子节点

XNode child = node.newXNode(children.item(i));

//如果类型是Node.CDATA_SECTION_NODE或者Node.TEXT_NODE时

if (child.getNode().getNodeType() == Node.CDATA_SECTION_NODE //节点

|| child.getNode().getNodeType() == Node.TEXT_NODE) { //纯文本

//获得内容

String data = child.getStringBody("");

//创建TextSqlNode对象

TextSqlNode textSqlNode = new TextSqlNode(data);

if (textSqlNode.isDynamic()) { //如果是动态的TextSqlNode对象,也就是使用了'${}'

//添加到contents中

contents.add(textSqlNode);

//标记为动态SQL

isDynamic = true;

} else { //如果是非动态的TextSqlNode对象,没有使用'${}'

//创建StaticTextSqlNode添加到contents中

contents.add(new StaticTextSqlNode(data));

}

} else if (child.getNode().getNodeType() == Node.ELEMENT_NODE) { // issue #628如果类型是Node.ELEMENT_NODE

//根据子节点的标签,获得对应的NodeHandler对象

String nodeName = child.getNode().getNodeName();

NodeHandler handler = nodeHandlerMap.get(nodeName);

if (handler == null) { //获得不到,说明是未知的标签,抛出BuilderException异常

throw new BuilderException("Unknown element in SQL statement.");

}

//执行NodeHandler处理

handler.handleNode(child, contents);

//标记为动态SQL

isDynamic = true;

}

}

//创建MixedSqlNode对象

return new MixedSqlNode(contents);

}

创建SqlNode数组contents,用于保存解析SQL后的一些列SqlNode对象

获取定义的SQL节点中所有子节点,返回一个NodeList对象,这个对象中包含了该SQL节点内的所有信息,然后逐个遍历子节点

1.其中文本内容会被解析成`#text>`节点,就算一个换行符也会解析成这个

2. ``会被解析成`#cdata-section>`节点

3.还有其他MyBatis自定义的标签

`

`等等

如果子节点是或者类型

获取子节点的文本内容

创建TextSqlNode对象

调用TextSqlNode的isDynamic()方法,点击去该进去看看就知道了,如果文本中使用了${},则标记为动态SQL语句,将其添加至contents数组中

否则就是静态文本内容,创建对应的StaticTextSqlNode对象,将其添加至contents数组中

如果类型是Node.ELEMENT_NODE时,也就是MyBatis的自定义标签

根据子节点的标签名称,获得对应的NodeHandler对象

执行NodeHandler的handleNode方法处理该节点,创建不通类型的SqlNode并添加到contents数组中,如何处理的在下面讲述

标记为动态SQL语句

最后将创建contents封装成MixedSqlNode对象

NodeHandler

XMLScriptBuilder的内部接口,用于处理MyBatis自定义标签,接口实现类如下图所示:

代码如下:

private interface NodeHandler {

/**

*处理Node

*

* @param nodeToHandle要处理的XNode节点

* @param targetContents目标的SqlNode数组。实际上,被处理的XNode节点会创建成对应的SqlNode对象,添加到targetContents中

*/

void handleNode(XNode nodeToHandle, List

targetContents);

}

这些NodeHandler实现类都定义在XMLScriptBuilder内部,用于处理不同标签,我们逐个来看

BindHandler

实现了NodeHandler接口,标签的处理器,代码如下:

/**

*

元素允许你在OGNL表达式(SQL语句)以外创建一个变量,并将其绑定到当前的上下文

*/

private class BindHandler implements NodeHandler {

public BindHandler() {

// Prevent Synthetic Access

}

@Override

public void handleNode(XNode nodeToHandle, List

targetContents) {

//解析name、value属性

final String name = nodeToHandle.getStringAttribute("name");

final String expression = nodeToHandle.getStringAttribute("value");

//创建VarDeclSqlNode对象

final VarDeclSqlNode node = new VarDeclSqlNode(name, expression);

targetContents.add(node);

}

}

  1. 获取标签的name和value属性

  2. 根据这些属性创建一个VarDeclSqlNode对象

  3. 添加到targetContents集合中

例如这样配置:

SELECT * FROM BLOG

WHERE title LIKE #{pattern}

TrimHandler

实现了NodeHandler接口,标签的处理器,代码如下:

private class TrimHandler implements NodeHandler {

public TrimHandler() {

// Prevent Synthetic Access

}

@Override

public void handleNode(XNode nodeToHandle, List

targetContents) {

//解析内部的SQL节点,成MixedSqlNode对象

MixedSqlNode mixedSqlNode = parseDynamicTags(nodeToHandle);

//获得prefix、prefixOverrides、"suffix"、suffixOverrides属性

String prefix = nodeToHandle.getStringAttribute("prefix");

String prefixOverrides = nodeToHandle.getStringAttribute("prefixOverrides");

String suffix = nodeToHandle.getStringAttribute("suffix");

String suffixOverrides = nodeToHandle.getStringAttribute("suffixOverrides");

//创建TrimSqlNode对象

TrimSqlNode trim = new TrimSqlNode(configuration, mixedSqlNode, prefix, prefixOverrides, suffix, suffixOverrides);

targetContents.add(trim);

}

}

  1. 继续调用parseDynamicTags方法解析标签内部的子标签节点,嵌套解析,生成MixedSqlNode对象

  2. 获得prefix、prefixOverrides、suffix、suffixOverrides属性

  3. 根据上面获取到的属性创建TrimSqlNode对象

  4. 添加到targetContents集合中

WhereHandler

实现了NodeHandler接口,标签的处理器,代码如下:

private class WhereHandler implements NodeHandler {

public WhereHandler() {

// Prevent Synthetic Access

}

@Override

public void handleNode(XNode nodeToHandle, List

targetContents) {

//解析内部的SQL节点,成MixedSqlNode对象

MixedSqlNode mixedSqlNode = parseDynamicTags(nodeToHandle);

//创建WhereSqlNode对象

WhereSqlNode where = new WhereSqlNode(configuration, mixedSqlNode);

targetContents.add(where);

}

}

  1. 继续调用parseDynamicTags方法解析标签内部的子标签节点,嵌套解析,生成MixedSqlNode对象

  2. 创建WhereSqlNode对象,该对象继承了TrimSqlNode,自定义前缀(WHERE)和需要删除的前缀(AND、OR等)

  3. 添加到targetContents集合中

SetHandler

实现了NodeHandler接口,标签的处理器,代码如下:

private class SetHandler implements NodeHandler {

public SetHandler() {

// Prevent Synthetic Access

}

@Override

public void handleNode(XNode nodeToHandle, List

targetContents) {

//解析内部的SQL节点,成MixedSqlNode对象

MixedSqlNode mixedSqlNode = parseDynamicTags(nodeToHandle);

SetSqlNode set = new SetSqlNode(configuration, mixedSqlNode);

targetContents.add(set);

}

}

  1. 继续调用parseDynamicTags方法解析标签内部的子标签节点,嵌套解析,生成MixedSqlNode对象

  2. 创建SetSqlNode对象,该对象继承了TrimSqlNode,自定义前缀(SET)和需要删除的前缀和后缀(,)

  3. 添加到targetContents集合中

ForEachHandler

实现了NodeHandler接口,标签的处理器,代码如下:

private class ForEachHandler implements NodeHandler {

public ForEachHandler() {

// Prevent Synthetic Access

}

@Override

public void handleNode(XNode nodeToHandle, List

targetContents) {

//解析内部的SQL节点,成MixedSqlNode对象

MixedSqlNode mixedSqlNode = parseDynamicTags(nodeToHandle);

//获得collection、item、index、open、close、separator属性

String collection = nodeToHandle.getStringAttribute("collection");

String item = nodeToHandle.getStringAttribute("item");

String index = nodeToHandle.getStringAttribute("index");

String open = nodeToHandle.getStringAttribute("open");

String close = nodeToHandle.getStringAttribute("close");

String separator = nodeToHandle.getStringAttribute("separator");

//创建ForEachSqlNode对象

ForEachSqlNode forEachSqlNode = new ForEachSqlNode(configuration, mixedSqlNode, collection, index, item, open, close, separator);

targetContents.add(forEachSqlNode);

}

}

  1. 继续调用parseDynamicTags方法解析标签内部的子标签节点,嵌套解析,生成MixedSqlNode对象

  2. 获得collection、item、index、open、close、separator属性

  3. 根据这些属性创建ForEachSqlNode对象

  4. 添加到targetContents集合中

IfHandler

实现了NodeHandler接口,标签的处理器,代码如下:

private class IfHandler implements NodeHandler {

public IfHandler() {

// Prevent Synthetic Access

}

@Override

public void handleNode(XNode nodeToHandle, List

targetContents) {

//解析内部的SQL节点,成MixedSqlNode对象

MixedSqlNode mixedSqlNode = parseDynamicTags(nodeToHandle);

//获得test属性

String test = nodeToHandle.getStringAttribute("test");

//创建IfSqlNode对象

IfSqlNode ifSqlNode = new IfSqlNode(mixedSqlNode, test);

targetContents.add(ifSqlNode);

}

}

  1. 继续调用parseDynamicTags方法解析标签内部的子标签节点,嵌套解析,生成MixedSqlNode对象

  2. 获得test属性

  3. 根据这个属性创建IfSqlNode对象

  4. 添加到targetContents集合中

OtherwiseHandler

实现了NodeHandler接口,标签的处理器,代码如下:

private class OtherwiseHandler implements NodeHandler {

public OtherwiseHandler() {

// Prevent Synthetic Access

}

@Override

public void handleNode(XNode nodeToHandle, List

targetContents) {

//解析内部的SQL节点,成MixedSqlNode对象

MixedSqlNode mixedSqlNode = parseDynamicTags(nodeToHandle);

targetContents.add(mixedSqlNode);

}

}

  1. 继续调用parseDynamicTags方法解析标签内部的子标签节点,嵌套解析,生成MixedSqlNode对象

  2. 添加到targetContents集合中,需要结合ChooseHandler使用

ChooseHandler

实现了NodeHandler接口,标签的处理器,代码如下:

private class ChooseHandler implements NodeHandler {

public ChooseHandler() {

// Prevent Synthetic Access

}

@Override

public void handleNode(XNode nodeToHandle, List

targetContents) {

List

whenSqlNodes = new ArrayList();

List

otherwiseSqlNodes = new ArrayList();

//解析

`

`

`

`

的节点们

handleWhenOtherwiseNodes(nodeToHandle, whenSqlNodes, otherwiseSqlNodes);

//获得

`

`

的节点,存在多个会抛出异常

SqlNode defaultSqlNode = getDefaultSqlNode(otherwiseSqlNodes);

//创建ChooseSqlNode对象

ChooseSqlNode chooseSqlNode = new ChooseSqlNode(whenSqlNodes, defaultSqlNode);

targetContents.add(chooseSqlNode);

}

private void handleWhenOtherwiseNodes(XNode chooseSqlNode, List

ifSqlNodes,

List

defaultSqlNodes) {

List

children = chooseSqlNode.getChildren();

for (XNode child : children) {

String nodeName = child.getNode().getNodeName();

NodeHandler handler = nodeHandlerMap.get(nodeName);

if (handler instanceof IfHandler) { //处理

`

`

标签的情况

handler.handleNode(child, ifSqlNodes);

} else if (handler instanceof OtherwiseHandler) { //处理

`

`

标签的情况

handler.handleNode(child, defaultSqlNodes);

}

}

}

private SqlNode getDefaultSqlNode(List

defaultSqlNodes) {

SqlNode defaultSqlNode = null;

if (defaultSqlNodes.size() == 1) {

defaultSqlNode = defaultSqlNodes.get(0);

} else if (defaultSqlNodes.size() > 1) {

throw new BuilderException("Too many default (otherwise) elements in choose statement.");

}

return defaultSqlNode;

}

}

  1. 先逐步处理标签的子标签们,通过组合IfHandler和OtherwiseHandler两个处理器,实现对子节点们的解析

  2. 如果存在子标签,则抛出异常

  3. 根据这些属性创建ChooseSqlNode对象

  4. 添加到targetContents集合中

DynamicContext

org.apache.ibatis.scripting.xmltags.DynamicContext:解析动态SQL语句时的上下文,用于解析SQL时,记录动态SQL处理后的SQL语句,内部提供ContextMap对象保存上下文的参数

构造方法

public class DynamicContext {

/**

*入参保存在ContextMap中的Key

*

* {@link #bindings}

*/

public static final String PARAMETER_OBJECT_KEY = "_parameter";

/**

*数据库编号保存在ContextMap中的Key

*

* {@link #bindings}

*/

public static final String DATABASE_ID_KEY = "_databaseId";

static {

//设置OGNL的属性访问器

OgnlRuntime.setPropertyAccessor(ContextMap.class, new ContextAccessor());

}

/**

*上下文的参数集合,包含附加参数(通过

`

`

标签生成的,或者

`

`

标签中的集合的元素等等)

*/

private final ContextMap bindings;

/**

*生成后的SQL

*/

private final StringJoiner sqlBuilder = new StringJoiner(" ");

/**

*唯一编号。在{@link org.apache.ibatis.scripting.xmltags.XMLScriptBuilder.ForEachHandler}使用

*/

private int uniqueNumber = 0;

public DynamicContext(Configuration configuration, Object parameterObject) {

//初始化bindings参数

//构建入参的MetaObject对象

MetaObject metaObject = configuration.newMetaObject(parameterObject);

//入参类型是否有对应的类型处理器

boolean existsTypeHandler = configuration.getTypeHandlerRegistry().hasTypeHandler(parameterObject.getClass());

bindings = new ContextMap(metaObject, existsTypeHandler);

} else {

bindings = new ContextMap(null, false);

}

//添加bindings的默认值

bindings.put(PARAMETER_OBJECT_KEY, parameterObject);

bindings.put(DATABASE_ID_KEY, configuration.getDatabaseId());

}

}

  • 类型

    属性

    说明

  • ContextMap

    bindings

    上下文的参数集合,包含附加参数(通过标签生成的,或者标签解析参数保存的),以及几个默认值

    StringJoiner

    sqlBuilder

    保存本次解析后的SQL,每次添加字符串以空格作为分隔符

    int

    uniqueNumber

    唯一编号,在ForEachHandler处理节点时需要用到,生成唯一数组作为集合中每个元素的索引(作为后缀)

  1. 初始化bindings参数,创建ContextMap对象

    1. 根据入参转换成MetaObject对象

    2. 在静态代码块中,设置OGNL的属性访问器,OgnlRuntime是OGNL库中的类,设置ContextMap对应的访问器是ContextAccessor类

  2. 往bindings中添加几个默认值:_parameter>入参对象,_databaseId->数据库标识符

ContextMap

DynamicContext的内部静态类,继承HashMap,用于保存解析动态SQL语句时的上下文的参数集合,代码如下:

static class ContextMap extends HashMap

{

private static final long serialVersionUID = 2977601501966151582L;

/**

* parameter对应的MetaObject对象

*/

private final MetaObject parameterMetaObject;

/**

*是否有对应的类型处理器

*/

private final boolean fallbackParameterObject;

public ContextMap(MetaObject parameterMetaObject, boolean fallbackParameterObject) {

this.parameterMetaObject = parameterMetaObject;

this.fallbackParameterObject = fallbackParameterObject;

}

@Override

public Object get(Object key) {

String strKey = (String) key;

if (super.containsKey(strKey)) {

return super.get(strKey);

}

if (parameterMetaObject == null) {

return null;

}

return parameterMetaObject.getOriginalObject();

} else {

// issue #61 do not modify the context when reading

return parameterMetaObject.getValue(strKey);

}

}

}

重写了HashMap的get(Object key)方法,增加支持对parameterMetaObject属性的访问

ContextAccessor

DynamicContext的内部静态类,实现ognl.PropertyAccessor接口,上下文访问器,代码如下:

static class ContextAccessor implements PropertyAccessor {

@Override

public Object getProperty(Map context, Object target, Object name) {

Map map = (Map) target;

//优先从ContextMap中,获得属性

Object result = map.get(name);

if (map.containsKey(name) || result != null) {

return result;

}

//

如果没有,则从PARAMETER_OBJECT_KEY对应的Map中,获得属性

Object parameterObject = map.get(PARAMETER_OBJECT_KEY);

if (parameterObject instanceof Map) {

return ((Map) parameterObject).get(name);

}

return null;

}

@Override

public void setProperty(Map context, Object target, Object name, Object value) {

Map

map = (Map

) target;

map.put(name, value);

}

@Override

public String getSourceAccessor(OgnlContext arg0, Object arg1, Object arg2) {

return null;

}

@Override

public String getSourceSetter(OgnlContext arg0, Object arg1, Object arg2) {

return null;

}

}

在DynamicContext的静态代码块中,设置OGNL的属性访问器,设置了ContextMap.class的属性访问器为ContextAccessor

这里方法的入参中的target,就是ContextMap对象

  1. 在重写的getProperty方法中,先从ContextMap里面获取属性值(可以回过去看下ContextMap的get方法)

  2. 没有获取到则获取PARAMETER_OBJECT_KEY属性的值,如果是Map类型,则从这里面获取属性值

财经自媒体联盟更多自媒体作者

新浪首页 语音播报 相关新闻 返回顶部