Mybatis动态解析

Mybatis动态解析

Mybatis配置文件:

mybatis配置文件在src/main/resources目录下,假设是mybatisConfig.xml(名称随意)。

<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "mybatis-3-config.dtd" >
<configuration>
    <!-- 可以配置多个运行环境,但是每个SqlSessionFactory 实例只能选择一个运行环境 -->
    <environments default="work">
        <environment id="work">
	        <!-- 两种事务管理器类型:JDBC和MANAGED -->
            <transactionManager type="JDBC"></transactionManager>
            <!-- 有三种数据源类型:UNPOOLED、POOLED、JNDI -->
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver" />
                <property name="url" value="jdbc:mysql://localhost:3306/test"/>
                <property name="username" value="root" />
                <property name="password" value="root123" />
            </dataSource>
        </environment>
    </environments>
    <mappers>
    <!-- 可配置多个mapper -->
        <mapper resource="com/example/mapper/StudentMapper.xml"/>
    </mappers>
</configuration>

如果是spring与mybatis整合,可以将数据源配置放到spring配置文件中。另外,如果mapper接口和mapper配置文件遵循:mapper.java和mapper.xml的文件名相同且在同一个文件夹下,在mybatis配置文件中可以不用配置mapper。

Mapper配置文件:

StudentMapper.xml配置文件在com/example/mapper目录下。

<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "mybatis-3-mapper.dtd" >
<mapper namespace="com.example.mapper.StudentMapper">
    
    <resultMap id="studentResultMap" type="com.example.entity.Student">
        <id column="stu_id" property="id"/>
        <result column="stu_name" property="name"/>
        <result column="stu_password" property="password"/>
    </resultMap>

    <insert id="saveStudent" parameterType="com.example.entity.Student">
        insert into student values(default,#{name},#{password})
    </insert>

    <select id="selectStudent" parameterType="com.example.entity.Student" resultMap="studentResultMap">
        select id,name from student where id = #{id}
    </select>
</mapper>

实体类:

package com.example.entity;
public class Student {
    private int id;
    private String name;
    private String password;

    public Student() {
    }
    //省略了getter、setter方法
}

对于上面mapper.xml配置文件中的两条SQL,mybatis在底层是如何解析的呢?

是通过XMLMapperBuilder解析mapper.xml文件的,具体源码见下文分析。

在单独使用mybatis时,SqlSessionFactorySqlSessionmybatis的核心,SqlSessionFactory用于生成SqlSessionSpringmybatis整合之后,通过SqlSessionFactoryBean创建SqlSessionFactory ,Spring与Mybatis整合时需要配置SqlSessionFactoryBean,该配置会加入数据源和mybatis xml配置文件路径等信息:

<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
    <property name="dataSource" ref="dataSource"/>
    <property name="configLocation" value="classpath:mybatisConfig.xml"/>
    <property name="mapperLocations" value="classpath*:com/example/mapper/*.xml"/>
</bean>

SqlSessionFactoryBean实现了Spring的InitializingBean接口,该接口的afterPropertiesSet方法中调用buildSqlSessionFactory方法,buildSqlSessionFactory方法中使用XMLConfigBuilder解析属性configLocation中配置的路径,还会使用XMLMapperBuilder解析mapperLocations属性中的各个xml文件。

1、SqlSessionFactoryBean的部分源码

SqlSessionFactoryBean类:

public class SqlSessionFactoryBean implements FactoryBean<SqlSessionFactory>, InitializingBean, ApplicationListener<ApplicationEvent> {
    private static final Log LOGGER = LogFactory.getLog(SqlSessionFactoryBean.class);
    private Resource configLocation;
    private Configuration configuration;
    private Resource[] mapperLocations;
    //省略了其他属性和方法
    public void afterPropertiesSet() throws Exception {
        Assert.notNull(this.dataSource, "Property 'dataSource' is required");
        Assert.notNull(this.sqlSessionFactoryBuilder, "Property 'sqlSessionFactoryBuilder' is required");
        Assert.state(this.configuration == null && this.configLocation == null || this.configuration == null || this.configLocation == null, "Property 'configuration' and 'configLocation' can not specified with together");
        this.sqlSessionFactory = this.buildSqlSessionFactory();
    }

    protected SqlSessionFactory buildSqlSessionFactory() throws IOException {
        XMLConfigBuilder xmlConfigBuilder = null;
        Configuration configuration;
        if (this.configuration != null) {
            configuration = this.configuration;
            if (configuration.getVariables() == null) {
                configuration.setVariables(this.configurationProperties);
            } else if (this.configurationProperties != null) {
                configuration.getVariables().putAll(this.configurationProperties);
            }
        } else if (this.configLocation != null) {
            // 创建XMLConfigBuilder对象
            xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), (String)null, this.configurationProperties);
            configuration = xmlConfigBuilder.getConfiguration();
        } else {
            if (LOGGER.isDebugEnabled()) {
                LOGGER.debug("Property 'configuration' or 'configLocation' not specified, using default MyBatis Configuration");
            }
            configuration = new Configuration();
            if (this.configurationProperties != null) {
                configuration.setVariables(this.configurationProperties);
            }
        }
        //省略了其他代码
        if (!ObjectUtils.isEmpty(this.mapperLocations)) {
            Resource[] var29 = this.mapperLocations;
            var27 = var29.length;
            for(var5 = 0; var5 < var27; ++var5) {
	            //遍历mapperLocations文件
                Resource mapperLocation = var29[var5];
                if (mapperLocation != null) {
                    try {
		                //使用XMLMapperBuilder解析各个mapper.xml文件
                        XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(), configuration, mapperLocation.toString(), configuration.getSqlFragments());
                        xmlMapperBuilder.parse();
                    } catch (Exception var20) {
                        throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", var20);
                    } finally {
                        ErrorContext.instance().reset();
                    }
                    if (LOGGER.isDebugEnabled()) {
                        LOGGER.debug("Parsed mapper file: '" + mapperLocation + "'");
                    }
                }
            }
        } else if (LOGGER.isDebugEnabled()) {
            LOGGER.debug("Property 'mapperLocations' was not specified or no matching resources found");
        }
        return this.sqlSessionFactoryBuilder.build(configuration);
    }
}
 

2、XMLConfigBuilder的部分源码

 

XMLConfigBuilder解析mybatis中configLocation属性中的全局xml文件。

XMLConfigBuilder类的parse方法,解析mybatis的核心配置文件

public Configuration parse() {
    if (this.parsed) {
        throw new BuilderException("Each XMLConfigBuilder can only be used once.");
    } else {
        this.parsed = true;
        //解析mybatis核心配置文件,<property name="configLocation" value="classpath:mybatisConfig.xml"/>
        this.parseConfiguration(this.parser.evalNode("/configuration"));
        return this.configuration;
    }
}
private void parseConfiguration(XNode root) {
    try {
        this.propertiesElement(root.evalNode("properties")); //解析<properties>标签
        Properties settings = this.settingsAsProperties(root.evalNode("settings")); //解析<settings>标签
        this.loadCustomVfs(settings);
        this.typeAliasesElement(root.evalNode("typeAliases")); //解析<typeAliases>标签
        this.pluginElement(root.evalNode("plugins"));
        this.objectFactoryElement(root.evalNode("objectFactory"));
        this.objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
        this.reflectorFactoryElement(root.evalNode("reflectorFactory"));
        this.settingsElement(settings);
        this.environmentsElement(root.evalNode("environments")); //解析<environments>标签
        this.databaseIdProviderElement(root.evalNode("databaseIdProvider"));
        this.typeHandlerElement(root.evalNode("typeHandlers")); //解析<typeHandlers>标签
        this.mapperElement(root.evalNode("mappers")); //解析<mappers>标签,即解析mapper.xml文件
    } catch (Exception var3) {
        throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + var3, var3);
    }
}
private void mapperElement(XNode parent) throws Exception {
    if (parent != null) {
        Iterator var2 = parent.getChildren().iterator();
        while(true) {
            while(var2.hasNext()) {
                XNode child = (XNode)var2.next();
                String resource;
                if ("package".equals(child.getName())) {
                    resource = child.getStringAttribute("name");
                    this.configuration.addMappers(resource);
                } else {
                    resource = child.getStringAttribute("resource");
                    String url = child.getStringAttribute("url");
                    String mapperClass = child.getStringAttribute("class");
                    XMLMapperBuilder mapperParser;
                    InputStream inputStream;
                    if (resource != null && url == null && mapperClass == null) {
                        ErrorContext.instance().resource(resource);
                        inputStream = Resources.getResourceAsStream(resource);
                        mapperParser = new XMLMapperBuilder(inputStream, this.configuration, resource, this.configuration.getSqlFragments());
                        mapperParser.parse();
                    } else if (resource == null && url != null && mapperClass == null) {
                        ErrorContext.instance().resource(url);
                        inputStream = Resources.getUrlAsStream(url);
                        mapperParser = new XMLMapperBuilder(inputStream, this.configuration, url, this.configuration.getSqlFragments());
                        mapperParser.parse();
                    } else {
                        if (resource != null || url != null || mapperClass == null) {
                            throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
                        }
                        Class<?> mapperInterface = Resources.classForName(mapperClass);
                        this.configuration.addMapper(mapperInterface);
                    }
                }
            }
            return;
        }
    }
}

