Mybatis源码解析——执行流程
吃了一年鱼,不知道是什么味道。这几天心血来潮手撸mybatis源码。
mybatis作为一个优秀的数据库框架,将sql语句等与java代码解耦,只要进行简单的配置,就可以对数据库进行操作。至于怎么配置,在我的其他篇博客有介绍,这里主要讲解执行流程。
一、核心对象创建
//就是一个简单的配置文件读取 SqlSessionFactoryBuilder facbd = new SqlSessionFactoryBuilder(); SqlSessionFactory fac=facbd.build(Resources.getResourceAsStream("xml")); SqlSession sqlsesssion = fac.openSession(); //sqlsession源码简化,想详细看搜方法名 //由下可以看出,就只是将配置文件通过XML解析后变为Java类,也就是Configuration核心对象。 public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) { SqlSessionFactory var5; var5 = this.build(parser.parse()); //调用本类的build方法 return va5 } public SqlSessionFactory build(Configuration config) { return new DefaultSqlSessionFactory(config); }
二、创建SqlSession对象,执行CRUD操作
sqlsession操作并不像标题那么简单,是相当的复杂!首先说明,不同的配置会产生的不同的核心对象,这里我所介绍的是开启二级缓存后的执行流程,因为涵盖的比较多,经此而已。
在讲解之前先介绍核心对象相关的几个重要对象。
-
TransactionFactory
事务工厂对象,通过此对象可创建Transaction对象,Transaction这里使用的实现是JdbcTransaction,这个对象实际是只是注入了DataSource与当前使用的Connection对象,底层实际也是通过这个对象获取Connection连接对像,再通过此对象对事务进行提交!
-
Excutor
Excutor接口相比就麻烦多了,此接口有两个直接子类CachingExecutor和BaseExecutor,BaseExecutor是一个抽象类,他的实现类有着更加偏底层的代码。通过核心对象可以创建Excutor的实现类对象。开启二级缓存后创建的Excutor,返回的是CachingExcutor,内部注入BaseExcutor的实现类,对其进行了增强,也就是一些添加缓存记录的操作。没有配置缓存,就直接是BaseExecutor的实现类。这个对象是通过核心对象创建,注入到SQL Session的对象中。
-
TransactionManager
处理缓存的数据的对象,通过TransactionalCache将sql查询的结果,再次通过Cache的实现类进行操纵。自定义的二级缓存就是通过实现Cache接口,核心配置类配置,才会提交到redis或内部的HashMap中。通过核心对象传入参数到TransactionFactory工厂创建事务管理器,通过此对象对mybatis框架的事务进行提交。(Spring中已删除,使用Spirng事务管理平台)
-
MappedStatement
我们在使用mybatis时候,总是要写mapper文件,mapper文件每一个sql语句标签(指select、update等标签)都会生成MappedStatement对象。此对象也有一个Cache属性,用来存储缓存的结果。详细了解可以看我的mybatis二级缓存理解。这个对象主要就是获取sql语句了。
sql语句主要可以分为四种,这里我只选其一,那就是select语句了.
demo= sqlSession.selectOne("statementId");
selectOne方法也是一层层的方法调用,最终调用此类的selectList方法调用注入的Excutor实现类CachingExecutor,使用的是query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler)方法,通过一些方法生成CacheKey,作为二级缓存(Map)的键,提高SqlSession多次相同的sql语句查询效率。
以下是通过CachingExecutor最终调用的方法。
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
Cache cache = ms.getCache();
if (cache != null) {
this.flushCacheIfRequired(ms);
if (ms.isUseCache() && resultHandler == null) {
this.ensureNoOutParams(ms, boundSql);
List<E> list = (List)this.tcm.getObject(cache, key);
if (list == null) {
list = this.delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
this.tcm.putObject(cache, key, list);
}
return list;
}
}
return this.delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
从上的方法中,可以看出CachingExcutor的执行流程大致先查看缓存中有没有数据,有数据,就直接从缓存中拿,这里是通过上面TransactionManager对象获取缓存;如过没有数据,再通过代理的Excutor(实质执行者,为BaseExcutor的实现类)执行query。
在BaseExcutor中实际调用的下面的方法。
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 var8 = this.deferredLoads.iterator();
while(var8.hasNext()) {
BaseExecutor.DeferredLoad deferredLoad = (BaseExecutor.DeferredLoad)var8.next();
deferredLoad.load();
}
this.deferredLoads.clear();
if (this.configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
this.clearLocalCache();
}
}
return list;
}
}
// 数据真实拉取调用的方法,此方法中有数据安全问题,具体会在下次博客中说。
private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
this.localCache.putObject(key, ExecutionPlaceholder.EXECUTION_PLACEHOLDER);
List list;
try {
list = this.doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
} finally {
this.localCache.removeObject(key);
}
this.localCache.putObject(key, list);
if (ms.getStatementType() == StatementType.CALLABLE) {
this.localOutputParameterCache.putObject(key, parameter);
}
return list;
}
由上面看出,也是与CachingExcutor雷同,内部也是封装了Excutor的子类。执行过程也是查看一级缓存中有没有缓存的结果,有就直接用;没有则从数据库拿。
下面也就是最后的调用了。
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.wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
stmt = this.prepareStatement(handler, ms.getStatementLog());
var9 = handler.query(stmt, resultHandler);
} finally {
this.closeStatement(stmt);
}
return var9;
}
// 通过TransactionManager获取连接对象,最后生成Statement返回。
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Connection connection = this.getConnection(statementLog);
Statement stmt = handler.prepare(connection, this.transaction.getTimeout());
handler.parameterize(stmt);
return stmt;
}
上面的方法相信学过jdbc的应该不陌生了,通过Statement执行sql语句逐级放回结果了。