Main 方法,mybatis 版本為 3.5.0
解析配置文件的所有信息,保存在 Configuration 中,返回包含 Configuration 的 DefaultSqlSession
MappedStatement:代表一個增刪改查的詳細信息
InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
session = sqlSessionFactory.openSession();
new SqlSessionFactoryBuilder().build(inputStream)
org.apache.ibatis.session.SqlSessionFactoryBuilder
/** * @param inputStream 參照 XML 文檔或更特定的 SqlMapConfig.xml 文件的 InputStream 實例 * @param environment 可選的參數,決定加載哪種環境(開發環境/生產環境),包括數據源和事務管理器 * @param properties 可選的參數,使用 properties,就會加載那些 properties(屬性配置文件),屬性可以用 ${propName} 語法形式多次用在配置文件中。和 Spring 很像,一個思想? * @return */ public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) { try { //委托 XMLConfigBuilder 來解析 xml 文件,並構建 XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties); 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. } } } /** * @param config Configuration * @return DefaultSqlSessionFactory */ public SqlSessionFactory build(Configuration config) { return new DefaultSqlSessionFactory(config); }
parser.parse()
org.apache.ibatis.builder.xml.XMLConfigBuilder
// 解析配置 public Configuration parse() { // 如果已經解析過了,報錯 if (parsed) { throw new BuilderException("Each XMLConfigBuilder can only be used once."); } parsed = true; // XML 配置文件根節點是 configuration parseConfiguration(parser.evalNode("/configuration")); return configuration; } // 分步驟解析 private void parseConfiguration(XNode root) { try { //issue #117 read properties first // 1.properties propertiesElement(root.evalNode("properties")); // 2.設置 Properties settings = settingsAsProperties(root.evalNode("settings")); loadCustomVfs(settings); // 顯式定義用什么log框架,不定義則用默認的自動發現 jar 包機制 loadCustomLogImpl(settings); // 3.類型別名 typeAliasesElement(root.evalNode("typeAliases")); // 4.插件 pluginElement(root.evalNode("plugins")); // 5.對象工廠 objectFactoryElement(root.evalNode("objectFactory")); // 6.對象包裝工廠 objectWrapperFactoryElement(root.evalNode("objectWrapperFactory")); reflectorFactoryElement(root.evalNode("reflectorFactory")); settingsElement(settings); // read it after objectFactory and objectWrapperFactory issue #631 // 7.環境 environmentsElement(root.evalNode("environments")); // 8.數據庫環境 databaseIdProviderElement(root.evalNode("databaseIdProvider")); // 9.類型處理器 typeHandlerElement(root.evalNode("typeHandlers")); // 10.SQL映射器 mapperElement(root.evalNode("mappers")); } catch (Exception e) { throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e); } } // 設置 private void settingsElement(Properties props) { // 如何自動映射列到字段屬性 configuration.setAutoMappingBehavior(AutoMappingBehavior.valueOf(props.getProperty("autoMappingBehavior", "PARTIAL"))); // MyBatis 自動映射時未知列或未知屬性處理策略,通過該配置可指定 MyBatis 在自動映射過程中遇到未知列或者未知屬性時如何處理,默認不做任何處理 configuration.setAutoMappingUnknownColumnBehavior(AutoMappingUnknownColumnBehavior.valueOf(props.getProperty("autoMappingUnknownColumnBehavior", "NONE"))); // 緩存 configuration.setCacheEnabled(booleanValueOf(props.getProperty("cacheEnabled"), true)); // proxyFactory (CGLIB | JAVASSIST),延遲加載的核心技術就是用代理模式,CGLIB/JAVASSIST 兩者選一 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)); // 允許 JDBC 支持生成的鍵 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)); // 是否將DB字段自動映射到駝峰式Java屬性(A_COLUMN-->aColumn) configuration.setMapUnderscoreToCamelCase(booleanValueOf(props.getProperty("mapUnderscoreToCamelCase"), false)); // 嵌套語句上使用RowBounds configuration.setSafeRowBoundsEnabled(booleanValueOf(props.getProperty("safeRowBoundsEnabled"), false)); // 默認用session級別的緩存 configuration.setLocalCacheScope(LocalCacheScope.valueOf(props.getProperty("localCacheScope", "SESSION"))); // 為 null 值設置 jdbctype configuration.setJdbcTypeForNull(JdbcType.valueOf(props.getProperty("jdbcTypeForNull", "OTHER"))); // Object 的哪些方法將觸發延遲加載 configuration.setLazyLoadTriggerMethods(stringSetValueOf(props.getProperty("lazyLoadTriggerMethods"), "equals,clone,hashCode,toString")); // 使用安全的 ResultHandler configuration.setSafeResultHandlerEnabled(booleanValueOf(props.getProperty("safeResultHandlerEnabled"), true)); // 動態 SQL 生成語言所使用的腳本語言 configuration.setDefaultScriptingLanguage(resolveClass(props.getProperty("defaultScriptingLanguage"))); // 默認枚舉類型處理器 configuration.setDefaultEnumTypeHandler(resolveClass(props.getProperty("defaultEnumTypeHandler"))); // 當結果集中含有 Null 值時是否執行映射對象的 setter 或者 Map 對象的 put 方法。此設置對於原始類型如 int,boolean 等無效 configuration.setCallSettersOnNulls(booleanValueOf(props.getProperty("callSettersOnNulls"), false)); // 允許使用方法簽名中的名稱作為語句參數名稱。使用該特性,工程必須采用 Java 8 編譯,並且加上-parameters選項。(從3.4.1開始) // 設置為true時傳遞參數需要使用 #{arg0}-#{argn}或者#{param1}-#{paramn},設置為false時 傳遞參數需要使用 #{0}-#{n}或者#{param1}-#{paramn} configuration.setUseActualParamName(booleanValueOf(props.getProperty("useActualParamName"), true)); // 當返回行的所有列都是空時,MyBatis默認返回null。當開啟時,MyBatis 會返回一個空實例。它也適用於嵌套的結果集(從3.4.2開始) configuration.setReturnInstanceForEmptyRow(booleanValueOf(props.getProperty("returnInstanceForEmptyRow"), false)); // logger 名字的前綴 configuration.setLogPrefix(props.getProperty("logPrefix")); // 配置工廠 configuration.setConfigurationFactory(resolveClass(props.getProperty("configurationFactory"))); } // 配置 SQL 映射器 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); // 映射器比較復雜,調用 XMLMapperBuilder,注意在 for 循環里每個 mapper 都重新 new 一個 XMLMapperBuilder 來解析 XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments()); mapperParser.parse(); } else if (resource == null && url != null && mapperClass == null) { // 使用絕對 url 路徑 ErrorContext.instance().resource(url); InputStream inputStream = Resources.getUrlAsStream(url); XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments()); mapperParser.parse(); } else if (resource == null && url == null && mapperClass != null) { // 使用 java 類名 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."); } } } } }
mapperElement(root.evalNode("mappers")) ---> mapperParser.parse()
org.apache.ibatis.builder.xml.XMLMapperBuilder
// 解析 SQL 映射文件 public void parse() { // 如果沒有加載過再加載,防止重復加載 if (!configuration.isResourceLoaded(resource)) { // 配置 mapper configurationElement(parser.evalNode("/mapper")); // 標記一下,已經加載過了 configuration.addLoadedResource(resource); // 綁定映射器到 namespace bindMapperForNamespace(); } parsePendingResultMaps(); parsePendingCacheRefs(); parsePendingStatements(); } private void configurationElement(XNode context) { try { // 1.配置 namespace String namespace = context.getStringAttribute("namespace"); if (namespace == null || namespace.equals("")) { throw new BuilderException("Mapper's namespace cannot be empty"); } builderAssistant.setCurrentNamespace(namespace); // 2.配置 cache-ref cacheRefElement(context.evalNode("cache-ref")); // 3.配置 cache cacheElement(context.evalNode("cache")); //4.配置 parameterMap (已經廢棄,老式風格的參數映射) parameterMapElement(context.evalNodes("/mapper/parameterMap")); //5.配置 resultMap (高級功能) resultMapElements(context.evalNodes("/mapper/resultMap")); //6.配置 sql (定義可重用的 SQL 代碼段) sqlElement(context.evalNodes("/mapper/sql")); //7.配置 select|insert|update|delete 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); } } // 7.配置 select|insert|update|delete private void buildStatementFromContext(List<XNode> list) { if (configuration.getDatabaseId() != null) { buildStatementFromContext(list, configuration.getDatabaseId()); } buildStatementFromContext(list, null); } // 7.1 構建語句 private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) { for (XNode context : list) { // 構建所有語句,一個 mapper 下可以有很多 select,語句比較復雜,核心都在這里面,所以調用 XMLStatementBuilder final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId); try { // 核心 XMLStatementBuilder.parseStatementNode statementParser.parseStatementNode(); } catch (IncompleteElementException e) { // 如果出現SQL語句不完整,把它記下來,放到 configuration 去 configuration.addIncompleteStatement(statementParser); } } } private void bindMapperForNamespace() { // 獲取 mapper 映射文件的命名空間 String namespace = builderAssistant.getCurrentNamespace(); if (namespace != null) { Class<?> boundType = null; try { // 創建 mapper 映射文件的代理接口,后續就可以 session.getMapper(xxxMapper.class) boundType = Resources.classForName(namespace); } catch (ClassNotFoundException e) { //ignore, bound type is not required } if (boundType != null) { // 將命名空間和代理接口添加至 configuration if (!configuration.hasMapper(boundType)) { // Spring may not know the real resource name so we set a flag // to prevent loading again this resource from the mapper interface // look at MapperAnnotationBuilder#loadXmlResource configuration.addLoadedResource("namespace:" + namespace); configuration.addMapper(boundType); } } } }
statementParser.parseStatementNode()
org.apache.ibatis.builder.xml.XMLStatementBuilder
// 解析語句(select|insert|update|delete) public void parseStatementNode() { String id = context.getStringAttribute("id"); String databaseId = context.getStringAttribute("databaseId"); // 如果databaseId不匹配,退出 if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) { return; } // 驅動程序每次批量返回的結果行數 Integer fetchSize = context.getIntAttribute("fetchSize"); // 超時時間 Integer timeout = context.getIntAttribute("timeout"); // 引用外部 parameterMap,已廢棄 String parameterMap = context.getStringAttribute("parameterMap"); // 參數類型 String parameterType = context.getStringAttribute("parameterType"); Class<?> parameterTypeClass = resolveClass(parameterType); // 引用外部的 resultMap(高級功能) String resultMap = context.getStringAttribute("resultMap"); // 結果類型 String resultType = context.getStringAttribute("resultType"); // 腳本語言,mybatis3.2 的新功能 String lang = context.getStringAttribute("lang"); // 得到語言驅動 LanguageDriver langDriver = getLanguageDriver(lang); Class<?> resultTypeClass = resolveClass(resultType); // 結果集類型,FORWARD_ONLY|SCROLL_SENSITIVE|SCROLL_INSENSITIVE 中的一種 String resultSetType = context.getStringAttribute("resultSetType"); // 語句類型, STATEMENT|PREPARED|CALLABLE 中的一種 StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString())); ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType); // 獲取命令類型(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); // 是否要緩存 select 結果 boolean useCache = context.getBooleanAttribute("useCache", isSelect); // 僅針對嵌套結果 select 語句適用:如果為 true,就是假設包含了嵌套結果集或是分組了,這樣的話當返回一個主結果行的時候,就不會發生有對前面結果集的引用的情況 // 這就使得在獲取嵌套的結果集的時候不至於導致內存不夠用。默認值:false boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false); // Include Fragments before parsing // 解析之前先解析<include>SQL片段 XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant); includeParser.applyIncludes(context.getNode()); // Parse selectKey after includes and remove them. // 解析之前先解析<selectKey> processSelectKeyNodes(id, parameterTypeClass, langDriver); // Parse the SQL (pre: <selectKey> and <include> were parsed and removed) // 解析成SqlSource,一般是 DynamicSqlSource SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass); String resultSets = context.getStringAttribute("resultSets"); // (僅對 insert 有用) 標記一個屬性, MyBatis 會通過 getGeneratedKeys 或者通過 insert 語句的 selectKey 子元素設置它的值 String keyProperty = context.getStringAttribute("keyProperty"); // (僅對 insert 有用) 標記一個屬性, MyBatis 會通過 getGeneratedKeys 或者通過 insert 語句的 selectKey 子元素設置它的值 String keyColumn = context.getStringAttribute("keyColumn"); 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; } // 調用助手類,最終添加到 configuration 的 mappedStatements 中 builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType, fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass, resultSetTypeEnum, flushCache, useCache, resultOrdered, keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets); }
時序圖

https://github.com/tuguangquan/mybatis/tree/master/src/main/java/org/apache/ibatis