由于XMLConfigBuilder内部也是使用XMLMapperBuilder,我们就看看XMLMapperBuilder的解析细节。

3、XMLMapperBuilder的部分源码

XMLMapperBuilder解析mybatis中mapperLocations属性中的mapper.xml文件。

public void parse() {
    if (!this.configuration.isResourceLoaded(this.resource)) {
        this.configurationElement(this.parser.evalNode("/mapper"));
        this.configuration.addLoadedResource(this.resource);
        this.bindMapperForNamespace();
    }

    this.parsePendingResultMaps();
    this.parsePendingCacheRefs();
    this.parsePendingStatements();
}

private void configurationElement(XNode context) {
    try {
        String namespace = context.getStringAttribute("namespace");
        if (namespace != null && !namespace.equals("")) {
            this.builderAssistant.setCurrentNamespace(namespace);
            this.cacheRefElement(context.evalNode("cache-ref"));
            this.cacheElement(context.evalNode("cache"));
	        //解析mapper.xml中的parameterMap节点
            this.parameterMapElement(context.evalNodes("/mapper/parameterMap"));
	        //解析mapper.xml中的resultMap节点
            this.resultMapElements(context.evalNodes("/mapper/resultMap"));
	        //解析mapper.xml中的sql节点
            this.sqlElement(context.evalNodes("/mapper/sql"));
	        //解析mapper.xml中的select|insert|update|delete节点
            this.buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
        } else {
            throw new BuilderException("Mapper's namespace cannot be empty");
        }
    } catch (Exception var3) {
        throw new BuilderException("Error parsing Mapper XML. Cause: " + var3, var3);
    }
}
private void buildStatementFromContext(List<XNode> list) {
    if (this.configuration.getDatabaseId() != null) {
        this.buildStatementFromContext(list, this.configuration.getDatabaseId());
    }

    this.buildStatementFromContext(list, (String)null);
}
private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
    Iterator var3 = list.iterator();
    while(var3.hasNext()) {
        XNode context = (XNode)var3.next();
        //使用XMLStatementBuilder解析select|insert|update|delete节点
        XMLStatementBuilder statementParser = new XMLStatementBuilder(this.configuration, this.builderAssistant, context, requiredDatabaseId);
        try {
            statementParser.parseStatementNode();
        } catch (IncompleteElementException var7) {
            this.configuration.addIncompleteStatement(statementParser);
        }
    }
}

4、XMLStatementBuilder的部分源码

XMLStatementBuilder解析mapper.xml文件中的select,insert,update,delete节点。

public void parseStatementNode() {
    //获取<select>、<insert>、<update>、<delete>标签中的id属性,即mapper接口中的方法名称
    String id = this.context.getStringAttribute("id");
    String databaseId = this.context.getStringAttribute("databaseId");
    if (this.databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
        Integer fetchSize = this.context.getIntAttribute("fetchSize");
        Integer timeout = this.context.getIntAttribute("timeout");
        //获取<select>、<insert>、<update>、<delete>标签中的parameterMap属性
        String parameterMap = this.context.getStringAttribute("parameterMap");
        //获取<select>、<insert>、<update>、<delete>标签中的parameterType属性
        String parameterType = this.context.getStringAttribute("parameterType");
        Class<?> parameterTypeClass = this.resolveClass(parameterType);
        //获取<select>、<insert>、<update>、<delete>标签中的resultMap属性
        String resultMap = this.context.getStringAttribute("resultMap");
        //获取<select>、<insert>、<update>、<delete>标签中的resultType属性
        String resultType = this.context.getStringAttribute("resultType");
        String lang = this.context.getStringAttribute("lang");
        LanguageDriver langDriver = this.getLanguageDriver(lang);
        Class<?> resultTypeClass = this.resolveClass(resultType);
        String resultSetType = this.context.getStringAttribute("resultSetType");
        StatementType statementType = StatementType.valueOf(this.context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
        ResultSetType resultSetTypeEnum = this.resolveResultSetType(resultSetType);
        String nodeName = this.context.getNode().getNodeName();
        SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
        boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
        boolean flushCache = this.context.getBooleanAttribute("flushCache", !isSelect).booleanValue();
        boolean useCache = this.context.getBooleanAttribute("useCache", isSelect).booleanValue();
        boolean resultOrdered = this.context.getBooleanAttribute("resultOrdered", false).booleanValue();
        XMLIncludeTransformer includeParser = new XMLIncludeTransformer(this.configuration, this.builderAssistant);
        includeParser.applyIncludes(this.context.getNode());
        this.processSelectKeyNodes(id, parameterTypeClass, langDriver);
        //XMLStatementBuilder中使用LanguageDriver解析SQL,并创建SqlSource
        SqlSource sqlSource = langDriver.createSqlSource(this.configuration, this.context, parameterTypeClass);
        String resultSets = this.context.getStringAttribute("resultSets");
        String keyProperty = this.context.getStringAttribute("keyProperty");
        String keyColumn = this.context.getStringAttribute("keyColumn");
        String keyStatementId = id + "!selectKey";
        keyStatementId = this.builderAssistant.applyCurrentNamespace(keyStatementId, true);
        Object keyGenerator;
        if (this.configuration.hasKeyGenerator(keyStatementId)) {
            keyGenerator = this.configuration.getKeyGenerator(keyStatementId);
        } else {
            keyGenerator = this.context.getBooleanAttribute("useGeneratedKeys", this.configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType)).booleanValue() ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
        }
        this.builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType, fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass, resultSetTypeEnum, flushCache, useCache, resultOrdered, (KeyGenerator)keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
    }
}

XMLLanguageDriver和RawLanguageDriver都继承了LanguageDriver类,由于在Configuration类的setDefaultScriptingLanguage方法中设置了XMLLanguageDriver,即默认使用XMLLanguageDriver类中的createSqlSource方法创建SqlSource。

public void setDefaultScriptingLanguage(Class<?> driver) {
    if (driver == null) {
        driver = XMLLanguageDriver.class;
    }
    this.getLanguageRegistry().setDefaultDriverClass(driver);
}

XMLLanguageDriver类中的createSqlSource方法如下所示:

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

5、XMLScriptBuilder的部分源码

XMLScriptBuilder解析xml中各个sql中的子节点,比如trim,if,where节点。

public class XMLScriptBuilder extends BaseBuilder {
    private final XNode context;
    private boolean isDynamic;
    private final Class<?> parameterType;
    public XMLScriptBuilder(Configuration configuration, XNode context) {
        this(configuration, context, (Class)null);
    }
    public XMLScriptBuilder(Configuration configuration, XNode context, Class<?> parameterType) {
        super(configuration);
        this.context = context;
        this.parameterType = parameterType;
    }
    //解析标签,并创建SQLSource
    public SqlSource parseScriptNode() {
        //解析SQL节点,获取节点列表
        List<SqlNode> contents = this.parseDynamicTags(this.context);
        //创建MixedSqlNode,MixedSqlNode实现了SqlNode接口
        MixedSqlNode rootSqlNode = new MixedSqlNode(contents);
        SqlSource sqlSource = null;
        if (this.isDynamic) {
	        //如果节点是标签,就用DynamicSqlSource创建SqlSource
            sqlSource = new DynamicSqlSource(this.configuration, rootSqlNode);
        } else {
	        //如果节点是文本,就用RawSqlSource创建SqlSource
            sqlSource = new RawSqlSource(this.configuration, rootSqlNode, this.parameterType);
        }
        //得到SqlSource,就可以得到BoundSql,BoundSql中含有sql,从而可以获取到sql语句
        return (SqlSource)sqlSource;
    }
    //解析标签
    List<SqlNode> parseDynamicTags(XNode node) {
        List<SqlNode> contents = new ArrayList();
        NodeList children = node.getNode().getChildNodes();
        for(int i = 0; i < children.getLength(); ++i) {
            XNode child = node.newXNode(children.item(i));
            String nodeName;
            if (child.getNode().getNodeType() != 4 && child.getNode().getNodeType() != 3) {
                if (child.getNode().getNodeType() == 1) {
                    nodeName = child.getNode().getNodeName();
                    XMLScriptBuilder.NodeHandler handler = this.nodeHandlers(nodeName);
                    if (handler == null) {
                        throw new BuilderException("Unknown element <" + nodeName + "> in SQL statement.");
                    }
                    handler.handleNode(child, contents);
                    this.isDynamic = true;
                }
            } else {
                nodeName = child.getStringBody("");
                TextSqlNode textSqlNode = new TextSqlNode(nodeName);
	    //如果节点是动态的,即节点是标签,不是文本,将节点添加到节点列表contents中
                if (textSqlNode.isDynamic()) {
                    contents.add(textSqlNode);
                    this.isDynamic = true;
                } else {
	        //如果节点是文本或CDATA,就创建StaticTextSqlNode,并将文本添加到contents中
                    contents.add(new StaticTextSqlNode(nodeName));
                }
            }
        }
        return contents;
    }

