我是靠谱客的博主 眯眯眼心锁,最近开发中收集的这篇文章主要介绍Mybatis启动源码分析,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

重新修改: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数据源与连接池

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

最后

以上就是眯眯眼心锁为你收集整理的Mybatis启动源码分析的全部内容,希望文章能够帮你解决Mybatis启动源码分析所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(68)

评论列表共有 0 条评论

立即
投稿
返回
顶部