深入理解MyBatis(二)—MyBatis初始化

深入理解MyBatis(二)—MyBatis初始化

MyBatis的配置信息存储在XML配置文件中,使用Configuration对象作为一个所偶有配置信息的容器,Configuration对象的组织结构和XML配置文件的组织结构几乎完全一样,因此MyBatis初始化就是加载XML配置文件,创建Configuration对象的过程;

个人主页:tuzhenyu’s page
原文地址:深入理解MyBatis(二)—MyBatis初始化

(1) Configuration创建和加载

  • MyBatis的初始化入口是从SqlSeesionFactory的创建开始的,将生成的配置文件输入流传递到SqlSessionBuilder类的build()方法中构建SqlSessionFactory实例
String resource = "mybatis/config.xml";
InputStream is = Main.class.getClassLoader().getResourceAsStream(resource);
SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(is);
  • 调用build()方法的重载方法,首先根据配置文件输入流进行XML解析,再根据解析结果创建SqlSessionFactory实例
public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
    SqlSessionFactory var5;
    try {
        XMLConfigBuilder e = new XMLConfigBuilder(reader, environment, properties);
        var5 = this.build(e.parse());
    } catch (Exception var14) {
        throw ExceptionFactory.wrapException("Error building SqlSession.", var14);
    } finally {
        ErrorContext.instance().reset();
        try {
            reader.close();
        } catch (IOException var13) {
            ;
        }
    }
    return var5;
}
  • XMLConfigBuilder在构造器中创建XPathParser实例,进行XML配置输入流的解析
public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) {
    this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props);
}
  • commentConstrutor()方法主要是对validation等属性进行定义,根据输入流创建Document的主要逻辑主要在createDocoument()方法中;
public XPathParser(InputStream inputStream, boolean validation, Properties variables, EntityResolver entityResolver) {
    this.commonConstructor(validation, variables, entityResolver);
    this.document = this.createDocument(new InputSource(inputStream));
}
  • createDocument()方法根据XML输入流创建Document实例的方法和Spring中将XML文件解析成Doucment方法相同,都是通过SAX的方法解析XML文档;

  • 首先创建DocumentBuilderFactory,再通过DocumentBuilderFactory创建DocumentBuilder,进而解析inputSource()返回Document对象;在解析过程中调用”org.xml.sax”包下额SAX解析方法,创建DOM树;

  • 解完的Document会存储在XPathParser实例对象中,XPathParser对象会存储在XMLConfigBuilder对象中,调用XMLConfigBuilder的parse()方法进行进一步解析;

private Document createDocument(InputSource inputSource) {
    try {
        DocumentBuilderFactory e = DocumentBuilderFactory.newInstance();
        e.setValidating(this.validation);
        e.setNamespaceAware(false);
        e.setIgnoringComments(true);
        e.setIgnoringElementContentWhitespace(false);
        e.setCoalescing(false);
        e.setExpandEntityReferences(true);
        DocumentBuilder builder = e.newDocumentBuilder();
        builder.setEntityResolver(this.entityResolver);
        builder.setErrorHandler(new ErrorHandler() {
            public void error(SAXParseException exception) throws SAXException {
                throw exception;
            }

            public void fatalError(SAXParseException exception) throws SAXException {
                throw exception;
            }

            public void warning(SAXParseException exception) throws SAXException {
            }
        });
        return builder.parse(inputSource);
    } catch (Exception var4) {
        throw new BuilderException("Error creating document instance.  Cause: " + var4, var4);
    }
}
  • XPathParser实例的parse()方法通过xpath的方法对XML进行解析,首先定位configuration标签作为根标签;

  • 与Spring解析XML不同的是Spring仅仅通过SAX方法将XML文件解析成Document对象,然后对根节点下的标签进行遍历解析生成对应的BeanDefinitiion,而MyBatis在解析玩完Document后利用XPath对标签进行定位获取;

public Configuration parse() {
    if(this.parsed) {
        throw new BuilderException("Each XMLConfigBuilder can only be used once.");
    } else {
        this.parsed = true;
        this.parseConfiguration(this.parser.evalNode("/configuration"));
        return this.configuration;
    }
}
  • 调用parseConfiguration()方法对XML进行解析,并根据标签解析结果对configuration对象进行属性的初始化设置
private void parseConfiguration(XNode root) {
    try {
        this.propertiesElement(root.evalNode("properties"));
        Properties e = this.settingsAsProperties(root.evalNode("settings"));
        this.loadCustomVfs(e);
        this.typeAliasesElement(root.evalNode("typeAliases"));
        this.pluginElement(root.evalNode("plugins"));
        this.objectFactoryElement(root.evalNode("objectFactory"));
        this.objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
        this.reflectorFactoryElement(root.evalNode("reflectorFactory"));
        this.settingsElement(e);
        this.environmentsElement(root.evalNode("environments"));
        this.databaseIdProviderElement(root.evalNode("databaseIdProvider"));
        this.typeHandlerElement(root.evalNode("typeHandlers"));
        this.mapperElement(root.evalNode("mappers"));
    } catch (Exception var3) {
        throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + var3, var3);
    }
}
  • 创建Configuration实例对象装载在DefaultSqlSessionFactory对象中,并返回DefaultSqlSessionFactory对象实例用于创建SqlSession;
