Mybatis 系列10-结合源码解析mybatis 的执行流程

【Mybatis 系列10-结合源码解析mybatis 执行流程】

【Mybatis 系列9-强大的动态sql 语句】

【Mybatis 系列8-结合源码解析select、resultMap的用法】 

【Mybatis 系列7-结合源码解析核心CRUD配置及用法】

【Mybatis 系列6-结合源码解析节点配置objectFactory、databaseIdProvider、plugins、mappers】

【Mybatis 系列5-结合源码解析TypeHandler】 

【Mybatis 系列4-结合源码解析节点typeAliases】

【Mybatis 系列3-结合源码解析properties节点和environments节点】

【Mybatis 系列2-配置文件】

【Mybatis 系列1-环境搭建】

 

 

 

在前九篇中,介绍了mybatis的配置以及使用,

那么本篇将走进mybatis的源码,分析mybatis 的执行流程

Mybatis 系列10-结合源码解析mybatis 的执行流程_第1张图片

1. SqlSessionFactory 与 SqlSession.

通过前面的章节对于mybatis 的介绍及使用,大家都能体会到SqlSession的重要性了吧, 没错,从表面上来看,咱们都是通过SqlSession去执行sql语句(注意:是从表面看,实际的待会儿就会讲)。那么咱们就先看看是怎么获取SqlSession的吧:

(1)首先,SqlSessionFactoryBuilder去读取mybatis的配置文件,然后build一个DefaultSqlSessionFactory。源码如下:

 1 /**
 2    * 一系列的构造方法最终都会调用本方法(配置文件为Reader时会调用本方法,还有一个InputStream方法与此对应)
 3    * @param reader
 4    * @param environment
 5    * @param properties
 6    * @return
 7    */
 8   public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
 9     try {
10       //通过XMLConfigBuilder解析配置文件,解析的配置相关信息都会封装为一个Configuration对象
11       XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
12       //这儿创建DefaultSessionFactory对象
13       return build(parser.parse());
14     } catch (Exception e) {
15       throw ExceptionFactory.wrapException("Error building SqlSession.", e);
16     } finally {
17       ErrorContext.instance().reset();
18       try {
19         reader.close();
20       } catch (IOException e) {
21         // Intentionally ignore. Prefer previous error.
22       }
23     }
24   }
25 
26   public SqlSessionFactory build(Configuration config) {
27     return new DefaultSqlSessionFactory(config);
28   }

 

(2)当我们获取到SqlSessionFactory之后,就可以通过SqlSessionFactory去获取SqlSession对象。

源码如下:

 1 /**
 2    * 通常一系列openSession方法最终都会调用本方法
 3    * @param execType 
 4    * @param level
 5    * @param autoCommit
 6    * @return
 7    */
 8   private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
 9     Transaction tx = null;
10     try {
11       //通过Confuguration对象去获取Mybatis相关配置信息, Environment对象包含了数据源和事务的配置
12       final Environment environment = configuration.getEnvironment();
13       final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
14       tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
15       //之前说了,从表面上来看,咱们是用sqlSession在执行sql语句, 实际呢,其实是通过excutor执行, excutor是对于Statement的封装
16       final Executor executor = configuration.newExecutor(tx, execType);
17       //关键看这儿,创建了一个DefaultSqlSession对象
18       return new DefaultSqlSession(configuration, executor, autoCommit);
19     } catch (Exception e) {
20       closeTransaction(tx); // may have fetched a connection so lets call close()
21       throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
22     } finally {
23       ErrorContext.instance().reset();
24     }
25   }

通过以上步骤,咱们已经得到SqlSession对象了。接下来就是该干嘛干嘛去了(话说还能干嘛,当然是执行sql语句咯)。

 

看了上面,咱们也回想一下之前写的Demo,

 1 SqlSessionFactory sessionFactory = null;  
 2 String resource = "mybatis-conf.xml";  
 3 try {
 4      //SqlSessionFactoryBuilder读取配置文件
 5     sessionFactory = new SqlSessionFactoryBuilder().build(Resources  
 6               .getResourceAsReader(resource));
 7 } catch (IOException e) {  
 8     e.printStackTrace();  
 9 }    
10 //通过SqlSessionFactory获取SqlSession
11 SqlSession sqlSession = sessionFactory.openSession();

还真这么一回事儿,对吧! SqlSession咱们也拿到了,咱们可以调用SqlSession中一系列的select..., insert..., update..., delete...方法轻松自如的进行CRUD操作了。

就这样? 那咱配置的映射文件去哪儿了?

 
2. 利器之MapperProxy:

在mybatis中,通过MapperProxy动态代理咱们的dao,

也就是说, 当咱们执行自己写的dao里面的方法的时候,其实是对应的mapperProxy在代理。

那么,咱们就看看怎么获取MapperProxy对象吧: (1)通过SqlSession从Configuration中获取。

源码如下:

 

1 /**
2    * 什么都不做,直接去configuration中找, 哥就是这么任性
3    */
4   @Override
5   public  T getMapper(Class type) {
6     return configuration.getMapper(type, this);
7   }

 

(2)SqlSession把包袱甩给了Configuration, 接下来就看看Configuration。

源码如下:

 

1 /**
2    * 烫手的山芋,俺不要,你找mapperRegistry去要
3    * @param type
4    * @param sqlSession
5    * @return
6    */
7   public  T getMapper(Class type, SqlSession sqlSession) {
8     return mapperRegistry.getMapper(type, sqlSession);
9   }

 

(3)Configuration不要这烫手的山芋,接着甩给了MapperRegistry,那咱看看MapperRegistry。

源码如下:

 1 /**
 2    * 烂活净让我来做了,没法了,下面没人了,我不做谁来做
 3    * @param type
 4    * @param sqlSession
 5    * @return
 6    */
 7   @SuppressWarnings("unchecked")
 8   public  T getMapper(Class type, SqlSession sqlSession) {
 9     //能偷懒的就偷懒,俺把粗活交给MapperProxyFactory去做
10     final MapperProxyFactory mapperProxyFactory = (MapperProxyFactory) knownMappers.get(type);
11     if (mapperProxyFactory == null) {
12       throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
13     }
14     try {
15       //关键在这儿
16       return mapperProxyFactory.newInstance(sqlSession);
17     } catch (Exception e) {
18       throw new BindingException("Error getting mapper instance. Cause: " + e, e);
19     }
20   }

 

(4)MapperProxyFactory是个苦B的人,粗活最终交给它去做了。

咱们看看源码:

 1 /**
 2    * 别人虐我千百遍,我待别人如初恋
 3    * @param mapperProxy
 4    * @return
 5    */
 6   @SuppressWarnings("unchecked")
 7   protected T newInstance(MapperProxy mapperProxy) {
 8     //动态代理我们写的dao接口
 9     return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
10   }
11   
12   public T newInstance(SqlSession sqlSession) {
13     final MapperProxy mapperProxy = new MapperProxy(sqlSession, mapperInterface, methodCache);
14     return newInstance(mapperProxy);
15   }

通过以上的动态代理,咱们就可以方便地使用dao接口啦, 就像之前咱们写的demo那样:

UserDao userMapper = sqlSession.getMapper(UserDao.class);
User insertUser = new User();

还没完, 咱们还没看具体是怎么执行sql语句的呢。

 
3. Excutor:

接下来,咱们才要真正去看sql的执行过程了。

上面,咱们拿到了MapperProxy, 每个MapperProxy对应一个dao接口,

那么咱们在使用的时候,MapperProxy是怎么做的呢?

 

源码奉上:

 1 MapperProxy:
 2 
 3 /**
 4    * MapperProxy在执行时会触发此方法
 5    */
 6   @Override
 7   public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
 8     if (Object.class.equals(method.getDeclaringClass())) {
 9       try {
10         return method.invoke(this, args);
11       } catch (Throwable t) {
12         throw ExceptionUtil.unwrapThrowable(t);
13       }
14     }
15     final MapperMethod mapperMethod = cachedMapperMethod(method);
16     //二话不说,主要交给MapperMethod自己去管
17     return mapperMethod.execute(sqlSession, args);
18   }

 

