Mybatis source code series execution process

1. Use of mybatis

public static void main(String[] args) throws IOException {
    //1.Get profile stream
    InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
    //2.structure SqlSessionFactory
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
    //3.establish SqlSession
    SqlSession sqlSession = sqlSessionFactory.openSession();
    //4.obtain mapper
    UnitMapper mapper = sqlSession.getMapper(UnitMapper.class);
    //5.Execute addition, deletion, modification and query
    Unitinfo_source unitinfo_source = mapper.selectById(1);
    System.out.println(unitinfo_source);
}

This is the code when we simply use mybatis without integrating Spring. First, let's take a look at the specific execution process of mybatis. Later, let's look at what mybatis has done after integrating Spring. And how to deal with mybatis in Springboot.

2. Execution process

Step 1: get the input stream of mybatis configuration file through Resources

Resources is a class under mybatis. getResourceAsStream reads the configuration file of mybatis and loads it in the form of stream

Step 2: build SqlSessionFactory

 1     public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
 2         SqlSessionFactory var5;
 3         try {
 4             XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
 5             var5 = this.build(parser.parse());
 6         } catch (Exception var14) {
 7             throw ExceptionFactory.wrapException("Error building SqlSession.", var14);
 8         } finally {
 9             ErrorContext.instance().reset();
10 
11             try {
12                 inputStream.close();
13             } catch (IOException var13) {
14             }
15 
16         }
17 
18         return var5;
19     }

The parse() method of XMLConfigBuilder is called. See what this method does

 1     public Configuration parse() {
 2         if (this.parsed) {
 3             throw new BuilderException("Each XMLConfigBuilder can only be used once.");
 4         } else {
 5             this.parsed = true;
 6             this.parseConfiguration(this.parser.evalNode("/configuration"));
 7             return this.configuration;
 8         }
 9     }
10 
11     private void parseConfiguration(XNode root) {
12         try {
13             this.propertiesElement(root.evalNode("properties"));
14             Properties settings = this.settingsAsProperties(root.evalNode("settings"));
15             this.loadCustomVfs(settings);
16             this.loadCustomLogImpl(settings);
17             this.typeAliasesElement(root.evalNode("typeAliases"));
18             this.pluginElement(root.evalNode("plugins"));
19             this.objectFactoryElement(root.evalNode("objectFactory"));
20             this.objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
21             this.reflectorFactoryElement(root.evalNode("reflectorFactory"));
22             this.settingsElement(settings);
23             this.environmentsElement(root.evalNode("environments"));
24             this.databaseIdProviderElement(root.evalNode("databaseIdProvider"));
25             this.typeHandlerElement(root.evalNode("typeHandlers"));
26             this.mapperElement(root.evalNode("mappers"));
27         } catch (Exception var3) {
28             throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + var3, var3);
29         }
30     }

You can see the parse() method of XMLConfigBuilder to parse the Configuration information of the Configuration file. You can see that you can parse the properties tag, settings, typeAliases alias and other information, and return a Configuration; Later, we will see that Configuration is a very important core class of Mybatis, including a lot of context information during execution. The Configuration information here is loaded into this class

1     public SqlSessionFactory build(Configuration config) {
2         return new DefaultSqlSessionFactory(config);
3     }

By calling the build method of SqlSessionFactory, the DefaultSqlSessionFactory object is returned. DefaultSqlSessionFactory is an implementation class of SqlSessionFactory

Step 3: create SqlSession

1     public SqlSession openSession() {
2         return this.openSessionFromDataSource(this.configuration.getDefaultExecutorType(), (TransactionIsolationLevel)null, false);
3     }
 1     private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
 2         Transaction tx = null;
 3 
 4         DefaultSqlSession var8;
 5         try {
 6             Environment environment = this.configuration.getEnvironment();
 7             TransactionFactory transactionFactory = this.getTransactionFactoryFromEnvironment(environment);
 8             tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
 9             Executor executor = this.configuration.newExecutor(tx, execType);
10             var8 = new DefaultSqlSession(this.configuration, executor, autoCommit);
11         } catch (Exception var12) {
12             this.closeTransaction(tx);
13             throw ExceptionFactory.wrapException("Error opening session.  Cause: " + var12, var12);
14         } finally {
15             ErrorContext.instance().reset();
16         }
17 
18         return var8;
19     }

