mybatis のソースコードの読み方をステップバイステップで教えます (2) - Mybatis フレームワークの構造

1. マイバティスとは何ですか?

1、MyBatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。
2、MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。
3、MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。

https://mybatis.org/mybatis-3/zh/index.html

上記のリンクは公式 Web サイトのリンクであり、そこから多くの設定と使用方法の情報を入手できます。
Mybatis の定義は、その具体的な機能、つまり目的についても非常に明確であり、ソースコードを見るときもその点に焦点を当てています。個人的には、ソースコードを見るときは、マクロ構造から具体的なミクロレベルまで読み進めて、マクロの枠組みをどのように実現するかをミクロの細部でまとめるべきだと考えています。
Mybatis は次の 3 つの側面から見ることができると思います。
ここに画像の説明を挿入します
Mybatis マクロ モジュール:
1. Mybatis 環境構成情報モジュールは、(mybatis-config.xml) 内の対応する構成情報に対応します。
2. マッパー インターフェイス情報と mapper.xml ファイル (また、ここではアノテーション) メソッドと SQL バインディングの問題
3. 最後に、SQL の実行、実行される対応するインターフェイス メソッドをどのように呼び出すか?

この3点を理解していただければmybatisへの理解がさらに深まると思います。次にこの3点からもソースコードを読んでいきます。

Mybatis のコア クラス:

**SqlSessionFactory:**すべての MyBatis ベースのアプリケーションは、SqlSessionFactory のインスタンスを中心としています。SqlSessionFactory のインスタンスは、SqlSessionFactoryBuilder を通じて取得できます。SqlSessionFactoryBuilder は、XML 構成ファイルまたは Java を通じて SqlSessionFactory のインスタンスを構築できます。SqlSessionFactory が作成されると、アプリケーションの実行中は常に存在する必要があるため、シングルトン モードまたは静的シングルトン モードを使用することをお勧めします。SqlSessionFactory は、構成ファイル内の環境に対応します。複数のデータベースを使用する場合は、1 つの SqlSessionFactory に対応するように複数の環境を構成します。

**SqlSession: **SqlSession は、DefaultSqlSession (デフォルトで使用) と SqlSessionManager という 2 つの実装クラスを持つインターフェイスです。SqlSession は、内部に保存されたエグゼキューター (Executor) を通じてデータの CRUD を実行します。さらに、SqlSession はスレッドセーフではありません。各操作の後に close を呼び出してデータベースを閉じる必要があるためです。SqlSession が常に閉じられるようにするために、try-finally を使用することが公式に推奨されています。

**Executor: **Executor (エグゼキュータ) インターフェイスには 2 つの実装クラスがあり、そのうち BaseExecutor には 3 つの継承クラスがあります。つまり、BatchExecutor (ステートメントを再利用し、バッチ更新を実行します)、ReuseExecutor (準備されたステートメントを再利用します。Simple との唯一の違いは、内部キャッシュ ステートメント)、SimpleExecutor (デフォルトでは、毎回新しいステートメントが作成されます)。上記3名が主な執行者となります。下の図からわかるように、Mybatis は Executor の設計でデコレータ モードを使用しており、CachingExecutor を使用して、キャッシュの目的で最初の 3 つの Executor をデコレートできます。
ここに画像の説明を挿入します
**MappedStatement: **MappedStatement は、SQL ステートメント、入力パラメータ、出力パラメータなどの情報を SQL マッピング ファイルに保存するために使用されます。SQL ノードは MappedStatement オブジェクトに対応します。

実行プロセスここに画像の説明を挿入します
https://blog.csdn.net/u010890358/article/details/80665753

2. Mybatisソースコードの構成情報の読み込み

最初のセクションでは、ソース コードをデバッグするための環境をセットアップし、デバッグを開始してソース コードを表示します。ソース コードを確認するときは、F7 を使用して上位層の各メソッドを入力して呼び出しを表示し、F8 を直接使用して rt.jar の基になるメソッドを呼び出します (呼び出し側メソッドによって返された結果がわかっている限り)。
Mybatis が構成ファイルを取得する方法を確認するためのブレークポイント

//2、初始化mybatis,创建SqlSessionFactory类实例
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

org.apache.ibatis.session.SqlSessionFactoryBuilder#build(java.io.InputStream, java.lang.String, java.util.Properties) 调用
org.apache.ibatis.parsing.XPathParser#createDocument()

org.apache.ibatis.builder.xml.XMLConfigBuilder#parse は、
XMLConfigBuilder メソッドの parseConfiguration メソッドを使用して、関連するものを解析します。inputStream ストリームは、mybatis-config.xml バイト ストリームです。XMLConfigBuilder の parse() メソッドは、mybatis を解析します-config.xml ノードを使用して、Configuration 構成クラスを取得します。以下に詳細な分析プロセスがあります。

this.environmentsElement(root.evalNode("environments"));

このメソッドは、環境設定ファイル
org.apache.ibatis.builder.xml.XMLConfigBuilder#parseConfigurationを取得します。

private void parseConfiguration(XNode root) {
    
    
        try {
    
    
            this.propertiesElement(root.evalNode("properties"));
            Properties settings = this.settingsAsProperties(root.evalNode("settings"));
            this.loadCustomVfs(settings);
            this.loadCustomLogImpl(settings);
            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(settings);
            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);
        }
    }

彼が環境をどのように解析するかを見てみましょう。F7 キーを入力すると、次のことがわかります。

org.apache.ibatis.builder.xml.XMLConfigBuilder#environmentsElement 

メソッドenvironmentElementのコードは次のとおりです。

//解析environments
private void environmentsElement(XNode context) throws Exception {
    
    
        if (context != null) {
    
    
            if (this.environment == null) {
    
    
                this.environment = context.getStringAttribute("default");
            }

            Iterator var2 = context.getChildren().iterator();

            while(var2.hasNext()) {
    
    
                XNode child = (XNode)var2.next();
                String id = child.getStringAttribute("id");
                if (this.isSpecifiedEnvironment(id)) {
    
    
                    TransactionFactory txFactory = this.transactionManagerElement(child.evalNode("transactionManager"));
                    DataSourceFactory dsFactory = this.dataSourceElement(child.evalNode("dataSource"));
                    DataSource dataSource = dsFactory.getDataSource();
                    Builder environmentBuilder = (new Builder(id)).transactionFactory(txFactory).dataSource(dataSource);
                    this.configuration.setEnvironment(environmentBuilder.build());
                }
            }
        }

    } 
    //解析dataSource
private DataSourceFactory dataSourceElement(XNode context) throws Exception {
    
    
        if (context != null) {
    
    
            String type = context.getStringAttribute("type");
            Properties props = context.getChildrenAsProperties();
            DataSourceFactory factory = (DataSourceFactory)this.resolveClass(type).newInstance();
            factory.setProperties(props);
            return factory;
        } else {
    
    
            throw new BuilderException("Environment declaration requires a DataSourceFactory.");
        }
    }

DataSource を取得するには、まず XMLConfigBuilder#environmentsElement メソッドを使用して構成ファイル (mybatis-config.xml) 内の環境ノードを取得します。次に、環境ノードを取得して、XMLConfigBuilder#dataSourceElement を使用して dataSource のコンテンツを解析します。 dataSource は、Environment の DataSource 変数に存在する必要があります。
環境構成情報が読み取られ、構成変数
this.configuration.setEnvironment(environmentBuilder.build()); に保存されます。

2.1. Mapperインターフェースとmapper.xmlファイルの結合原理の分析

