MyBatis-SqlSessionFactory 的創建(源碼)


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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM