I have been too busy recently and have not had time to continue updating the blog. Today I took a break from my busy schedule to continue my Mybatis learning journey. In the first nine articles, the configuration and use of mybatis were introduced, so this article will go into the source code of mybatis and analyze the execution process of mybatis. Well, I don’t like talking, so let’s go straight to work:
1. SqlSessionFactory and SqlSession.
Through the introduction and use of mybatis in the previous chapters, everyone can realize the importance of SqlSession. Yes, from the surface , we all execute sql statements through SqlSession (note: this is from the surface, the actual situation will be discussed later). So let’s take a look at how to get SqlSession:
(1) First, SqlSessionFactoryBuilder reads the configuration file of mybatis , and then build a DefaultSqlSessionFactory. The source code is as follows:
/** * 一系列的构造方法最终都会调用本方法(配置文件为Reader时会调用本方法,还有一个InputStream方法与此对应) * @param reader * @param environment * @param properties * @return */ public SqlSessionFactory build(Reader reader, String environment, Properties properties) { try { //通过XMLConfigBuilder解析配置文件,解析的配置相关信息都会封装为一个Configuration对象 XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties); //这儿创建DefaultSessionFactory对象 return build(parser.parse()); } catch (Exception e) { throw ExceptionFactory.wrapException("Error building SqlSession.", e); } finally { ErrorContext.instance().reset(); try { reader.close(); } catch (IOException e) { // Intentionally ignore. Prefer previous error. } } } public SqlSessionFactory build(Configuration config) { return new DefaultSqlSessionFactory(config); }
(2) After we obtain the SqlSessionFactory, we can obtain the SqlSession object through the SqlSessionFactory. The source code is as follows:
/** * 通常一系列openSession方法最终都会调用本方法 * @param execType * @param level * @param autoCommit * @return */ private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) { Transaction tx = null; try { //通过Confuguration对象去获取Mybatis相关配置信息, Environment对象包含了数据源和事务的配置 final Environment environment = configuration.getEnvironment(); final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment); tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit); //之前说了,从表面上来看,咱们是用sqlSession在执行sql语句, 实际呢,其实是通过excutor执行, excutor是对于Statement的封装 final Executor executor = configuration.newExecutor(tx, execType); //关键看这儿,创建了一个DefaultSqlSession对象 return new DefaultSqlSession(configuration, executor, autoCommit); } catch (Exception e) { closeTransaction(tx); // may have fetched a connection so lets call close() throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e); } finally { ErrorContext.instance().reset(); } }
Through the above steps, we have obtained the SqlSession object. The next step is to do whatever you have to do (what else can you do, of course, execute sql statements). After reading the above, let’s also think back to the Demo we wrote before.
SqlSessionFactory sessionFactory = null; String resource = "mybatis-conf.xml"; try { //SqlSessionFactoryBuilder读取配置文件 sessionFactory = new SqlSessionFactoryBuilder().build(Resources .getResourceAsReader(resource)); } catch (IOException e) { e.printStackTrace(); } //通过SqlSessionFactory获取SqlSessionSqlSession sqlSession = sessionFactory.openSession();
It’s really like this, right?
We also got SqlSession. We can call a series of select..., insert..., update..., delete... methods in SqlSession to easily perform CRUD operations. that's all? So where are the mapping files we configured? Don’t worry, let’s continue reading:
2. The sharp weapon MapperProxy:
In mybatis, dynamically proxy our dao through MapperProxy. In other words, when we execute the method in the dao we wrote, the corresponding mapperProxy is actually acting as the proxy. So, let’s take a look at how to get the MapperProxy object:
(1) Get from Configuration through SqlSession. The source code is as follows:
/** * 什么都不做,直接去configuration中找, 哥就是这么任性 */ @Override public <T> T getMapper(Class<T> type) { return configuration.<T>getMapper(type, this); }
(2) SqlSession throws the burden to Configuration, let’s take a look at Configuration next. The source code is as follows:
/** * 烫手的山芋,俺不要,你找mapperRegistry去要 * @param type * @param sqlSession * @return */ public <T> T getMapper(Class<T> type, SqlSession sqlSession) { return mapperRegistry.getMapper(type, sqlSession); }
(3) Configuration does not want this hot potato, and then dumps it to MapperRegistry, then let’s take a look at MapperRegistry. The source code is as follows:
/** * 烂活净让我来做了,没法了,下面没人了,我不做谁来做 * @param type * @param sqlSession * @return */ @SuppressWarnings("unchecked") public <T> T getMapper(Class<T> type, SqlSession sqlSession) { //能偷懒的就偷懒,俺把粗活交给MapperProxyFactory去做 final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type); if (mapperProxyFactory == null) { throw new BindingException("Type " + type + " is not known to the MapperRegistry."); } try { //关键在这儿 return mapperProxyFactory.newInstance(sqlSession); } catch (Exception e) { throw new BindingException("Error getting mapper instance. Cause: " + e, e); } }
(4)MapperProxyFactory是个苦B的人,粗活最终交给它去做了。咱们看看源码:
/** * 别人虐我千百遍,我待别人如初恋 * @param mapperProxy * @return */ @SuppressWarnings("unchecked") protected T newInstance(MapperProxy<T> mapperProxy) { //动态代理我们写的dao接口 return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy); } public T newInstance(SqlSession sqlSession) { final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache); return newInstance(mapperProxy); }
通过以上的动态代理,咱们就可以方便地使用dao接口啦, 就像之前咱们写的demo那样:
UserDao userMapper = sqlSession.getMapper(UserDao.class); User insertUser = new User();
这下方便多了吧, 呵呵, 貌似mybatis的源码就这么一回事儿啊。
别急,还没完, 咱们还没看具体是怎么执行sql语句的呢。
3. Excutor:
接下来,咱们才要真正去看sql的执行过程了。
上面,咱们拿到了MapperProxy, 每个MapperProxy对应一个dao接口, 那么咱们在使用的时候,MapperProxy是怎么做的呢? 源码奉上:
MapperProxy:
/** * MapperProxy在执行时会触发此方法 */ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if (Object.class.equals(method.getDeclaringClass())) { try { return method.invoke(this, args); } catch (Throwable t) { throw ExceptionUtil.unwrapThrowable(t); } } final MapperMethod mapperMethod = cachedMapperMethod(method); //二话不说,主要交给MapperMethod自己去管 return mapperMethod.execute(sqlSession, args); }
MapperMethod:
/** * 看着代码不少,不过其实就是先判断CRUD类型,然后根据类型去选择到底执行sqlSession中的哪个方法,绕了一圈,又转回sqlSession了 * @param sqlSession * @param args * @return */ public Object execute(SqlSession sqlSession, Object[] args) { Object result; if (SqlCommandType.INSERT == command.getType()) { Object param = method.convertArgsToSqlCommandParam(args); result = rowCountResult(sqlSession.insert(command.getName(), param)); } else if (SqlCommandType.UPDATE == command.getType()) { Object param = method.convertArgsToSqlCommandParam(args); result = rowCountResult(sqlSession.update(command.getName(), param)); } else if (SqlCommandType.DELETE == command.getType()) { Object param = method.convertArgsToSqlCommandParam(args); result = rowCountResult(sqlSession.delete(command.getName(), param)); } else if (SqlCommandType.SELECT == command.getType()) { if (method.returnsVoid() && method.hasResultHandler()) { executeWithResultHandler(sqlSession, args); result = null; } else if (method.returnsMany()) { result = executeForMany(sqlSession, args); } else if (method.returnsMap()) { result = executeForMap(sqlSession, args); } else { Object param = method.convertArgsToSqlCommandParam(args); result = sqlSession.selectOne(command.getName(), param); } } else { throw new BindingException("Unknown execution method for: " + command.getName()); } if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) { throw new BindingException("Mapper method '" + command.getName() + " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ")."); } return result; }
既然又回到SqlSession了, 那么咱们就看看SqlSession的CRUD方法了,为了省事,还是就选择其中的一个方法来做分析吧。这儿,咱们选择了selectList方法:
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) { try { MappedStatement ms = configuration.getMappedStatement(statement); //CRUD实际上是交给Excetor去处理, excutor其实也只是穿了个马甲而已,小样,别以为穿个马甲我就不认识你嘞! return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER); } catch (Exception e) { throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e); } finally { ErrorContext.instance().reset(); } }
然后,通过一层一层的调用,最终会来到doQuery方法, 这儿咱们就随便找个Excutor看看doQuery方法的实现吧,我这儿选择了SimpleExecutor:
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException { Statement stmt = null; try { Configuration configuration = ms.getConfiguration(); StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql); stmt = prepareStatement(handler, ms.getStatementLog()); //StatementHandler封装了Statement, 让 StatementHandler 去处理 return handler.<E>query(stmt, resultHandler); } finally { closeStatement(stmt); } }
接下来,咱们看看StatementHandler 的一个实现类 PreparedStatementHandler(这也是我们最常用的,封装的是PreparedStatement), 看看它使怎么去处理的:
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException { //到此,原形毕露, PreparedStatement, 这个大家都已经滚瓜烂熟了吧 PreparedStatement ps = (PreparedStatement) statement; ps.execute(); //结果交给了ResultSetHandler 去处理 return resultSetHandler.<E> handleResultSets(ps); }
到此, 一次sql的执行流程就完了。 我这儿仅抛砖引玉,建议有兴趣的去看看Mybatis3的源码。
好啦,本次就到此结束啦,最近太忙了, 又该忙去啦。
以上就是深入浅出Mybatis系列(十)---SQL执行流程分析(源码篇)的内容,更多相关内容请关注PHP中文网(www.php.cn)!