最初のセクションでは、parseConfiguration メソッドが mybatis ファイルを解析し、マッパー ノードを解析するメソッドを持っています。このメソッドを入力すると、コードは次のようになります。

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 inputStream;
                        if (resource != null && url == null && mapperClass == null) {
    
    
                            ErrorContext.instance().resource(resource);
                            inputStream = Resources.getResourceAsStream(resource);
                            mapperParser = new XMLMapperBuilder(inputStream, this.configuration, resource, this.configuration.getSqlFragments());
                            mapperParser.parse();
                        } else if (resource == null && url != null && mapperClass == null) {
    
    
                            ErrorContext.instance().resource(url);
                            inputStream = Resources.getUrlAsStream(url);
                            mapperParser = new XMLMapperBuilder(inputStream, 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;
            }
        }
    }

これを見たことがありますか?

 String resource = child.getStringAttribute("resource");
String url = child.getStringAttribute("url");
String mapperClass = child.getStringAttribute("class");

構成ファイル内のマッパー リソース、リソース、および自動パッケージ スキャン パッケージは、実際には 4 つの方法で記述できますが、解析方法が異なります。デフォルトではリソースを使用します。時間があれば、他の 2 つを学習できます。

<mappers>
        <mapper resource="mapper/PaymentMapper.xml"/>
    </mappers>

mapperParser = new XMLMapperBuilder(inputStream, this.configuration, resource, this.configuration.getSqlFragments()); mapper.xml リソース ファイルを取得し、
mapperParser.parse() を使用して xml ファイルを解析します。このメソッドを入力して、マッパー ファイルを解析しますを入力し、 this.configurationElement(this.parser.evalNode("/mapper")); メソッドを入力します。このメソッドには、mapper.xml 関連の属性ノードを解析するためのメソッドがあります。(XXXmapper.xml ファイルに詳しくない学生は、標準 XML について詳しく読んでください)

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();
    }

this.configurationElement(this.parser.evalNode("/mapper"));メソッドの詳細

 private void configurationElement(XNode context) {
    
    
        try {
    
    
            String namespace = context.getStringAttribute("namespace");
            if (namespace != null && !namespace.equals("")) {
    
    
                this.builderAssistant.setCurrentNamespace(namespace);
                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. The XML location is '" + this.resource + "'. Cause: " + var3, var3);
        }
    }

最後の org.apache.ibatis.builder.xml.XMLMapperBuilder#buildStatementFromContext(java.util.List<org.apache.ibatis.parsing.XNode>) メソッドに注目してください。F7 キーでこのメソッドに入ります。crud のすべての SQL ステートメントを取得します。

buildStatementFromContext(List list, String requiredDatabaseId);方法:

private void buildStatementFromContext(List<XNode> 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 {
    
    
            //解析sql语句,进入该方法
                statementParser.parseStatementNode();
            } catch (IncompleteElementException var7) {
    
    
                this.configuration.addIncompleteStatement(statementParser);
            }
        }

org.apache.ibatis.builder.xml.XMLStatementBuilder#parseStatementNode
parseStatementNode()

  public void parseStatementNode() {
    
    
  // id对应于mapper接口的方法,单个mapper.xml中必须唯一,全路径名加+id作为mappedStatement的可以值,所以必须唯一
        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", !isSelect);
            boolean useCache = this.context.getBooleanAttribute("useCache", isSelect);
            boolean resultOrdered = this.context.getBooleanAttribute("resultOrdered", false);
            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", this.configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType)) ? 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);
        }
    }

org.apache.ibatis.scripting.xmltags.XMLLanguageDriver#createSqlSource(org.apache.ibatis.session.Configuration, org.apache.ibatis.parsing.XNode, java.lang.Class<?>)

public SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType) {
    
    
        XMLScriptBuilder builder = new XMLScriptBuilder(configuration, script, parameterType);
        //1、解析节点,返回SqlSource
        return builder.parseScriptNode();
    }

 public SqlSource parseScriptNode() {
    
    
        MixedSqlNode rootSqlNode = this.parseDynamicTags(this.context);
        SqlSource sqlSource = null;
        if (this.isDynamic) {
    
    
            sqlSource = new DynamicSqlSource(this.configuration, rootSqlNode);
        } else {
    
    
        //2、进入RawSqlSource
            sqlSource = new RawSqlSource(this.configuration, rootSqlNode, this.parameterType);
        }

        return (SqlSource)sqlSource;
    }

 public RawSqlSource(Configuration configuration, String sql, Class<?> parameterType) {
    
    
        SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
        Class<?> clazz = parameterType == null ? Object.class : parameterType;
        //3、sql解析主要目的是将#{id}转换成?,这便到了大家熟悉的?问号参数占位符
        this.sqlSource = sqlSourceParser.parse(sql, clazz, new HashMap());
    }

 public SqlSource parse(String originalSql, Class<?> parameterType, Map<String, Object> additionalParameters) {
    
    
        SqlSourceBuilder.ParameterMappingTokenHandler handler = new SqlSourceBuilder.ParameterMappingTokenHandler(this.configuration, parameterType, additionalParameters);
        GenericTokenParser parser = new GenericTokenParser("#{", "}", handler);
        //4、真正替换是在这个方法中进行
        String sql = parser.parse(originalSql);
        return new StaticSqlSource(this.configuration, sql, handler.getParameterMappings());
    }

public String parse(String text) {
    
    
        if (text != null && !text.isEmpty()) {
    
    
            int start = text.indexOf(this.openToken);
            if (start == -1) {
    
    
                return text;
            } else {
    
    
                char[] src = text.toCharArray();
                int offset = 0;
                StringBuilder builder = new StringBuilder();

                for(StringBuilder expression = null; start > -1; start = text.indexOf(this.openToken, offset)) {
    
    
                    if (start > 0 && src[start - 1] == '\\') {
    
    
                        builder.append(src, offset, start - offset - 1).append(this.openToken);
                        offset = start + this.openToken.length();
                    } else {
    
    
                        if (expression == null) {
    
    
                            expression = new StringBuilder();
                        } else {
    
    
                            expression.setLength(0);
                        }

                        builder.append(src, offset, start - offset);
                        offset = start + this.openToken.length();

                        int end;
                        for(end = text.indexOf(this.closeToken, offset); end > -1; end = text.indexOf(this.closeToken, offset)) {
    
    
                            if (end <= offset || src[end - 1] != '\\') {
    
    
                                expression.append(src, offset, end - offset);
                                int var10000 = end + this.closeToken.length();
                                break;
                            }

                            expression.append(src, offset, end - offset - 1).append(this.closeToken);
                            offset = end + this.closeToken.length();
                        }

                        if (end == -1) {
    
    
                            builder.append(src, start, src.length - start);
                            offset = src.length;
                        } else {
    
    
//5、添加问号?占位符                            builder.append(this.handler.handleToken(expression.toString()));
                            offset = end + this.closeToken.length();
                        }
                    }
                }

                if (offset < src.length) {
    
    
                    builder.append(src, offset, src.length - offset);
                }

                return builder.toString();
            }
        } else {
    
    
            return "";
        }
    }

最后会跑到这个方法里
public String handleToken(String content) {
    
    
            Object parameter = this.context.getBindings().get("_parameter");
            if (parameter == null) {
    
    
                this.context.getBindings().put("value", (Object)null);
            } else if (SimpleTypeRegistry.isSimpleType(parameter.getClass())) {
    
    
                this.context.getBindings().put("value", parameter);
            }

            Object value = OgnlCache.getValue(content, this.context.getBindings());
            // 5、获取?
            String srtValue = value == null ? "" : String.valueOf(value);
            this.checkInjection(srtValue);
            return srtValue;
        }

最終分析の効果
ここに画像の説明を挿入します

2.2 mappedStatement の作成

以下のような方法:

public Builder(Configuration configuration, String id, SqlSource sqlSource, SqlCommandType sqlCommandType) {
    
    
            this.mappedStatement.configuration = configuration;
            this.mappedStatement.id = id;
            this.mappedStatement.sqlSource = sqlSource;
            this.mappedStatement.statementType = StatementType.PREPARED;
            this.mappedStatement.resultSetType = ResultSetType.DEFAULT;
            this.mappedStatement.parameterMap = (new org.apache.ibatis.mapping.ParameterMap.Builder(configuration, "defaultParameterMap", (Class)null, new ArrayList())).build();
            this.mappedStatement.resultMaps = new ArrayList();
            this.mappedStatement.sqlCommandType = sqlCommandType;
            this.mappedStatement.keyGenerator = (KeyGenerator)(configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType) ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE);
            String logId = id;
            if (configuration.getLogPrefix() != null) {
    
    
                logId = configuration.getLogPrefix() + id;
            }

            this.mappedStatement.statementLog = LogFactory.getLog(logId);
            this.mappedStatement.lang = configuration.getDefaultScriptingLanguageInstance();
        }

ここに画像の説明を挿入します
mybatis フレームワークを介して実行される Configuration クラスをもう一度見ました。個人的には、Configuration は mybatis の魂だと思います。Configuration には、ほぼすべての情報が含まれています。多くのクラスにもこのクラスがあります。これにより、複雑さを軽減できる啓示も得られます。フレームワーク設計の難易度に関係なく、必要なリソースがこのカテゴリから見つかります。

public MappedStatement addMappedStatement(String id, SqlSource sqlSource, StatementType statementType, SqlCommandType sqlCommandType, Integer fetchSize, Integer timeout, String parameterMap, Class<?> parameterType, String resultMap, Class<?> resultType, ResultSetType resultSetType, boolean flushCache, boolean useCache, boolean resultOrdered, KeyGenerator keyGenerator, String keyProperty, String keyColumn, String databaseId, LanguageDriver lang, String resultSets) {
    
    
        if (this.unresolvedCacheRef) {
    
    
            throw new IncompleteElementException("Cache-ref not yet resolved");
        } else {
    
    
            id = this.applyCurrentNamespace(id, false);
            boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
            org.apache.ibatis.mapping.MappedStatement.Builder statementBuilder = (new org.apache.ibatis.mapping.MappedStatement.Builder(this.configuration, id, sqlSource, sqlCommandType)).resource(this.resource).fetchSize(fetchSize).timeout(timeout).statementType(statementType).keyGenerator(keyGenerator).keyProperty(keyProperty).keyColumn(keyColumn).databaseId(databaseId).lang(lang).resultOrdered(resultOrdered).resultSets(resultSets).resultMaps(this.getStatementResultMaps(resultMap, resultType, id)).resultSetType(resultSetType).flushCacheRequired((Boolean)this.valueOrDefault(flushCache, !isSelect)).useCache((Boolean)this.valueOrDefault(useCache, isSelect)).cache(this.currentCache);
            ParameterMap statementParameterMap = this.getStatementParameterMap(parameterMap, parameterType, id);
            if (statementParameterMap != null) {
    
    
                statementBuilder.parameterMap(statementParameterMap);
            }

            MappedStatement statement = statementBuilder.build();
          //保存mappedStatement到configuration中的mappedStatements中
          this.configuration.addMappedStatement(statement);
            return statement;
        }
    }

ここに画像の説明を挿入します
ここで、id は、mapper.xml の各 select|upadate|insert|delete ノードが一意である必要がある理由を説明できます。名前空間 + id は、構成ラベルのキー値として保存されます。

これまでのところ、mapper.xml は場所によって解析されていますが、これをマッパー インターフェイスにバインドするにはどうすればよいですか?
org.apache.ibatis.builder.xml.XMLMapperBuilder#parse メソッドの this.bindMapperForNamespace(); メソッドを参照してください。

private void bindMapperForNamespace() {
    
    
        String namespace = this.builderAssistant.getCurrentNamespace();
        if (namespace != null) {
    
    
            Class boundType = null;

            try {
    
    
                boundType = Resources.classForName(namespace);
            } catch (ClassNotFoundException var4) {
    
    
                ;
            }

            if (boundType != null && !this.configuration.hasMapper(boundType)) {
    
    
                this.configuration.addLoadedResource("namespace:" + namespace);
                //1、添加绑定interface
                this.configuration.addMapper(boundType);
            }
        }

    }
MapperRegistry的绑定方法,如果该接口已经加载则不会第二次加载
并爆已经被注册异常:"Type " + type + " is already known to the MapperRegistry
public <T> void addMapper(Class<T> type) {
    
    
        if (type.isInterface()) {
    
    
            if (this.hasMapper(type)) {
    
    
                throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
            }

            boolean loadCompleted = false;

            try {
    
    
                this.knownMappers.put(type, new MapperProxyFactory(type));
                MapperAnnotationBuilder parser = new MapperAnnotationBuilder(this.config, type);
                parser.parse();
                loadCompleted = true;
            } finally {
    
    
                if (!loadCompleted) {
    
    
                    this.knownMappers.remove(type);
                }

            }
        }

    }

ここまでで、mybatis の初期化と SqlSessionFactory クラスのインスタンスの作成が完了しました。次に、SQL を実行するための特定のメソッドを呼び出す方法です。次のセクションを参照してください。
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

3. mybatis は特定のデータベース操作を実行します

3.1 sqlSessionの取得

エグゼキュータを取得するには、デフォルトで SimpleExecutor エグゼキュータが使用されます。

private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
    
    
        Transaction tx = null;

        DefaultSqlSession var8;
        try {
    
    
            Environment environment = this.configuration.getEnvironment();
            TransactionFactory transactionFactory = this.getTransactionFactoryFromEnvironment(environment);
            //1、事务级别,及开启与否
            tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
            //2、sql执行器
            Executor executor = this.configuration.newExecutor(tx, execType);
            var8 = new DefaultSqlSession(this.configuration, executor, autoCommit);
        } catch (Exception var12) {
    
    
            this.closeTransaction(tx);
            throw ExceptionFactory.wrapException("Error opening session.  Cause: " + var12, var12);
        } finally {
    
    
            ErrorContext.instance().reset();
        }

        return var8;
    }

3.2 マッパーインターフェースメソッドの呼び出し処理

エグゼキュータの getMapper を実行して、インターフェイス オブジェクト インスタンス PaymentMapper を取得します。

  public <T> T getMapper(Class<T> type) {
    
    
  		//又是这个configuration进去看看如何获取的
        return this.configuration.getMapper(type, this);
    }
 public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    
    
        return this.mapperRegistry.getMapper(type, sqlSession);
    }

動的プロキシがマッパー インターフェイス インスタンスを取得する

 public T newInstance(SqlSession sqlSession) {
    
    
        MapperProxy<T> mapperProxy = new MapperProxy(sqlSession, this.mapperInterface, this.methodCache);
        //1、动态代理获取接口实例
        return this.newInstance(mapperProxy);
    }