First, we get that the Transaction was parsed from the configuration file and created by the TransactionFactory. We know that Transaction submission, rollback and other operations are generally used in sql execution.

Create an executor object. Executor is the core executor of mybatis, which is used to add, delete, modify and query. The first and second level caches are processed in the executor.

Finally, the SqlSession object is obtained. DefaultSqlSession is an implementation of SqlSession.

Step 4: get Mapper

Call getMapper method of SqlSession

1     public <T> T getMapper(Class<T> type) {
2         return this.configuration.getMapper(type, this);
3     }
    public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
        return this.mapperRegistry.getMapper(type, sqlSession);
    }

Then you can find that the getMapper method in Configuration is called, and then MapperRegistry (the class that registers mapper) is called to obtain mapper

    public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
        MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory)this.knownMappers.get(type);
        if (mapperProxyFactory == null) {
            throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
        } else {
            try {
                return mapperProxyFactory.newInstance(sqlSession);
            } catch (Exception var5) {
                throw new BindingException("Error getting mapper instance. Cause: " + var5, var5);
            }
        }
    }

You can see that the newInstance method of mapperProxyFactory is called

1     protected T newInstance(MapperProxy<T> mapperProxy) {
2         return Proxy.newProxyInstance(this.mapperInterface.getClassLoader(), new Class[]{this.mapperInterface}, mapperProxy);
3     }
4 
5     public T newInstance(SqlSession sqlSession) {
6         MapperProxy<T> mapperProxy = new MapperProxy(sqlSession, this.mapperInterface, this.methodCache);
7         return this.newInstance(mapperProxy);
8     }

We can see that when calling getMapper in SqlSession, we actually implement it through the dynamic proxy of jdk, through proxy Newproxyinstance to create a mapper proxy object

Step 5: add, delete, modify and query mapper

In step 4, we know that getMapper obtains the proxy object, that is, proxy The mapperProxy object class of the last parameter of newproxyinstance must implement the invoke method of InvocationHandler,

 

 1     public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
 2         try {
 3             if (Object.class.equals(method.getDeclaringClass())) {
 4                 return method.invoke(this, args);
 5             }
 6 
 7             if (method.isDefault()) {
 8                 if (privateLookupInMethod == null) {
 9                     return this.invokeDefaultMethodJava8(proxy, method, args);
10                 }
11 
12                 return this.invokeDefaultMethodJava9(proxy, method, args);
13             }
14         } catch (Throwable var5) {
15             throw ExceptionUtil.unwrapThrowable(var5);
16         }
17 
18         MapperMethod mapperMethod = this.cachedMapperMethod(method);
19         return mapperMethod.execute(this.sqlSession, args);
20     }

In the invoke method, first judge whether it is a normal class or whether it is the default method. If it is a normal class, execute its default method; If it is the default defaut method, execute the corresponding java8 or java9 methods (I don't know this specifically, but I can learn more if I'm interested); let's mainly look at line 19, the extract method of mapperMethod

 1     public Object execute(SqlSession sqlSession, Object[] args) {
 2         Object result;
 3         Object param;
 4         switch(this.command.getType()) {
 5         case INSERT:
 6             param = this.method.convertArgsToSqlCommandParam(args);
 7             result = this.rowCountResult(sqlSession.insert(this.command.getName(), param));
 8             break;
 9         case UPDATE:
10             param = this.method.convertArgsToSqlCommandParam(args);
11             result = this.rowCountResult(sqlSession.update(this.command.getName(), param));
12             break;
13         case DELETE:
14             param = this.method.convertArgsToSqlCommandParam(args);
15             result = this.rowCountResult(sqlSession.delete(this.command.getName(), param));
16             break;
17         case SELECT:
18             if (this.method.returnsVoid() && this.method.hasResultHandler()) {
19                 this.executeWithResultHandler(sqlSession, args);
20                 result = null;
21             } else if (this.method.returnsMany()) {
22                 result = this.executeForMany(sqlSession, args);
23             } else if (this.method.returnsMap()) {
24                 result = this.executeForMap(sqlSession, args);
25             } else if (this.method.returnsCursor()) {
26                 result = this.executeForCursor(sqlSession, args);
27             } else {
28                 param = this.method.convertArgsToSqlCommandParam(args);
29                 result = sqlSession.selectOne(this.command.getName(), param);
30                 if (this.method.returnsOptional() && (result == null || !this.method.getReturnType().equals(result.getClass()))) {
31                     result = Optional.ofNullable(result);
32                 }
33             }
34             break;
35         case FLUSH:
36             result = sqlSession.flushStatements();
37             break;
38         default:
39             throw new BindingException("Unknown execution method for: " + this.command.getName());
40         }
41 
42         if (result == null && this.method.getReturnType().isPrimitive() && !this.method.returnsVoid()) {
43             throw new BindingException("Mapper method '" + this.command.getName() + " attempted to return null from a method with a primitive return type (" + this.method.getReturnType() + ").");
44         } else {
45             return result;
46         }
47     }

