Mybatis原始碼系列 執行流程(一)

天中之雲發表於2021-12-31

1.Mybatis的使用

public static void main(String[] args) throws IOException {
    //1.獲取配置檔案流
    InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
    //2.構建SqlSessionFactory
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
    //3.建立SqlSession
    SqlSession sqlSession = sqlSessionFactory.openSession();
    //4.獲取mapper
    UnitMapper mapper = sqlSession.getMapper(UnitMapper.class);
    //5.執行增刪改查
    Unitinfo_source unitinfo_source = mapper.selectById(1);
    System.out.println(unitinfo_source);
}

這是我們在簡單使用mybatis時的程式碼,並未整合Spring,首先來看下Mybatis具體的執行流程是什麼。後續我們再看整合Spring後Mybatis做了什麼處理。以及Springboot中對Mybatis做了怎樣的處理。

2.執行流程

第一步:通過Resources獲取mybatis配置檔案的輸入流

Resources是Mybatis下的一個類,getResourceAsStream讀取到mybatis的配置檔案,以流的形式載入進來

第二步:構建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     }

呼叫了XMLConfigBuilder的parse()方法,具體看下這個方法是幹什麼的

 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     }

可以看到XMLConfigBuilder的parse()方法來解析配置檔案的配置資訊,可以看到,解析properties標籤,settings,typeAliases別名等等一些資訊,並返回一個Configuration;後續我們會看到,Configuration是Mybatis很重要的一個核心類,包括了很多執行過程中的上下文資訊,我們這裡的配置資訊被載入到了這個類中

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

通過呼叫SqlSessionFactory的build方法,返回了DefaultSqlSessionFactory物件,DefaultSqlSessionFactory是SqlSessionFactory的一個實現類

  第三步:建立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     }

首先獲取到剛才從配置檔案解析到事務,TransactionFactory建立出Transaction, 我們知道sql執行一般都會用到事務提交、回滾等操作。

建立Executor物件,executor是mybatis的核心執行器,用於執行增刪改查操作。一二級快取都在執行器中進行處理。

最後得到了SqlSession物件了,DefaultSqlSession是SqlSession的一個實現。

第四步:獲取Mapper

呼叫SqlSession的getMapper方法

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

然後可以發現呼叫了Configuration中的getMapper方法,再呼叫MapperRegistry(註冊mapper的類)獲取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);
            }
        }
    }

可以看到呼叫了mapperProxyFactory的newInstance方法

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     }

可以看到我們在SqlSession呼叫getMapper時,實際上是通過jdk的動態代理來實現的,通過Proxy.newProxyInstance來建立mapper代理物件

第五步:執行mapper的增刪改查方法

通過第四步中,我們知道了getMapper獲取到了代理物件,也就是說Proxy.newProxyInstance最後一個引數mapperProxy物件類必定實現了InvocationHandler的invoke方法,

 

 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     }

invoke方法中首先判斷了是否是普通類或者判斷是否是預設方法,如果是普通類,執行其預設方法;如果是預設defaut方法,執行相應的java8或java9相應方法(這個沒具體瞭解,如果感興趣可以自己深入瞭解);我們們主要是看19行, mapperMethod的excute方法

 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     }

這個就是我們核心執行增刪改查的方法,通過判斷 sql的執行型別,執行相應的方法,返回相應的結果

也許有人有疑問,是從哪裡解析到sql語句的呢,其實在解析配置檔案的時候就已經將sql解析到Configuration類物件中了,我們看下原始碼

 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     }

我們看到在16行,有this.mapperElement(root.evalNode("mappers"))來解析mapper節點資訊

 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     }

通過原始碼我們可以看到在16行以下,XMLBuilderMapper的parse方法解析mapper的xml檔案,在這個方法中會將sql解析。

其實在執行器執行的時候會根據解析到的id與sql的對映去拿到執行的SQL,再交由底層jdbc來執行相應的sql語句

相關文章