```java
 protected T newInstance(MapperProxy<T> mapperProxy) {
    
    
 //2、返回
        return Proxy.newProxyInstance(this.mapperInterface.getClassLoader(), new Class[]{
    
    this.mapperInterface}, mapperProxy);
    }

特定のメソッドを呼び出す

private MapperMethod cachedMapperMethod(Method method) {
    
    
        return (MapperMethod)this.methodCache.computeIfAbsent(method, (k) -> {
    
    
            return new MapperMethod(this.mapperInterface, method, this.sqlSession.getConfiguration());
        });
    }


 public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
    
    
        this.command = new MapperMethod.SqlCommand(config, mapperInterface, method);
        this.method = new MapperMethod.MethodSignature(config, mapperInterface, method);
    }

実際には、対応するメソッドや SQL を取得するまでにいくつかの手順がありますが、省略しましたので、自分でデバッグする場合は詳しく読んでください。

 private MappedStatement resolveMappedStatement(Class<?> mapperInterface, String methodName, Class<?> declaringClass, Configuration configuration) {
    
    
            String statementId = mapperInterface.getName() + "." + methodName;
            if (configuration.hasStatement(statementId)) {
    
    
                return configuration.getMappedStatement(statementId);
            } else if (mapperInterface.equals(declaringClass)) {
    
    
                return null;
            } else {
    
    
                Class[] var6 = mapperInterface.getInterfaces();
                int var7 = var6.length;

                for(int var8 = 0; var8 < var7; ++var8) {
    
    
                    Class<?> superInterface = var6[var8];
                    if (declaringClass.isAssignableFrom(superInterface)) {
    
    
                        MappedStatement ms = this.resolveMappedStatement(superInterface, methodName, declaringClass, configuration);
                        if (ms != null) {
    
    
                            return ms;
                        }
                    }
                }

                return null;
            }
        }
    }

ここに画像の説明を挿入しますmappedStatement を取得したら、インターフェイスのパスとメソッドに基づいて対応する SQL ステートメントを見つけ、次のステップは SQL ステートメントを実行するプロセスです。

ここに画像の説明を挿入します
SQLを実行する

return mapperMethod.execute(this.sqlSession, args);

SQL ステートメントが実行される場所: org.apache.ibatis.binding.MapperMethod#execute
public Objectexecute(SqlSession sqlSession, Object[] args)
パラメーターを取得します
param = this.method.convertArgsToSqlCommandParam(args);
SQL ステートメントを実行して戻ります結果
result = sqlSession.selectOne(this.command.getName(), param);

public Object execute(SqlSession sqlSession, Object[] args) {
    
    
        Object result;
        Object param;
        switch(this.command.getType()) {
    
    
        case INSERT:
            param = this.method.convertArgsToSqlCommandParam(args);
            result = this.rowCountResult(sqlSession.insert(this.command.getName(), param));
            break;
        case UPDATE:
            param = this.method.convertArgsToSqlCommandParam(args);
            result = this.rowCountResult(sqlSession.update(this.command.getName(), param));
            break;
        case DELETE:
            param = this.method.convertArgsToSqlCommandParam(args);
            result = this.rowCountResult(sqlSession.delete(this.command.getName(), param));
            break;
        case SELECT:
            if (this.method.returnsVoid() && this.method.hasResultHandler()) {
    
    
                this.executeWithResultHandler(sqlSession, args);
                result = null;
            } else if (this.method.returnsMany()) {
    
    
                result = this.executeForMany(sqlSession, args);
            } else if (this.method.returnsMap()) {
    
    
                result = this.executeForMap(sqlSession, args);
            } else if (this.method.returnsCursor()) {
    
    
                result = this.executeForCursor(sqlSession, args);
            } else {
    
    
                param = this.method.convertArgsToSqlCommandParam(args);
                result = sqlSession.selectOne(this.command.getName(), param);
                if (this.method.returnsOptional() && (result == null || !this.method.getReturnType().equals(result.getClass()))) {
    
    
                    result = Optional.ofNullable(result);
                }
            }
            break;
        case FLUSH:
            result = sqlSession.flushStatements();
            break;
        default:
            throw new BindingException("Unknown execution method for: " + this.command.getName());
        }

        if (result == null && this.method.getReturnType().isPrimitive() && !this.method.returnsVoid()) {
    
    
            throw new BindingException("Mapper method '" + this.command.getName() + " attempted to return null from a method with a primitive return type (" + this.method.getReturnType() + ").");
        } else {
    
    
            return result;
        }
    }

sqlSession を入力して SQL ステートメントを実行します

public <T> T selectOne(String statement, Object parameter) {
    
    
        List<T> list = this.selectList(statement, parameter);
        if (list.size() == 1) {
    
    
            return list.get(0);
        } else if (list.size() > 1) {
    
    
            throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size());
        } else {
    
    
            return null;
        }
    }

検索を実行し、キャッシュを有効にします。Mybatis キャッシュはデフォルトで有効になっています。

public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
    
    
        BoundSql boundSql = ms.getBoundSql(parameterObject);
        CacheKey key = this.createCacheKey(ms, parameterObject, rowBounds, boundSql);
        return this.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
    }
    

キー値をキャッシュし
ここに画像の説明を挿入します
てSQLを実行し、

public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    
    
        ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
        if (this.closed) {
    
    
            throw new ExecutorException("Executor was closed.");
        } else {
    
    
            if (this.queryStack == 0 && ms.isFlushCacheRequired()) {
    
    
                this.clearLocalCache();
            }

            List list;
            try {
    
    
                ++this.queryStack;
                list = resultHandler == null ? (List)this.localCache.getObject(key) : null;
                if (list != null) {
    
    
                    this.handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
                } else {
    
    
                // 没有缓存则直接在数据库中查找,你以为这是最后的方法吗,不你错了,看接下来的方法
                    list = this.queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
                }
            } finally {
    
    
                --this.queryStack;
            }

            if (this.queryStack == 0) {
    
    
                Iterator var8 = this.deferredLoads.iterator();

                while(var8.hasNext()) {
    
    
                    BaseExecutor.DeferredLoad deferredLoad = (BaseExecutor.DeferredLoad)var8.next();
                    deferredLoad.load();
                }

                this.deferredLoads.clear();
                if (this.configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
    
    
                    this.clearLocalCache();
                }
            }

            return list;
        }
    }

// キャッシュがない場合は、データベースを直接検索します。これが最後の方法だと思いますか? いいえ、違います。次の方法を参照してください。

private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    
    
		//1、存入缓存,用的是private Map<Object, Object> cache = new HashMap();
        this.localCache.putObject(key, ExecutionPlaceholder.EXECUTION_PLACEHOLDER);

        List list;
        try {
    
    
            list = this.doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
        } finally {
    
    
            this.localCache.removeObject(key);
        }

        this.localCache.putObject(key, list);
        if (ms.getStatementType() == StatementType.CALLABLE) {
    
    
            this.localOutputParameterCache.putObject(key, parameter);
        }

        return list;
    }

次に doQuery メソッドを実行します

 public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
    
    
        Statement stmt = null;

        List var9;
        try {
    
    
        //1、获取配置文件
            Configuration configuration = ms.getConfiguration();
            //2、获取PreparedStatementHandler
            StatementHandler handler = configuration.newStatementHandler(this.wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
            //3、获取prepareStatement
            stmt = this.prepareStatement(handler, ms.getStatementLog());
            //4、执行
            var9 = handler.query(stmt, resultHandler);
        } finally {
    
    
            this.closeStatement(stmt);
        }

        return var9;
    }
    
  public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
    
    
        PreparedStatement ps = (PreparedStatement)statement;
		//执行sql数据查找
        ps.execute();
        //结果处理
        return this.resultSetHandler.handleResultSets(ps);
    }

これは n 層にパッケージ化されています。自分で見るときはしばらくお待ちください。時間をかけてタイミング ダイアグラムを描くと、より直感的に理解できるかもしれません。
ここに画像の説明を挿入します
これがプロセス全体です。検索のデバッグのみを行います。時間があれば、他の SQL も試すことができます。このチュートリアルは主に、ソース コードを読むためのアイデアを提供することを目的としています。不正確な点があれば修正してください。役立つと思われる場合は試してみてください。

時間を見つけてタイミング図を描くようにしてください

おすすめ

転載: blog.csdn.net/u010445301/article/details/106714919
おすすめ