    XMLScriptBuilder.NodeHandler nodeHandlers(String nodeName) {
        Map<String, XMLScriptBuilder.NodeHandler> map = new HashMap();
        map.put("trim", new XMLScriptBuilder.TrimHandler());
        map.put("where", new XMLScriptBuilder.WhereHandler());
        map.put("set", new XMLScriptBuilder.SetHandler());
        map.put("foreach", new XMLScriptBuilder.ForEachHandler());
        map.put("if", new XMLScriptBuilder.IfHandler());
        map.put("choose", new XMLScriptBuilder.ChooseHandler());
        map.put("when", new XMLScriptBuilder.IfHandler());
        map.put("otherwise", new XMLScriptBuilder.OtherwiseHandler());
        map.put("bind", new XMLScriptBuilder.BindHandler());
        return (XMLScriptBuilder.NodeHandler)map.get(nodeName);
    }
//NodeHandler是XMLScriptBuilder类的内部接口,ChooseHandler、IfHandler、ForEachHandler、TrimHandler等都是XMLScriptBuilder的内部类,且都实现了NodeHandler接口
private interface NodeHandler {
        void handleNode(XNode var1, List<SqlNode> var2);
    }
//ForEachHandler实现了NodeHandler接口,重写了handleNode方法,对foreach节点(即<foreach>标签)进行解析
private class ForEachHandler implements XMLScriptBuilder.NodeHandler {
        public ForEachHandler() {
        }
        public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
            List<SqlNode> contents = XMLScriptBuilder.this.parseDynamicTags(nodeToHandle);
            MixedSqlNode mixedSqlNode = new MixedSqlNode(contents);
            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 = new ForEachSqlNode(XMLScriptBuilder.this.configuration, mixedSqlNode, collection, index, item, open, close, separator);
            targetContents.add(forEachSqlNode);
        }
    }
}

6、SqlSource接口

SqlSource是Sql源接口,代表从xml文件或注解映射的sql内容,主要就是用于创建BoundSql,有实现类DynamicSqlSource(动态Sql源),StaticSqlSource(静态Sql源)等。

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

DynamicSqlSource和RawSqlSource都实现了SqlSource接口。

public class DynamicSqlSource implements SqlSource {
    private final Configuration configuration;
    private final SqlNode rootSqlNode;
    public DynamicSqlSource(Configuration configuration, SqlNode rootSqlNode) {
        this.configuration = configuration;
        this.rootSqlNode = rootSqlNode;
    }
    public BoundSql getBoundSql(Object parameterObject) {
        //根据SqlNode处理DynamicContext,在DynamicContext中存放了sql语句中的节点和参数
        DynamicContext context = new DynamicContext(this.configuration, parameterObject);
        this.rootSqlNode.apply(context);
        SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(this.configuration);
        Class<?> parameterType = parameterObject == null ? Object.class : parameterObject.getClass();
        SqlSource sqlSource = sqlSourceParser.parse(context.getSql(), parameterType, context.getBindings());
        BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
        Iterator var7 = context.getBindings().entrySet().iterator();
        while(var7.hasNext()) {
            Entry<String, Object> entry = (Entry)var7.next();
            boundSql.setAdditionalParameter((String)entry.getKey(), entry.getValue());
        }
        return boundSql;
    }
}