public SqlSessionFactory build(Configuration config) {
    return new DefaultSqlSessionFactory(config);
}

(2) Mappers标签的解析

  • mappers标签的解析主要是遍历mappers标签下的所有mapper标签,将mapper映射文件内的每条SQL语句对象化成一个个MapperStatement对象;mapper标签解析的入口是mapperElement()方法;
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 mapperInterface1;
                    if(resource != null && url == null && mapperClass == null) {
                        ErrorContext.instance().resource(resource);
                        mapperInterface1 = Resources.getResourceAsStream(resource);
                        mapperParser = new XMLMapperBuilder(mapperInterface1, this.configuration, resource, this.configuration.getSqlFragments());
                        mapperParser.parse();
                    } else if(resource == null && url != null && mapperClass == null) {
                        ErrorContext.instance().resource(url);
                        mapperInterface1 = Resources.getUrlAsStream(url);
                        mapperParser = new XMLMapperBuilder(mapperInterface1, 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;
        }
    }
}
  • 每一个mapper映射文件都会创建一个XMLMapperBuilder对象,调用其parse()方法进行单个映射文件mapper的解析;

  • 主要是对mapper映射文件下的ResultMap,cache-ref和具体的sql操作语句(select,insert,update,delete)进行解析;

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();
}
  • 对具体sql操作语句的解析通过调用buildStatementFromContext()方法实现;
private void configurationElement(XNode context) {
    try {
        String e = context.getStringAttribute("namespace");
        if(e != null && !e.equals("")) {
            this.builderAssistant.setCurrentNamespace(e);
            this.cacheRefElement(context.evalNode("cache-ref"));
            this.cacheElement(context.evalNode("cache"));
            this.parameterMapElement(context.evalNodes("/mapper/parameterMap"));
            this.resultMapElements(context.evalNodes("/mapper/resultMap"));
            this.sqlElement(context.evalNodes("/mapper/sql"));
            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);
    }
}
  • 通过XPath从mapper映射文件中获取全部SQL操作集合,每条操作创建一个XMLStatementBuilder对象进行解析
private void buildStatementFromContext(List list, String requiredDatabaseId) {
    Iterator var3 = list.iterator();

    while(var3.hasNext()) {
        XNode context = (XNode)var3.next();
        XMLStatementBuilder statementParser = new XMLStatementBuilder(this.configuration, this.builderAssistant, context, requiredDatabaseId);

        try {
            statementParser.parseStatementNode();
        } catch (IncompleteElementException var7) {
            this.configuration.addIncompleteStatement(statementParser);
        }
    }

}
  • 在XMLStatementBuilder对象的parseStatementNode()方法中构建SqlSource对象和MappedStatement对象
public void parseStatementNode() {
    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");
        String parameterMap = this.context.getStringAttribute("parameterMap");
        String parameterType = this.context.getStringAttribute("parameterType");
        Class parameterTypeClass = this.resolveClass(parameterType);
        String resultMap = this.context.getStringAttribute("resultMap");
        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", Boolean.valueOf(!isSelect)).booleanValue();
        boolean useCache = this.context.getBooleanAttribute("useCache", Boolean.valueOf(isSelect)).booleanValue();
        boolean resultOrdered = this.context.getBooleanAttribute("resultOrdered", Boolean.valueOf(false)).booleanValue();
        XMLIncludeTransformer includeParser = new XMLIncludeTransformer(this.configuration, this.builderAssistant);
        includeParser.applyIncludes(this.context.getNode());
        this.processSelectKeyNodes(id, parameterTypeClass, langDriver);
        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", Boolean.valueOf(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);
    }
}
  • 在创建MapperStatement对象前需要先创建SqlSource,BoundSql作为从mapper中解析出的sql语句的载体;BoundSql对象中的sql已经解析为带有占位符?的可执行的sql;parameterMappings是执行时传入的参数,因此每执行一次都会创建一个BoundSql对象;
public class BoundSql {
    private String sql;
    private List parameterMappings;
    private Object parameterObject;
    private Map additionalParameters;
    private MetaObject metaParameters;

    public BoundSql(Configuration configuration, String sql, List 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 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);
    }
}
  • 在生成SqlSource对象过程中会用到XMLScriptBuilder对动态sql进行解析,转换成可执行的sql;

总结

  • MyBatis的初始化主要是加载解析XML配置文件,根据解析结果创建Configuration对象;将创建好的Configuration对象装载生成SqlSessionFactory实例对象;

  • 加载mappers标签过程中,会对每个mapper映射文件创建一个XMLMapperBuilder对象进行解析,在单个mapper映射文件解析过程中,会对每个sql操作标签创建一个XMLStatementBuilder对象用于解析,解析每一个sql操作生成对应的MapperStatement对象用于后续的操作;

你可能感兴趣的:(mybatis,深入理解MyBatis)