This is our core method of adding, deleting, modifying and querying. By judging the execution type of sql, execute the corresponding methods and return the corresponding results

Some people may have questions about where to parse the sql statement. In fact, when parsing the Configuration file, the sql has been parsed into the Configuration class object. Let's look at the source code

 1     private void parseConfiguration(XNode root) {
 2         try {
 3             this.propertiesElement(root.evalNode("properties"));
 4             Properties settings = this.settingsAsProperties(root.evalNode("settings"));
 5             this.loadCustomVfs(settings);
 6             this.loadCustomLogImpl(settings);
 7             this.typeAliasesElement(root.evalNode("typeAliases"));
 8             this.pluginElement(root.evalNode("plugins"));
 9             this.objectFactoryElement(root.evalNode("objectFactory"));
10             this.objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
11             this.reflectorFactoryElement(root.evalNode("reflectorFactory"));
12             this.settingsElement(settings);
13             this.environmentsElement(root.evalNode("environments"));
14             this.databaseIdProviderElement(root.evalNode("databaseIdProvider"));
15             this.typeHandlerElement(root.evalNode("typeHandlers"));
16             this.mapperElement(root.evalNode("mappers"));
17         } catch (Exception var3) {
18             throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + var3, var3);
19         }
20     }

We see this in line 16 Mapperelement (root. Evalnode ("mappers") to parse mapper node information

 1     private void mapperElement(XNode parent) throws Exception {
 2         if (parent != null) {
 3             Iterator var2 = parent.getChildren().iterator();
 4 
 5             while(true) {
 6                 while(var2.hasNext()) {
 7                     XNode child = (XNode)var2.next();
 8                     String resource;
 9                     if ("package".equals(child.getName())) {
10                         resource = child.getStringAttribute("name");
11                         this.configuration.addMappers(resource);
12                     } else {
13                         resource = child.getStringAttribute("resource");
14                         String url = child.getStringAttribute("url");
15                         String mapperClass = child.getStringAttribute("class");
16                         XMLMapperBuilder mapperParser;
17                         InputStream inputStream;
18                         if (resource != null && url == null && mapperClass == null) {
19                             ErrorContext.instance().resource(resource);
20                             inputStream = Resources.getResourceAsStream(resource);
21                             mapperParser = new XMLMapperBuilder(inputStream, this.configuration, resource, this.configuration.getSqlFragments());
22                             mapperParser.parse();
23                         } else if (resource == null && url != null && mapperClass == null) {
24                             ErrorContext.instance().resource(url);
25                             inputStream = Resources.getUrlAsStream(url);
26                             mapperParser = new XMLMapperBuilder(inputStream, this.configuration, url, this.configuration.getSqlFragments());
27                             mapperParser.parse();
28                         } else {
29                             if (resource != null || url != null || mapperClass == null) {
30                                 throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
31                             }
32 
33                             Class<?> mapperInterface = Resources.classForName(mapperClass);
34                             this.configuration.addMapper(mapperInterface);
35                         }
36                     }
37                 }
38 
39                 return;
40             }
41         }
42     }

Through the source code, we can see that the parse method of xmlbuilder mapper parses the mapper's xml file below 16 lines. In this method, sql will be parsed.

In fact, when the executor executes, it will get the executed SQL according to the resolved id and SQL mapping, and then hand it over to the underlying jdbc to execute the corresponding SQL statements

Keywords: Mybatis

Added by jingato on Sat, 01 Jan 2022 03:13:58 +0200