BoundSql类:

public class BoundSql {
    private final String sql;
    private final List<ParameterMapping> parameterMappings;
    private final Object parameterObject;
    private final Map<String, Object> additionalParameters;
    private final MetaObject metaParameters;
    public BoundSql(Configuration configuration, String sql, List<ParameterMapping> parameterMappings, Object parameterObject) {
        this.sql = sql;
        this.parameterMappings = parameterMappings;
        this.parameterObject = parameterObject;
        this.additionalParameters = new HashMap();
        this.metaParameters = configuration.newMetaObject(this.additionalParameters);
    }
    public String getSql() {
        return this.sql;
    }
    public List<ParameterMapping> getParameterMappings() {
        return this.parameterMappings;
    }
    public Object getParameterObject() {
        return this.parameterObject;
    }
    public boolean hasAdditionalParameter(String name) {
        String paramName = (new PropertyTokenizer(name)).getName();
        return this.additionalParameters.containsKey(paramName);
    }
    public void setAdditionalParameter(String name, Object value) {
        this.metaParameters.setValue(name, value);
    }
    public Object getAdditionalParameter(String name) {
        return this.metaParameters.getValue(name);
    }
}

7、SqlNode接口

TextSqlNode、StaticTextSqlNode、TrimSqlNode、MixedSqlNode等都实现了SqlNode接口。

public interface SqlNode {
    //每个实现类都对apply方法进行了重写
    boolean apply(DynamicContext var1);
}
MixedSqlNode实现类:
public class MixedSqlNode implements SqlNode {
    //contents是SqlNode列表,包含了实现SqlNode接口的各个实现类
    private final List<SqlNode> contents;
    public MixedSqlNode(List<SqlNode> contents) {
        this.contents = contents;
    }
    public boolean apply(DynamicContext context) {
        Iterator var2 = this.contents.iterator();
        while(var2.hasNext()) {
            SqlNode sqlNode = (SqlNode)var2.next();
	        //调用具体某个实现类的apply方法
            sqlNode.apply(context);
        }
        return true;
    }
}
TextSqlNode实现类:
public class TextSqlNode implements SqlNode {
    private final String text;
    private final Pattern injectionFilter;
    public TextSqlNode(String text) {
        this(text, (Pattern)null);
    }
    public TextSqlNode(String text, Pattern injectionFilter) {
        this.text = text;
        this.injectionFilter = injectionFilter;
    }
    public boolean isDynamic() {
        TextSqlNode.DynamicCheckerTokenParser checker = new TextSqlNode.DynamicCheckerTokenParser();
        GenericTokenParser parser = this.createParser(checker);
        parser.parse(this.text);
        return checker.isDynamic();
    }
    //将解析的SQL文本添加到DynamicContext中
    public boolean apply(DynamicContext context) {
        GenericTokenParser parser = this.createParser(new TextSqlNode.BindingTokenParser(context, this.injectionFilter));
        context.appendSql(parser.parse(this.text));
        return true;
    }
    //省略了其他方法
}

由以上部分源码可知,XMLConfigBuilder、XMLMapperBuilder、XMLStatementBuilder、XMLScriptBuilder都实现了BaseBuilder接口。

BaseBuilder接口及其实现类,这些Builder的作用就是用于构造sql。

下面简单分析下其中4个Builder:

1、XMLConfigBuilder

解析mybatis中configLocation属性中的全局xml文件(即mybatis的核心配置文件,如上例中的configuration.xml文件),内部会使用XMLMapperBuilder解析各个xml文件。

2、XMLMapperBuilder

遍历mybatis中mapperLocations属性中的xml文件中每个节点的Builder(即mapper配置文件,如上例中的StudentMapper.xml文件),内部会使用XMLStatementBuilder处理xml中的每个节点。

3、XMLStatementBuilder

解析mapper.xml文件中各个节点,比如select,insert,update,delete节点(即<select>等标签),内部会使用XMLScriptBuilder处理节点的sql部分,遍历产生的数据会丢到Configuration的mappedStatements中。

4、XMLScriptBuilder

解析xml中各个sql中的子节点,比如trim,if,where节点(即<trim>、<if>等标签)。

