public static void main(String[] args) throws IOException { String resource = "mybatis-config.xml"; InputStream inputStream = Resources.getResourceAsStream(resource); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); try (SqlSession session = sqlSessionFactory.openSession()) { //Mode 1: UserMapper mapper = session.getMapper(UserMapper.class); User user1 = mapper.selectBlog("1"); //Mode 2: User user2 = session.selectOne( "test.UserMapper.selectBlog", 1); } }
1. Constructing sqlSessionFactory
Read and parse MyBatis configuration file mybatis-config.xml, and save the parsed node data to the Configuration object.
Constructing sqlSessionFactory through Configuration Object
//Read configuration files InputStream inputStream =Resources.getResourceAsStream("mybatis-config.xml"); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream)
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) { try { /** * 1.1 Read the mybatis-config.xml file and convert the XML file to the Document object */ XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties); /** * 1.2 Method parser.parse(), parse the Document, save the data to Configuration, and return to Configuration * * 1.3 Build SqlSessionFactory through build(configuration) */ return build(parser.parse()); } catch (Exception e) { throw ExceptionFactory.wrapException("Error building SqlSession.", e); } finally { ErrorContext.instance().reset(); try { inputStream.close(); } catch (IOException e) { // Intentionally ignore. Prefer previous error. } } }
1.1. Conversion of xml files to Document objects
public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) { this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props); } private XMLConfigBuilder(XPathParser parser, String environment, Properties props) { /** * 1.1.1 Create the configuration information saving class configuration in XMLConfigBuilder */ super(new Configuration()); ErrorContext.instance().resource("SQL Mapper Configuration"); this.configuration.setVariables(props); this.parsed = false; this.environment = environment; this.parser = parser; }
1.2 method parser.parse(), parses Document, saves data to Configuration, and returns Configuration
public Configuration parse() { if (parsed) { throw new BuilderException("Each XMLConfigBuilder can only be used once."); } parsed = true; // Parse from root node < configuration > parseConfiguration(parser.evalNode("/configuration")); return configuration; }
private void parseConfiguration(XNode root) { try { /** * Comparing with mybatis-configuration.xml, the corresponding node labels are parsed separately. */ // 1.2.1 parsing properties nodes propertiesElement(root.evalNode("properties")); //1.2.2 Parsing setting Node Properties settings = settingsAsProperties(root.evalNode("settings")); loadCustomVfs(settings); loadCustomLogImpl(settings); // 1.2.3 Resolution of typeAliases aliases typeAliasesElement(root.evalNode("typeAliases")); pluginElement(root.evalNode("plugins")); objectFactoryElement(root.evalNode("objectFactory")); objectWrapperFactoryElement(root.evalNode("objectWrapperFactory")); reflectorFactoryElement(root.evalNode("reflectorFactory")); //1.2.2.1 Save setting node to configuration settingsElement(settings); //1.2.4 Environment node contains data source parsing and transaction configuration parsing environmentsElement(root.evalNode("environments")); databaseIdProviderElement(root.evalNode("databaseIdProvider")); //1.2.5 Resolving typeHandlers Type Processors typeHandlerElement(root.evalNode("typeHandlers")); // 1.2.6 parsing Mapper mapper mapperElement(root.evalNode("mappers")); } catch (Exception e) { throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e); } }
1.2.1 Parsing properties nodes
<!-- resource External file attributes override child nodes property information --> <properties resource="jdbc.properties"> <property name="username" value="dev_user"/> <property name="password" value="F2Fa3!33TYyg"/> </properties>
<!-- url External file attributes override child nodes property information --> <properties url="jdbc.properties"> <property name="username" value="dev_user"/> <property name="password" value="F2Fa3!33TYyg"/> </properties>
/** * 1.2.1 Parsing properties nodes * @param context * @throws Exception */ private void propertiesElement2(XNode context) throws Exception { if (context != null) { //Properties inherit Hashtable. First load the property child node name value attribute. Put in Hashtable Properties defaults = context.getChildrenAsProperties(); //Read the properties resource and url in the properties node String resource = context.getStringAttribute("resource"); String url = context.getStringAttribute("url"); //If both url and resource exist, an exception is thrown if (resource != null && url != null) { throw new BuilderException("The properties element cannot specify both a URL and a resource based property file reference. Please specify one or the other."); } if (resource != null) { //The name value of the external file introduced by the resource attribute overwrites the information of the nodes of Properties. defaults.putAll(Resources.getResourceAsProperties(resource)); } else if (url != null) { //The name value of the external file introduced by the url attribute overwrites the information of the nodes of Properties. defaults.putAll(Resources.getUrlAsProperties(url)); } //Read the variables attribute information in the Configuration object and, if so, add it to the properties object Properties vars = configuration.getVariables(); if (vars != null) { defaults.putAll(vars); } //Setting data in Properties to variables in XPathParser parser.setVariables(defaults); //Setting data in Properties to variables in configuration configuration.setVariables(defaults); } }
1.2.2 Parsing setting Node
<settings> <! - Open the cache - > <setting name="cacheEnabled" value="true"/> <! - Delayed loading - > <setting name="lazyLoadingEnabled" value="true"/> <! -- A single statement returns multiple result sets - > <setting name="multipleResultSetsEnabled" value="true"/> <! - Column labels instead of column names - > <setting name="useColumnLabel" value="true"/> <! - JDBC is not allowed to support automatic generation of primary keys - > <setting name="useGeneratedKeys" value="false"/> <setting name="autoMappingBehavior" value="PARTIAL"/> <setting name="autoMappingUnknownColumnBehavior" value="WARNING"/> <! -- Configure the defau lt executor. SIMPLE is a common actuator. The REUSE executor reuses prepared statements. BATCH executors reuse statements and perform batch updates - > <setting name="defaultExecutorType" value="SIMPLE"/> <! -- Timeout, which determines the number of seconds the driver waits for the database response. > <setting name="defaultStatementTimeout" value="25"/> <! -- Set a prompt value for the fetchSize of the driven resu lt set. This parameter can only be overridden in query settings. > <setting name="defaultFetchSize" value="100"/> <setting name="safeRowBoundsEnabled" value="false"/> <setting name="mapUnderscoreToCamelCase" value="false"/> <setting name="localCacheScope" value="SESSION"/> <setting name="jdbcTypeForNull" value="OTHER"/> <setting name="lazyLoadTriggerMethods" value="equals,clone,hashCode,toString"/> </settings>
/** * 1.2.2 Resolving setting Node * @param context * @return */ private Properties settingsAsProperties(XNode context) { if (context == null) { return new Properties(); } /** * Resolve the child node property of the setting node: name value and save it to Properties (Hashtable) */ Properties props = context.getChildrenAsProperties(); // Check that all settings are known to the configuration class MetaClass metaConfig = MetaClass.forClass(Configuration.class, localReflectorFactory); for (Object key : props.keySet()) { if (!metaConfig.hasSetter(String.valueOf(key))) { throw new BuilderException("The setting " + key + " is not known. Make sure you spelled it correctly (case sensitive)."); } } return props; }
/** * 1.2.1.1 Save the setting node to configuration * @param props */ private void settingsElement(Properties props) { configuration.setAutoMappingBehavior(AutoMappingBehavior.valueOf(props.getProperty("autoMappingBehavior", "PARTIAL"))); configuration.setAutoMappingUnknownColumnBehavior(AutoMappingUnknownColumnBehavior.valueOf(props.getProperty("autoMappingUnknownColumnBehavior", "NONE"))); configuration.setCacheEnabled(booleanValueOf(props.getProperty("cacheEnabled"), true)); configuration.setProxyFactory((ProxyFactory) createInstance(props.getProperty("proxyFactory"))); configuration.setLazyLoadingEnabled(booleanValueOf(props.getProperty("lazyLoadingEnabled"), false)); configuration.setAggressiveLazyLoading(booleanValueOf(props.getProperty("aggressiveLazyLoading"), false)); configuration.setMultipleResultSetsEnabled(booleanValueOf(props.getProperty("multipleResultSetsEnabled"), true)); configuration.setUseColumnLabel(booleanValueOf(props.getProperty("useColumnLabel"), true)); configuration.setUseGeneratedKeys(booleanValueOf(props.getProperty("useGeneratedKeys"), false)); configuration.setDefaultExecutorType(ExecutorType.valueOf(props.getProperty("defaultExecutorType", "SIMPLE"))); configuration.setDefaultStatementTimeout(integerValueOf(props.getProperty("defaultStatementTimeout"), null)); configuration.setDefaultFetchSize(integerValueOf(props.getProperty("defaultFetchSize"), null)); configuration.setDefaultResultSetType(resolveResultSetType(props.getProperty("defaultResultSetType"))); configuration.setMapUnderscoreToCamelCase(booleanValueOf(props.getProperty("mapUnderscoreToCamelCase"), false)); configuration.setSafeRowBoundsEnabled(booleanValueOf(props.getProperty("safeRowBoundsEnabled"), false)); configuration.setLocalCacheScope(LocalCacheScope.valueOf(props.getProperty("localCacheScope", "SESSION"))); configuration.setJdbcTypeForNull(JdbcType.valueOf(props.getProperty("jdbcTypeForNull", "OTHER"))); configuration.setLazyLoadTriggerMethods(stringSetValueOf(props.getProperty("lazyLoadTriggerMethods"), "equals,clone,hashCode,toString")); configuration.setSafeResultHandlerEnabled(booleanValueOf(props.getProperty("safeResultHandlerEnabled"), true)); configuration.setDefaultScriptingLanguage(resolveClass(props.getProperty("defaultScriptingLanguage"))); configuration.setDefaultEnumTypeHandler(resolveClass(props.getProperty("defaultEnumTypeHandler"))); configuration.setCallSettersOnNulls(booleanValueOf(props.getProperty("callSettersOnNulls"), false)); configuration.setUseActualParamName(booleanValueOf(props.getProperty("useActualParamName"), true)); configuration.setReturnInstanceForEmptyRow(booleanValueOf(props.getProperty("returnInstanceForEmptyRow"), false)); configuration.setLogPrefix(props.getProperty("logPrefix")); configuration.setConfigurationFactory(resolveClass(props.getProperty("configurationFactory"))); }
1.2.3 Resolution of typeAliases aliases
Aliases exist only to reduce redundancy in class fully qualified names
<typeAliases> <typeAlias alias="Author" type="domain.blog.Author"/> <typeAlias alias="Blog" type="domain.blog.Blog"/> <typeAlias alias="Comment" type="domain.blog.Comment"/> <typeAlias alias="Post" type="domain.blog.Post"/> <typeAlias alias="Section" type="domain.blog.Section"/> <typeAlias alias="Tag" type="domain.blog.Tag"/> <! - These two tags can coexist. But the < type Aliases /> tag must be in front of the < package /> tag. Because a class can have multiple aliases, both label settings are valid at this time. > <package name="domain.blog"/> </typeAliases>
/** * 1.2.3 Resolving typeAliases aliases * @param parent */ private void typeAliasesElement(XNode parent) { if (parent != null) { for (XNode child : parent.getChildren()) { //These two tags can coexist. But < type Aliases /> // The label must be in front of the <package/> label. // Because a class can have multiple aliases, both label settings are valid at this time. if ("package".equals(child.getName())) { String typeAliasPackage = child.getStringAttribute("name"); configuration.getTypeAliasRegistry().registerAliases(typeAliasPackage); } else { String alias = child.getStringAttribute("alias"); String type = child.getStringAttribute("type"); try { Class<?> clazz = Resources.classForName(type); if (alias == null) { typeAliasRegistry.registerAlias(clazz); } else { typeAliasRegistry.registerAlias(alias, clazz); } } catch (ClassNotFoundException e) { throw new BuilderException("Error registering typeAlias for '" + alias + "'. Cause: " + e, e); } } } } }
configuration.getTypeAliasRegistry().registerAliases(typeAliasPackage);
Every Java Bean in the package domain. blog, without annotations, uses a lowercase unqualified class name of the bean as its alias. For example, the alias of domain.blog.Author is author; if there are annotations, the alias is its annotation value. See the following example:
@Alias("author") public class Author { ... }
MyBatis provides built-in type aliases for common Java types. They are case-insensitive. For example _byte, byte_long, long
1.2.4 Data Source and Transaction Analysis in the next section
1.2.5 parse type handlers (complete conversion between Java data types and database data types)
<typeHandlers> <typeHandler handler="org.mybatis.example.ExampleTypeHandle" javaType="VARCHAR" jdbcType="String"></typeHandler> </typeHandlers>
public class ExampleTypeHandler extends BaseTypeHandler<String> { @Override public void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType) throws SQLException { ps.setString(i, parameter); } @Override public String getNullableResult(ResultSet rs, String columnName) throws SQLException { return rs.getString(columnName); } @Override public String getNullableResult(ResultSet rs, int columnIndex) throws SQLException { return rs.getString(columnIndex); } @Override public String getNullableResult(CallableStatement cs, int columnIndex) throws SQLException { return cs.getString(columnIndex); } }
1.2.6 Parsing Mapper Mapper Mapper
<! - Four ways to configure xml Mapping Files - > <! - Use resource references relative to class paths - > <mappers> <mapper resource="org/mybatis/builder/AuthorMapper.xml"/> </mappers> <! - Use fully qualified resource locators (URL s) --> <mappers> <mapper url="file:///var/mappers/AuthorMapper.xml"/> </mappers> <! - Fully qualified class names using mapper interfaces <mappers> <mapper class="org.mybatis.builder.AuthorMapper"/> </mappers> <! - Register all the mapper interfaces in the package as mappers - >. <mappers> <package name="org.mybatis.builder"/> </mappers>
private void mapperElement(XNode parent) throws Exception { if (parent != null) { for (XNode child : parent.getChildren()) { /** * Parsing package node */ if ("package".equals(child.getName())) { String mapperPackage = child.getStringAttribute("name"); configuration.addMappers(mapperPackage); } else { /** * resource,url,class Only one of the three tags has value, and the other two are null, can they be executed properly. */ //Find the xxDao.xml Mapping file through resource 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); XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments()); //Parsing x xxDao.xml mapping file mapperParser.parse(); } else if (resource == null && url != null && mapperClass == null) { //Find the xxDao.xml Mapping file by url ErrorContext.instance().resource(url); InputStream inputStream = Resources.getUrlAsStream(url); XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments()); //Parsing x xxDao.xml mapping file 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."); } } } } }
Through the resource, the URL finds the xxDao.xml Mapping file, continues to parse its nodes, and the parsed results are stored in the configuration object.
/** * Parsing xxxDao.xml file * @param context */ private void configurationElement2(XNode context) { try { //Read namespaces 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 Tags cacheRefElement(context.evalNode("cache-ref")); //Parsing cache tags cacheElement(context.evalNode("cache")); //Parsing/mapper/parameterMap Tags parameterMapElement(context.evalNodes("/mapper/parameterMap")); //Resolution/mapper/resultMap Tags resultMapElements(context.evalNodes("/mapper/resultMap")); //Parsing / mapper/sql Tags sqlElement(context.evalNodes("/mapper/sql")); //Resolve the select|insert|update|delete tag buildStatementFromContext(context.evalNodes("select|insert|update|delete")); } catch (Exception e) { throw new BuilderException("Error parsing Mapper XML. Cause: " + e, e); } }
1.3 Build SqlSessionFactory through build(configuration)
Previously, we parsed the nodes in mybatis-config.xml file, saved the parsed data to configuration, and returned to configuration.
/** * 1.3 Build SqlSessionFactory through build(configuration) * @param config * @return */ public SqlSessionFactory build(Configuration config) { return new DefaultSqlSessionFactory(config); }
Summary:
Read and parse MyBatis configuration file mybatis-config.xml, and save the parsed node data to the Configuration object.
Constructing sqlSessionFactory through Configuration Object