MyBatis源码分析

MyBatis源码分析是一个系列,旨在深入了解MyBatis执行流程以及原理。本篇我们先大体的看下MyBatis整个的执行流程,对MyBatis整体有个了解,然后我们在找几个主题深入的去讨论,比如缓存、连接池等,希望通过MyBatis系列,对你有所帮助,那么我们开始吧!

首先每一个框架都会有一个入口,让我们可以很容易的拿来即用,同样也可以沿着入口,了解这个框架的整体流程,然后在深扒底层的原理细节。MyBatis官网提供给我们如何在项目中使用它。

1
2
3
4
5
6
7
8
9
10
11
String resource = "org/mybatis/example/mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

SqlSession session = sqlSessionFactory.openSession();
try {
BlogMapper mapper = session.getMapper(BlogMapper.class);
Blog blog = mapper.selectBlog(101);
} finally {
session.close();
}

上述代码是一个典型的MyBatis查询语句,那我们从这几句话开始我们的分析。

首先创建了一个SqlSessionFactory,也就是MyBatis的入口。它的主要功能就是创建SqlSession

SqlSessionFactyoryBuilder类中根据提供的XML配置,创建了一个DefaultSqlSessionFactory的实例

通过SqlSessionFactory打开一个SqlSession,首先我们去看下SqlSession接口的描述

1
2
3
SqlSession用于处理MyBatis的主Java接口。

通过此接口,您可以执行命令,获取映射器和管理事务。

SqlSession的方法都是常用的增删改查,事务的回滚提交,以及缓存等,该接口是MyBatis操作的核心。

那我们去看下刚才代码中创建DefaultSqlSessionFactory实例是如何打开一个SqlSession

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
try {
final Environment environment = configuration.getEnvironment();
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
final Executor executor = configuration.newExecutor(tx, execType);
return new DefaultSqlSession(configuration, executor, autoCommit);
} catch (Exception e) {
closeTransaction(tx);
throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}

方法返回一个DefaultSqlSession实例,一起创建还有TransactionFactoryExecutor实例,跟每个SqlSession绑定。

ExcutorType可以通过XML配置中的setting.defaultExecutorType来自定义。如果没有在配置中指定,会设置一个默认值,在方法XMLConfigBuilder.settingsElement()中可以找到相应的代码。

TransactionFactory的创建是通过Environment.getTransactionFactory()方法来实现的。同样也可以使用xml中setting.transactionManager进行配置配置的。Environment的实例化可以在XMLConfigBuilder.environmentsElement()找到,根据提供的type对应的Class对象反射创建一个TransactionFactory

1
2
3
4
5
6
7
8
9
10
private TransactionFactory transactionManagerElement(XNode context) throws Exception {
if (context != null) {
String type = context.getStringAttribute("type");
Properties props = context.getChildrenAsProperties();
TransactionFactory factory = (TransactionFactory) resolveClass(type).newInstance();
factory.setProperties(props);
return factory;
}
throw new BuilderException("Environment declaration requires a TransactionFactory.");
}

XML中setting.transactionManager的配置是JDBC,在TypeAliasRegistry.resolveAlias方法中解析到指定的类。

其中使用的TYPE_ALIASES是一个Map,在TypeAliasRegistry的构造函数中已经初始化了一部分数据,但是我们并没有看到JDBC的key,我们在继续深入查看调用TypeAliasRegistry.registerAlias方法的地方,可以看到Configuration的构造函数中设置了该值:

1
typeAliasRegistry.registerAlias("JDBC", JdbcTransactionFactory.class);

所以上面TransactionFactory实例了一个JdbcTransactionFactory的对象,并根据数据源、事务隔离级别、是否自动提交事务,创建了一个Transaction实例,Transaction包装了数据库连接,处理连接生命周期,包括:创建,准备,提交/回滚和关闭。

接下来创建了一个Executor

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
executorType = executorType == null ? defaultExecutorType : executorType;
executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
Executor executor;
if (ExecutorType.BATCH == executorType) {
executor = new BatchExecutor(this, transaction);
} else if (ExecutorType.REUSE == executorType) {
executor = new ReuseExecutor(this, transaction);
} else {
executor = new SimpleExecutor(this, transaction);
}
if (cacheEnabled) {
executor = new CachingExecutor(executor);
}
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}

cacheEnabled可以通过xml中settings.cacheEnabled配置,默认为true,可以在XMLConfigBuilder.settingsElement方法中找到相应代码:

1
configuration.setCacheEnabled(booleanValueOf(props.getProperty("cacheEnabled"), true));

创建了一个CachingExecutor实例。最后将三个参数组合创建了一个DefaultSqlSession实例。

然后我们就走到真正的查询是怎么进行的了,那我们继续看代码,我们找一个最简单的selectOne去看下。

1
2
3
4
5
6
7
8
9
10
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
try {
MappedStatement ms = configuration.getMappedStatement(statement);
return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}

MappedStatement是读取在xml中配置的Mapper信息,然后解析里面的insert/update/delete/select语句创建的实例。具体实现代码可以在XMLConfigBuilder.parseConfiguration()方法中找到。executor我们在上面提到是创建的CachingExecutor实例,所以代码走到该类的query方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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) {
flushCacheIfRequired(ms);
if (ms.isUseCache() && resultHandler == null) {
ensureNoOutParams(ms, boundSql);
@SuppressWarnings("unchecked")
List<E> list = (List<E>) tcm.getObject(cache, key);
if (list == null) {
list = delegate.<E>query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
tcm.putObject(cache, key, list); // issue #578 and #116
}
return list;
}
}
return delegate.<E>query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}

我们可以看到,代码先去查找MappedStatement对应的缓存,如果找不到缓存会委派到具体的具体的类去查询,可以进入BaseExecutor的query方法,首先他也会查询缓存,这个缓存是SqlSession的本地缓存,如果没有缓存才会从数据库去查找。

至此,一个sql的执行的主流程就到此结束了。

坚持原创技术分享,更多深度分析、实践代码,您的支持将鼓励我继续创作!