欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

MyBatis框架源码解析

程序员文章站 2022-04-21 23:52:04
...

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<User> 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变量上;

(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<User> users = session.selectList("cn.itsource.mapper.UserMapper.getUser");

我们进入核心的selectList方法里面去:

public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
List var6;
try {
//从配置得到Mapper语句对象 –解析mapper.xml文件
MappedStatement ms = this.configuration.getMappedStatement(statement);
//通过执行器调用query方法 封装数据 返回结果
List<E> 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 <E> List<E> 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 <E> List<E> 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 <E> List<E> 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 <E> List<E> 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从加载配置文件到调用方法以及返回数据进行封装的整个过程; 从源码里面我们看出,框架底层分别使用很多类和接口来完成开发,而且具备单一职责的原则,一个类就做一个事情,通过层层组合,就完成整套流程,当然  这次分析源码,就是一部分内容。下次在继续;

转载于:https://www.jianshu.com/p/5fb93f0c4857