MyBatis框架源码解析

MyBatis是一个优秀的持久层框架,通过简洁的XML配置方式就能消除以前传统
使用JDBC出现大量重复的代码,以及参数的设置和结果集的映射. 如果能够懂的底层代码和原理,在使用上就会更得心应手。而且出现错误,也能更快的找出错误的原因。解析源码
还有个好处,就是能够看一下大神的设计思想。这些设计思想也可以运用到实际开发中,而且如果自己明白框架怎么写之后,自己也可以尝试去写一个开源框架出来; 同时,阅读解析源码,也是高级程序员必备的技能之一;
下面我们就从一个简单的示例开始源码的分析:

public static void main(String[] args) { String resource = "Configuration.xml"; Reader reader; try { //解析配置文件得到Reader reader = Resources.*getResourceAsReader*(resource); //通过配置文件得到SqlSessionFactory SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader); //得到SqlSession SqlSession session = sqlSessionFactory.openSession(); try { //调用查询方法 完成封装 List users = session.selectList("cn.itsource.mapper.UserMapper.getUser"); users.forEach(user->{ System.*out*.println(user); }); } finally { session.close(); } } catch (IOException e) { e.printStackTrace(); } }

(1) 第一步 得到SqlSessionFactory对象 ,关键方法 SqlSessionFactoryBuilder().build(reader).我们跟进源码SqlSessionFactoryBuilder类的build方法:
public SqlSessionFactory build(Reader reader, String environment, Properties properties) { SqlSessionFactory var5; try { // XMLConfigBuilder XML配置解析类 XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties); //parser.parse()解析结果放入Configuration里面 //build方法得到DefaultSqlSessionFactory var5 = this.build(parser.parse()); } catch (Exception var14) { throw ExceptionFactory.wrapException("Error building SqlSession.", var14); } finally { ErrorContext.instance().reset(); try { reader.close(); } catch (IOException var13) { ; } } return var5; }

通过build方法 最终得到DefaultSqlSessionFactory
public SqlSessionFactory build(Configuration config) { return new DefaultSqlSessionFactory(config); }

已经得到SqlSessionFactory对象了,而且把配置文件信息存入了DefaultSqlSessionFactory 这个类的config变量上;
【MyBatis框架源码解析】(2)通过 sqlSessionFactory.openSession()得到SqlSession对象
源码如下:
public SqlSession openSession() { 通过该方法得到SqlSession return this.openSessionFromDataSource(this.configuration.getDefaultExecutorType(), (TransactionIsolationLevel)null, false); }

openSessionFromDataSource方法如下:
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
DefaultSqlSession var8; try { //从配置类里面得到环境变量 Environment environment = this.configuration.getEnvironment(); //得到事务配置 TransactionFactory transactionFactory = this.getTransactionFactoryFromEnvironment(environment); //得到事务对象 tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit); //通过事务得到执行器 Executor executor = this.configuration.newExecutor(tx, execType, autoCommit); //通过执行器得到DefaultSqlSession对象-关键代码 var8 = new DefaultSqlSession(this.configuration, executor); } catch (Exception var12) { this.closeTransaction(tx); throw ExceptionFactory.wrapException("Error opening session.Cause: " + var12, var12); } finally { ErrorContext.instance().reset(); }return var8;

}
最终得到一个SqlSession对象(DefaultSqlSession是SqlSession实现类)(3)通过sqlSession对象调用SelectList方法返回数据

List users = session.selectList("cn.itsource.mapper.UserMapper.getUser");
我们进入核心的selectList方法里面去:

public List selectList(String statement, Object parameter, RowBounds rowBounds) {
List var6;
try {
//从配置得到Mapper语句对象 –解析mapper.xml文件
MappedStatement ms = this.configuration.getMappedStatement(statement);
//通过执行器调用query方法 封装数据 返回结果
List result = this.executor.query(ms, this.wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
var6 = result;
} catch (Exception var10) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + var10, var10);
} finally {
ErrorContext.instance().reset();
}
return var6;

}
Query方法最终调用BaseExecutor里面query方法:

public List query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
//动态解析sql
BoundSql boundSql = ms.getBoundSql(parameter);
//缓存
CacheKey key = this.createCacheKey(ms, parameter, rowBounds, boundSql);
//查询
return this.query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
在这个方法里面做了下面一些事情:(1) 解析动态的sql (2) 加入缓存里面 (3) 进行查询

public List query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
if (this.closed) {
throw new ExecutorException("Executor was closed.");
} else {
if (this.queryStack == 0 && ms.isFlushCacheRequired()) {
this.clearLocalCache();
}
List list; try { ++this.queryStack; list = resultHandler == null ? (List)this.localCache.getObject(key) : null; if (list != null) { this.handleLocallyCachedOutputParameters(ms, key, parameter, boundSql); } else { list = this.queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql); } } finally { --this.queryStack; }if (this.queryStack == 0) { Iterator i$ = this.deferredLoads.iterator(); while(i$.hasNext()) { BaseExecutor.DeferredLoad deferredLoad = (BaseExecutor.DeferredLoad)i$.next(); deferredLoad.load(); }this.deferredLoads.clear(); if (this.configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) { this.clearLocalCache(); } } return list; }

}
从上面的源码中可以看出最终调用:queryFromDatabase方法这个方法最终会调用SimpleExecutor的doQuery方法

public List doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
Statement stmt = null;
List var9; try { Configuration configuration = ms.getConfiguration(); StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, rowBounds, resultHandler, boundSql); stmt = this.prepareStatement(handler, ms.getStatementLog()); //通过handler调用query查询方法 var9 = handler.query(stmt, resultHandler); } finally { this.closeStatement(stmt); }return var9;

最终调用PreparedStatementHandler类的query方法

public List query(Statement statement, ResultHandler resultHandler) throws SQLException {
PreparedStatement ps = (PreparedStatement)statement;
ps.execute();
//处理结果的封装
return this.resultSetHandler.handleResultSets(ps);
}
大致的流程图:得到SqlSession的过程 ![源码时代](https://upload-images.jianshu.io/upload_images/13211021-7597f31d5a4e9f10.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)调用方法的过程 ![源码时代](https://upload-images.jianshu.io/upload_images/13211021-98a7170096fd3650.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)封装数据过程: ![源码时代](https://upload-images.jianshu.io/upload_images/13211021-5de519f221f00850.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)好了,这次源码分析就到这个地方; 通过这次源码剖析,我们看到了mybatis从加载配置文件到调用方法以及返回数据进行封装的整个过程; 从源码里面我们看出,框架底层分别使用很多类和接口来完成开发,而且具备单一职责的原则,一个类就做一个事情,通过层层组合,就完成整套流程,当然这次分析源码,就是一部分内容。下次在继续;

    推荐阅读