8、案例分析

假设mapper.xml文件中有下面这段SQL配置,接下来分析mybatis是如何解析这段SQL的。

<update id="update" parameterType="org.format.dynamicproxy.mybatis.bean.User"> 	
    UPDATE users 
    <trim prefix="SET" prefixOverrides=","> 
        <if test="name != null and name != ''"> 
            name = #{name} 
        </if> 
        <if test="age != null and age != ''"> 
        , age = #{age} 
        </if> 
        <if test="birthday != null and birthday != ''"> 
        , birthday = #{birthday} 
        </if> 
    </trim> 
    where id = #{id} 
</update>

1、首先通过XMLStatementBuilder解析mapper.xml文件中各个select、insert、update、delete节点,如此处的update节点。得到该节点下的所有子节点,分别是下面的三个子节点:

(1)文本节点 \n UPDATE users \n

(2)trim子节点 ...

(3)文本节点 \n where id = #{id} \n

2、其次通过XMLScriptBuilder解析SQL中的各个子节点

(1)如果节点类型是文本或者CDATA,构造一个TextSqlNode或StaticTextSqlNode

(2)如果节点类型是元素,说明该update节点是个动态sql,然后会使用NodeHandler处理各个类型的子节点。这里的NodeHandler是XMLScriptBuilder的一个内部接口,其实现类包括TrimHandler、WhereHandler、SetHandler、IfHandler、ChooseHandler等。看类名也就明白了这个Handler的作用,比如我们分析的trim节点,对应的是TrimHandler;if节点,对应的是IfHandler...

子节点trim被TrimHandler处理,TrimHandler内部也使用parseDynamicTags方法解析节点

3、遇到子节点是元素的话,重复以上步骤。

//NodeHandler是XMLScriptBuilder类的内部接口,TrimHandler是XMLScriptBuilder的内部类,实现了NodeHandler接口
private class TrimHandler implements XMLScriptBuilder.NodeHandler {
    public TrimHandler() {
    }
    public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
        List<SqlNode> contents = XMLScriptBuilder.this.parseDynamicTags(nodeToHandle);
        MixedSqlNode mixedSqlNode = new MixedSqlNode(contents);
        String prefix = nodeToHandle.getStringAttribute("prefix");
        String prefixOverrides = nodeToHandle.getStringAttribute("prefixOverrides");
        String suffix = nodeToHandle.getStringAttribute("suffix");
        String suffixOverrides = nodeToHandle.getStringAttribute("suffixOverrides");
        TrimSqlNode trim = new TrimSqlNode(XMLScriptBuilder.this.configuration, mixedSqlNode, prefix, prefixOverrides, suffix, suffixOverrides);
        targetContents.add(trim);
    }
}

以上update SQL最终通过parseDynamicTags方法得到的SqlNode集合如下:

trim子节点:

XMLScriptBuilder的作用是解析sql中的子节点,如trim、if、where等节点,在XMLScriptBuilder类的parseScriptNode方法中构造了SqlSource。由于这个update SQL是个动态节点,因此构造出的SqlSource是DynamicSqlSource。

    //解析标签,并创建SQLSource
    public SqlSource parseScriptNode() {
        List<SqlNode> contents = this.parseDynamicTags(this.context);
        MixedSqlNode rootSqlNode = new MixedSqlNode(contents);
        SqlSource sqlSource = null;
        if (this.isDynamic) {
	        //如果节点是标签,就用DynamicSqlSource创建SqlSource
            sqlSource = new DynamicSqlSource(this.configuration, rootSqlNode);
        } else {
	        //如果节点是文本,就用RawSqlSource创建SqlSource
            sqlSource = new RawSqlSource(this.configuration, rootSqlNode, this.parameterType);
        }
        //得到SqlSource,就可以得到BoundSql,BoundSql中含有sql,从而可以获取到sql语句
        return (SqlSource)sqlSource;
    }

由上面SqlSource接口的源码可知,在DynamicSqlSource内部构造sql,并通过BoundSql返回。因此,最终将mapper.xml文件中的<select>、<insert>、<update>、<delete>节点中的由节点组成的SQL解析成一条完整的SQL语句,放到BoundSql中。然后再通过mybatis的预编译功能对解析后的SQL进行预编译,最后将预编译后的SQL语句交给数据库管理系统(DBMS)执行。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值