The initialization of Mybatis is completed by SqlSessionFactoryBuilder. The main work is to parse the XML file, encapsulate the parsed class contents into the Configuration class, and finally encapsulate the Configuration class into SqlSessionFactory and return it. Since then, the initialization is completed.
Three classes, XMLConfigBuilder, XMLMapperBuilder and XMLStatementBuilder, are used to complete the parsing of XML files:
- XMLConfigBuilder: responsible for parsing the mappers node in the global configuration file (mybatis-config.xml).
- Xmlmaperbuilder: copy and parse the cache ref, cache, parameterMap, resultMap and sql nodes in the xxxMapper.xml mapping file.
- XMLStatementBuilder: responsible for parsing the SQL statement nodes in the xxxMapper.xml mapping file, such as select, insert, update and delete.
- XMLScriptBuilder: responsible for parsing SQL scripts and then encapsulating them as SqlSource.
Mybatis initialization process
SqlSessionFactoryBuilder
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) { try { // Delegate to XMLConfigBuilder to parse configuration files XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties); // Start parsing return build(parser.parse()); } ... } public SqlSessionFactory build(Configuration config) { return new DefaultSqlSessionFactory(config); }
XMLConfigBuilder
private void parseConfiguration(XNode root) { try { //issue #117 read properties first // Resolving < Properties > nodes propertiesElement(root.evalNode("properties")); // Analyze < Settings > nodes Properties settings = settingsAsProperties(root.evalNode("settings")); loadCustomVfs(settings); loadCustomLogImpl(settings); // Analyze < typealiases > nodes typeAliasesElement(root.evalNode("typeAliases")); pluginElement(root.evalNode("plugins")); objectFactoryElement(root.evalNode("objectFactory")); objectWrapperFactoryElement(root.evalNode("objectWrapperFactory")); reflectorFactoryElement(root.evalNode("reflectorFactory")); settingsElement(settings); // read it after objectFactory and objectWrapperFactory issue #631 environmentsElement(root.evalNode("environments")); databaseIdProviderElement(root.evalNode("databaseIdProvider")); typeHandlerElement(root.evalNode("typeHandlers")); // Parsing < mappers > nodes mapperElement(root.evalNode("mappers")); } catch (Exception e) { throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e); } } private void mapperElement(XNode parent) throws Exception { if (parent != null) { for (XNode child : parent.getChildren()) { if ("package".equals(child.getName())) { String mapperPackage = child.getStringAttribute("name"); configuration.addMappers(mapperPackage); } else { String resource = child.getStringAttribute("resource"); String url = child.getStringAttribute("url"); String mapperClass = child.getStringAttribute("class"); if (resource != null && url == null && mapperClass == null) { ErrorContext.instance().resource(resource); InputStream inputStream = Resources.getResourceAsStream(resource); // Delegate XMLMapperBuilder to parse the mapping file XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments()); mapperParser.parse(); } else if (resource == null && url != null && mapperClass == null) { ErrorContext.instance().resource(url); InputStream inputStream = Resources.getUrlAsStream(url); // Delegate XMLMapperBuilder to parse the mapping file XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments()); mapperParser.parse(); } else if (resource == null && url == null && mapperClass != null) { Class<?> mapperInterface = Resources.classForName(mapperClass); configuration.addMapper(mapperInterface); } else { throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one."); } } } } }
XMLMapperBuilder
private void configurationElement(XNode context) { try { String namespace = context.getStringAttribute("namespace"); if (namespace == null || namespace.equals("")) { throw new BuilderException("Mapper's namespace cannot be empty"); } builderAssistant.setCurrentNamespace(namespace); // Parsing < cache ref > nodes cacheRefElement(context.evalNode("cache-ref")); // Parsing < cache > nodes cacheElement(context.evalNode("cache")); // Parsing < parametermap > nodes parameterMapElement(context.evalNodes("/mapper/parameterMap")); // Parsing < resultmap > nodes resultMapElements(context.evalNodes("/mapper/resultMap")); sqlElement(context.evalNodes("/mapper/sql")); // Parse the < select|insert|update|delete > node buildStatementFromContext(context.evalNodes("select|insert|update|delete")); } catch (Exception e) { throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e); } } private void buildStatementFromContext(List<XNode> list) { if (configuration.getDatabaseId() != null) { buildStatementFromContext(list, configuration.getDatabaseId()); } buildStatementFromContext(list, null); } private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) { for (XNode context : list) { // Parse the < select|insert|update|delete > node and delegate it to XMLStatementBuilder final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId); try { statementParser.parseStatementNode(); } catch (IncompleteElementException e) { configuration.addIncompleteStatement(statementParser); } } }
XMLStatementBuilder
public void parseStatementNode() { String id = context.getStringAttribute("id"); String databaseId = context.getStringAttribute("databaseId"); if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) { return; } // Judge the type of SQL statement according to the node name (select|insert|update|delete) String nodeName = context.getNode().getNodeName(); SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH)); boolean isSelect = sqlCommandType == SqlCommandType.SELECT; boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect); boolean useCache = context.getBooleanAttribute("useCache", isSelect); boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false); // Include Fragments before parsing // Analyze < include > nodes XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant); includeParser.applyIncludes(context.getNode()); String parameterType = context.getStringAttribute("parameterType"); Class<?> parameterTypeClass = resolveClass(parameterType); String lang = context.getStringAttribute("lang"); LanguageDriver langDriver = getLanguageDriver(lang); // Parse selectKey after includes and remove them. // Parse the < selectkey > node and delete the < selectkey > node in the XML processSelectKeyNodes(id, parameterTypeClass, langDriver); // Parse the SQL (pre: <selectKey> and <include> were parsed and removed) KeyGenerator keyGenerator; String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX; keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true); if (configuration.hasKeyGenerator(keyStatementId)) { keyGenerator = configuration.getKeyGenerator(keyStatementId); } else { keyGenerator = context.getBooleanAttribute("useGeneratedKeys", configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType)) ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE; } // Parse the SQL statement and encapsulate it as SqlSource SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass); StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString())); Integer fetchSize = context.getIntAttribute("fetchSize"); Integer timeout = context.getIntAttribute("timeout"); String parameterMap = context.getStringAttribute("parameterMap"); String resultType = context.getStringAttribute("resultType"); Class<?> resultTypeClass = resolveClass(resultType); String resultMap = context.getStringAttribute("resultMap"); String resultSetType = context.getStringAttribute("resultSetType"); ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType); if (resultSetTypeEnum == null) { resultSetTypeEnum = configuration.getDefaultResultSetType(); } String keyProperty = context.getStringAttribute("keyProperty"); String keyColumn = context.getStringAttribute("keyColumn"); String resultSets = context.getStringAttribute("resultSets"); builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType, fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass, resultSetTypeEnum, flushCache, useCache, resultOrdered, keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets); }
Mybatis initialization flowchart
Core data structure class
Configuration
Configuration is actually the Java form of XML configuration file. Configuration is a single example, and its life cycle is application level.
ResultMap
Corresponds to the resultMap node in the xxxMapper.xml mapping file. The child nodes in the < resultMap > node are encapsulated with ResultMapping, such as: < ID >, < result > and other nodes.
MappedStatement
It corresponds to the < Select >, < Insert >, < update > and < delete > nodes in the xxxMapper.xml mapping file.
SqlSource
Corresponding to the sql statement in the xxxMapper.xml mapping file, after parsing the statement contained in SqlSource, it finally only contains the following statements: Placeholder, which can be directly submitted to the database for execution;
MapperRegistry
It is the registry of Mapper interface dynamic proxy factory classes. In MyBatis, the InvocationHandler interface is implemented by MapperProxy, and the instance object of dynamic proxy is generated by MapperProxyFactory.
MyBatis builder class diagram
The whole initialization process of Mybatis uses builder mode. Builder mode is more suitable for generating complex objects, mainly focusing on the specific details of object instantiation.