Mybatis源码系列 执行流程(一)

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语句

Tags: