概述
重新修改: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中支持三种数据源:
- UNPOOLED 不使用连接池的数据源
- POOLED 使用连接池的数据源
- 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);
}
到此算是全部分析完成了。
参考
- 终结篇:MyBatis原理深入解析
- 《深入理解mybatis原理》 Mybatis数据源与连接池
如有不对之处,请指出,共同学习!
最后
以上就是眯眯眼心锁为你收集整理的Mybatis启动源码分析的全部内容,希望文章能够帮你解决Mybatis启动源码分析所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复