Teach you step by step how to read mybatis source code (2) - Mybatis framework structure

1. What is Mybatis?

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

https://mybatis.org/mybatis-3/z h/index.html

The link above is the official website link, and a lot of configuration and usage information can be obtained there.
The definition of Mybatis is also very clear about its specific functions, that is, its purpose. When we look at the source code, we also focus on these points. I personally think that when looking at the source code, we should start from the macro structure to the specific micro level. After reading it, we can summarize how to realize the macro framework in terms of micro details.
I think Mybatis can be viewed from the following three aspects:
Insert image description here
Mybatis macro module:
1. The Mybatis environment configuration information module corresponds to the corresponding configuration information in (mybatis-config.xml)
2. Mapper interface information and mapper.xml file (also here It can be annotation) method and sql binding problem
3. Finally, the execution of sql, how do we call the corresponding interface method to be executed?

In my opinion, if you can understand these three points, you will have a deeper understanding of mybatis. Next, I will also read the source code from these three points.

Mybatis core classes:

**SqlSessionFactory:**Every MyBatis-based application is centered on an instance of SqlSessionFactory. Instances of SqlSessionFactory can be obtained through SqlSessionFactoryBuilder. The SqlSessionFactoryBuilder can build an instance of SqlSessionFactory from an XML configuration file or through Java. Once SqlSessionFactory is created, it should always exist during the running of the application. It is recommended to use singleton mode or static singleton mode. A SqlSessionFactory corresponds to an environment in the configuration file. If you want to use multiple databases, configure multiple environments to correspond to one SqlSessionFactory.

**SqlSession: **SqlSession is an interface, which has two implementation classes, namely DefaultSqlSession (used by default) and SqlSessionManager. SqlSession performs CRUD on data through the internally stored executor (Executor). In addition, SqlSession is not thread-safe, because close must be called to close the database after each operation. The official recommendation is to use try-finally to ensure that SqlSession is always closed.

**Executor: **The Executor (executor) interface has two implementation classes, of which BaseExecutor has three inheritance classes, namely BatchExecutor (reuse statements and perform batch updates), ReuseExecutor (reuse prepared statements, the only difference from Simple That is the internal cache statement), SimpleExecutor (by default, a new statement will be created every time). The above three are the main Executors. As you can see from the figure below, Mybatis uses the decorator mode in the design of Executor. We can use CachingExecutor to decorate the first three executors for the purpose of caching.
Insert image description here
**MappedStatement: **MappedStatement is used to store the information in our SQL mapping file including sql statements, input parameters, output parameters, etc. A SQL node corresponds to a MappedStatement object.

Execution process Insert image description here
https://blog.csdn.net/u010890358/article/details/80665753

2. Reading the configuration information of Mybatis source code

Through the first section, we will set up the environment for debugging the source code and start debugging to view the source code. When looking at the source code, use F7 to enter each method in the upper layer to view the calls, and directly use F8 to call the underlying methods of rt.jar. As long as you know the result returned by the calling method.
Breakpoint to see how Mybatis obtains the configuration file

//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
uses the parseConfiguration method in the XMLConfigBuilder method to parse the related ones. The inputStream stream is the mybatis-config.xml byte stream. The parse() method of XMLConfigBuilder parses the mybatis-config.xml node to get Configuration configuration class. There is a detailed analysis process below.

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

This method gets the environment configuration file
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);
        }
    }

Let’s take a look at how he parses environments. After F7 enters, we find that

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

The code of method environmentsElement is as follows:

//解析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.");
        }
    }

To obtain the DataSource, first obtain the environments node in the configuration file (mybatis-config.xml) through the XMLConfigBuilder#environmentsElement method. Then obtain the environment node, and then parse the content in the dataSource through XMLConfigBuilder#dataSourceElement. The content in the dataSource must exist in the Environment. in the DataSource variable.
The environment configuration information is read and stored in the Confiuartion variable
this.configuration.setEnvironment(environmentBuilder.build());

2.1. Analysis of the binding principle of Mapper interface and mapper.xml file

In the first section, the parseConfiguration method parses the mybatis file and has a method for parsing mappers nodes. Enter this method and the code is as follows:

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

Have you seen this?

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

The mapper resource, resource and automatic package scanning package in the configuration file can actually be written in four ways, but the parsing methods are different. We use resource by default. If you have time, you can study the other two.

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

mapperParser = new XMLMapperBuilder(inputStream, this.configuration, resource, this.configuration.getSqlFragments()); Obtain the mapper.xml resource file and use
mapperParser.parse() to parse the xml file; enter this method, parse the mapper file, and enter this.configurationElement(this.parser.evalNode("/mapper")); method. In this method, there is a method for parsing mapper.xml related attribute nodes. (Students who are not familiar with the XXXXmapper.xml file should read more about the standard 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"));Method details

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

Pay attention to the last org.apache.ibatis.builder.xml.XMLMapperBuilder#buildStatementFromContext(java.util.List<org.apache.ibatis.parsing.XNode>) method. F7 enters this method. Get all sql statements of crud.

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

The effect of the final analysis
Insert image description here

2.2 Create mappedStatement

Methods as below:

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

Insert image description here
We saw the Configuration class again, which runs through the mybatis framework. Personally, I think Configuration is the soul of mybatis. It holds almost all the information. Many classes also have this class. This also gives us a revelation that can reduce the complexity of the framework design. Difficulty, whatever resources you want can be found from this category.

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

Insert image description here
Here the id can explain why each select|upadate|insert|delete node of mapper.xml must be unique. Namespace+id is saved as a key value in the configuration label.

So far, mapper.xml has been parsed by location. How to bind it to the mapper interface? Please see
the this.bindMapperForNamespace(); method in the org.apache.ibatis.builder.xml.XMLMapperBuilder#parse method.

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

            }
        }

    }

So far, the initialization of mybatis and the creation of SqlSessionFactory class instances have been completed. Next is how we call the specific method to execute sql. Please see the next section
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

3. mybatis performs specific database operations

3.1 Obtaining sqlSession

To obtain the executor, the SimpleExecutor executor is used by default.

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 The calling process of mapper interface method

Execute the executor's getMapper to obtain the interface object instance PaymentMapper paymentMapper = session.getMapper(PaymentMapper.class);

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

Dynamic proxy obtains mapper interface instance

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

Call specific method

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

There are actually a few steps in between to obtain the corresponding method and sql. I have omitted them. Please read them more when you debug by yourself.

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

Insert image description hereAfter obtaining the mappedStatement, the corresponding sql statement is found based on the interface path and method. The next step is the process of executing the sql statement.

Insert image description here
execute sql

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

Where the sql statement is executed: org.apache.ibatis.binding.MapperMethod#execute
public Object execute(SqlSession sqlSession, Object[] args)
gets the parameter
param = this.method.convertArgsToSqlCommandParam(args);
executes the sql statement and returns the result
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;
        }
    }

Enter sqlSession to execute sql statements

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

Perform a search and enable caching. Mybatis caching is enabled by default.

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

Cache key value
Insert image description here
and execute 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;
        }
    }

// If there is no cache, search directly in the database. Do you think this is the last method? No, you are wrong. See the next method.

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

Then execute the doQuery method

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

It is packaged in n layers. Please be patient when you look at it for yourself. It may be much more intuitive if you take the time to draw a timing diagram.
Insert image description here
This is the whole process. I only debug the search. If you have time, you can also try the other SQL. This tutorial is mainly to provide you with an idea for reading the source code. Please correct me for any inaccuracies. You can try it if you think it helps.

Try to find time to draw a timing diagram

Guess you like

Origin blog.csdn.net/u010445301/article/details/106714919