Mybatis启动源码分析

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/lu__peng/article/details/79098294

重新修改:2018.1.27更新

根据上一篇博文第一个Mybatis的例子为例,分析Mybatis在启动过程找那个都干了什么事情。
创建SqlSessionFactory实例
每一个Mybatis应用都是以一个SqlSessionFactory实例为中心的(官网上的话),因此要使用Mybatis框架的功能,必须首先通过SqlSessionFactoryBuilder获得该实例,获取方法如下:

new SqlSessionFactoryBuilder().build(is);

那么在获取SqlSessionFactory实例的过程中,Mybatis框架帮助我们做了什么工作呢,可以来看一看
1)调用SqlSessionFactoryBuilder中的build(InputStream inputStream)方法,该方法的内部结构为:

public SqlSessionFactory build(InputStream inputStream) {
        return this.build((InputStream)inputStream, (String)null, (Properties)null);
    }

2)可以看到在build方法内部调用了该方法的重载方法:

public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
        SqlSessionFactory var5;
        try {
            XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
            var5 = this.build(parser.parse());
        } catch (Exception var14) {
            throw ExceptionFactory.wrapException("Error building SqlSession.", var14);
        } finally {
            ErrorContext.instance().reset();

            try {
                inputStream.close();
            } catch (IOException var13) {
                ;
            }

        }

3)在该方法内部,首先创建一个XMLConfigBuilder的实例化对象parser,然后调用对象方法parse(),对配置文件进行读取获取必要的信息,具体代码如下:

private void parseConfiguration(XNode root) {
        try {
            this.propertiesElement(root.evalNode("properties"));
            this.typeAliasesElement(root.evalNode("typeAliases"));
            this.pluginElement(root.evalNode("plugins"));
            this.objectFactoryElement(root.evalNode("objectFactory"));
            this.objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
            this.settingsElement(root.evalNode("settings"));
            this.environmentsElement(root.evalNode("environments"));
            this.databaseIdProviderElement(root.evalNode("databaseIdProvider"));
            this.typeHandlerElement(root.evalNode("typeHandlers"));
            this.mapperElement(root.evalNode("mappers"));
        } catch (Exception var3) {
            throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + var3, var3);
        }
    }

在分析上边这段代码之前,有必要说明一下,Mybatis的xml配置文件中各个标签表示的含义:
这里写图片描述
根据上表可以分析上面的parse()方法,该方法会读取Mybatis的配置文件,并依次解析各个标签中的属性,获取配置数据,其中最重要的我认为是:

environmentsElement(root.evalNode("environments"));

下面来看看environmentsElement()方法干了什么事情,代码如下:

private void environmentsElement(XNode context) throws Exception {
   //省略部分代码...

   while(i$.hasNext()) {
        XNode child = (XNode)i$.next();
        String id = child.getStringAttribute("id");
        if (this.isSpecifiedEnvironment(id)) {
            //根据配置文件的数据源类型,生成对应的数据源工厂类,
            //由于在配置文件中配置的数据源为POOLED,因此获取
            //对应的数据源工厂类为PooledDataSourceFactory
            DataSourceFactory dsFactory = this.dataSourceElement(child.evalNode("dataSource"));
            //借助于该工厂类获取对应数据源对象,PooledDataSource 对象
            DataSource dataSource = dsFactory.getDataSource();
            Builder environmentBuilder = (new Builder(id)).transactionFactory(txFactory).dataSource(dataSource);
            this.configuration.setEnvironment(environmentBuilder.build());
        }
    }

}

上面的代码已经解释的很清楚了,目前mybatis中支持三种数据源:

  1. UNPOOLED 不使用连接池的数据源
  2. POOLED 使用连接池的数据源
  3. JNDI 使用JNDI实现的数据源(没有用过这个)

解析完配置文件之后会将配置信息保存在Configuration对象中,在将这个Configuration对象作为参数传入到build()方法中,返回一个SqlSessionFactory对象:

public SqlSessionFactory build(Configuration config) {
   return new DefaultSqlSessionFactory(config);
}

总结一下:在这一步主要完成的工作:读取mybatis配置文件;将配置信息存到Configuration对象中,实例化对应的数据源对象,生成一个SqlSessionFactory对象并返回给用户。

4)借助于SqlSessionFactory的openSession()方法生成一个SqlSession对象作为操作数据库的工具,负责具体的与数据库交互的操作(比如select,insert等等),Sql语句可以从映射文件中获取。

5)连接数据库

前4步完成之后,实际上还没有与数据库建立连接。我们知道通过JDBC连接数据库的方式是:

//加载驱动
Class.forName(name);
//获取连接数据库的Connection对象
conn = DriverManager.getConnection(url, user, password);
//作为操作数据库的工具,负责执行Sql语句
pst = conn.prepareStatement(sql);

实际上Mybatis内部也是通过JDBC方式进行连接的,神奇吧!
当用户通过SqlSession对象执行相关操作时,Mybatis才开始连接数据库,如执行selectOne(),selectList()等等。
那么来看看它是如何最终于数据库建立连接的(获得Connection对象就认为与数据库建立了连接):

当用户执行selectOne()方法时,实际上调用了DefaultSqlSession类的selectList()方法,代码如下:

public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
    List var6;
    try {
        MappedStatement ms = this.configuration.getMappedStatement(statement);
        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;
}

在selectList()内部执行了query()方法,经过一系列的方法调用最终会调用执行SimpleExecutor 的query()方法(是从BaseExecutor继承来的方法),具体可以通过dubug的方式看看在这个过程中到底经过了哪些方法,才最终调用了这个query方法,而SimpleExecutor 的query()方法的代码如下:

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());
        var9 = handler.query(stmt, resultHandler);
    } finally {
        this.closeStatement(stmt);
    }

    return var9;
}

query()方法内部调用prepareStatement()方法:

//看到了没有,Mybatis内部用于操作数据库的工具仍然是Statement
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
    //获取数据库连接的Connection对象
   Connection connection = this.getConnection(statementLog);
   //创建一个Statement对象
    Statement stmt = handler.prepare(connection);
    handler.parameterize(stmt);
    return stmt;
}

然后又调用了JdbcTransaction的openConnection()方法真正开始进行数据库连接,而且还会有日志信息提示。

protected void openConnection() throws SQLException {
    if (log.isDebugEnabled()) {
        log.debug("Openning JDBC Connection");
    }
    //由于这里使用的POOLED数据源,因此使用 PooledDataSource中的getConnection()进行数据库连接
    this.connection = this.dataSource.getConnection();
    if (this.level != null) {
        this.connection.setTransactionIsolation(this.level.getLevel());
    }
    this.setDesiredAutoCommit(this.autoCommmit);
}

使用 PooledDataSource中的getConnection()进行数据库连接代码如下:

private PooledConnection popConnection(String username, String password) throws SQLException {
    //...省略
    conn = new PooledConnection(this.dataSource.getConnection(), this);
    //...省略
}

在方法内部会调用this.dataSource.getConnection()方法,最终调用的方法是doGetConnection(),该方法是PooledDataSource从父类UnpooledDataSource继承来的:

private Connection doGetConnection(Properties properties) throws SQLException {
   this.initializeDriver();
    Connection connection = DriverManager.getConnection(this.url, properties);
    this.configureConnection(connection);
    return connection;
}

到此为止,真相大白,原来Mybatis内部任然采用的是JDBC连接数据方式,分析了一晚上,有收获!!!哈哈


新加的Mybatis内部是如何执行sql语句得到结果的???


6)在了解Mybatis内部执行Sql语句机制之前,先来看看JDBC时如何执行SQL语句的。

//利用Connection对象获取Statement对象,
//Statement里面带有很多方法,比如executeUpdate可以实现插入,更新和删除等
stmt = conn.createStatement()
/**利用Statement工具执行Sql语句*/
//返回一个受影响的行数,如果返回-1就没有更新成功
int result = stmt.executeUpdate(sql); 
//返回查询结果的集合,没有的话返回空值
ResultSet rs = stmt.executeQuery(sql);

上面的解释已经很清楚了吧,下面来看看Mybatis时怎么干的吧

先看看上面已经贴的一段代码:
当Mybatis获得Connection对象之后,会首先借助于Connection对象创建一个Statement对象,具体是由方法prepareStatement()完成的,上面有这个方法的源代码。
接下来看看这段源代码(实际上上面已经用过这段代码)

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);
        //获取Statement对象
        stmt = this.prepareStatement(handler, ms.getStatementLog());
        //负责Sql语句的执行
        var9 = handler.query(stmt, resultHandler);
    } finally {
        this.closeStatement(stmt);
    }
    return var9;
}

接下来看看负责执行sql语句的那行代码,会执行SimpleStatementHandler中的query()方法,看看代码:

public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
    //获取sql语句
   String sql = this.boundSql.getSql();
   //执行sql语句,看到了没有,实际上还是借助于Statement工具来执行SQL语句的,
   //statement就是刚刚在上面获取的那个
   statement.execute(sql);
   //返回执行sql语句后的结果集合形式,这个就是返回给用户的结果
   return this.resultSetHandler.handleResultSets(statement);
}

到此算是全部分析完成了。

参考

  1. 终结篇:MyBatis原理深入解析
  2. 《深入理解mybatis原理》 Mybatis数据源与连接池

如有不对之处,请指出,共同学习!

猜你喜欢

转载自blog.csdn.net/lu__peng/article/details/79098294