MapperMethod:

 1  /**
 2    * 看着代码不少,不过其实就是先判断CRUD类型,然后根据类型去选择到底执行sqlSession中的哪个方法,绕了一圈,又转回sqlSession了
 3    * @param sqlSession
 4    * @param args
 5    * @return
 6    */
 7   public Object execute(SqlSession sqlSession, Object[] args) {
 8     Object result;
 9     if (SqlCommandType.INSERT == command.getType()) {
10       Object param = method.convertArgsToSqlCommandParam(args);
11       result = rowCountResult(sqlSession.insert(command.getName(), param));
12     } else if (SqlCommandType.UPDATE == command.getType()) {
13       Object param = method.convertArgsToSqlCommandParam(args);
14       result = rowCountResult(sqlSession.update(command.getName(), param));
15     } else if (SqlCommandType.DELETE == command.getType()) {
16       Object param = method.convertArgsToSqlCommandParam(args);
17       result = rowCountResult(sqlSession.delete(command.getName(), param));
18     } else if (SqlCommandType.SELECT == command.getType()) {
19       if (method.returnsVoid() && method.hasResultHandler()) {
20         executeWithResultHandler(sqlSession, args);
21         result = null;
22       } else if (method.returnsMany()) {
23         result = executeForMany(sqlSession, args);
24       } else if (method.returnsMap()) {
25         result = executeForMap(sqlSession, args);
26       } else {
27         Object param = method.convertArgsToSqlCommandParam(args);
28         result = sqlSession.selectOne(command.getName(), param);
29       }
30     } else {
31       throw new BindingException("Unknown execution method for: " + command.getName());
32     }
33     if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
34       throw new BindingException("Mapper method '" + command.getName() 
35           + " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
36     }
37     return result;
38   }

 

既然又回到SqlSession了, 那么咱们就看看SqlSession的CRUD方法了,为了省事,还是就选择其中的一个方法来做分析吧。这儿,咱们选择了selectList方法:

 1 public  List selectList(String statement, Object parameter, RowBounds rowBounds) {
 2     try {
 3       MappedStatement ms = configuration.getMappedStatement(statement);
 4       //CRUD实际上是交给Excetor去处理, excutor其实也只是穿了个马甲而已,小样,别以为穿个马甲我就不认识你嘞!
 5       return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
 6     } catch (Exception e) {
 7       throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
 8     } finally {
 9       ErrorContext.instance().reset();
10     }
11   }

 

然后,通过一层一层的调用,最终会来到doQuery方法, 这儿咱们就随便找个Excutor看看doQuery方法的实现吧,我这儿选择了SimpleExecutor:

 1 public  List doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
 2     Statement stmt = null;
 3     try {
 4       Configuration configuration = ms.getConfiguration();
 5       StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
 6       stmt = prepareStatement(handler, ms.getStatementLog());
 7       //StatementHandler封装了Statement, 让 StatementHandler 去处理
 8       return handler.query(stmt, resultHandler);
 9     } finally {
10       closeStatement(stmt);
11     }
12   }

 

接下来,咱们看看StatementHandler 的一个实现类 PreparedStatementHandler(这也是我们最常用的,封装的是PreparedStatement), 看看它使怎么去处理的:

1 public  List query(Statement statement, ResultHandler resultHandler) throws SQLException {
2      //到此,原形毕露, PreparedStatement, 这个大家都已经滚瓜烂熟了吧
3     PreparedStatement ps = (PreparedStatement) statement;
4     ps.execute();
5     //结果交给了ResultSetHandler 去处理
6     return resultSetHandler. handleResultSets(ps);
7   }

到此, 一次sql的执行流程就完了。

 

 

一只阿木木仅抛砖引玉,有兴趣的建议看看Mybatis3的源码。

 

欢迎关注公众号:一只阿木木

你可能感兴趣的:(Mybatis 系列10-结合源码解析mybatis 的执行流程)