从二月二十六号开始,我就要求自己出一期与MyBatis有关的文章,直到三月三号那天才发表第一篇文章。这速度,这质量,着实堪忧。经过这件事,我也深刻认识到自己性格上的缺陷——懒惰。为了克服这个坏毛病,我决定今天继续深入。有人告诉我mybatis超级简单,不必在它身上浪费时间。话虽如此,但麻雀虽小,五脏俱全,其中必然有一些惊艳的地方值得我们深入研究。不妨一起看看?
前篇文章——《MyBatis是纸老虎吗?(一)》——给出了MyBatis的使用案例,通过执行我们可以很方便的从数据表中获得那个需要的数据,并将其包装成我们熟悉的对象。这个执行过程究竟是怎样的呢?先看下面这幅图片:
从图中可以直到,程序会先将mybatis配置文件读取到内存中(以流的形式读取,即InputStream),接着将该流对象传递给SqlSessionFactoryBuilder对象的build()方法构建一个类型为SqlSessionFactory对象。好,先来梳理一下这两个类,其中SqlSessionFactoryBuilder是一个既没有继承任何父类,又没有任何子类的类。从其名字不难猜出,这个类的主要作用就是构建SqlSessionFactory对象,事实也确实如此。这个类采用建造者设计模式(我本人不太确定这种说法是否准确)并结合方法重载的语法,定义了很多build()方法。这些方法最终都调用了下面两个相对核心方法,它们分别为:
- public SqlSessionFactory build(Reader reader, String environment, Properties properties)
- public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties)
乍看之下这两个方法没有任何区别,仔细一看确有不同。它们的区别主要在于方法的第一个参数,第一个方法接收了一个Reader类型的参数,第二个接收了一个InputStream类型的参数。这两个方法的主要作用就是解析mybatis配置文件。因为这两个方法当中都有这样一段代码:
XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
// 或
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
return build(parser.parse());
上面为什么说这两个方法是相对核心的方法呢?因为它们两个最终会调用下面这个方法去创建SqlSessionFactory对象(实际为DefaultSqlSessionFactory类型),该方法的源码如下所示:
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
下面就让我们一起来看一下SqlSessionFactoryBuilder类的详细定义吧。下面列出的就是该类的源码:
public class SqlSessionFactoryBuilder {
public SqlSessionFactory build(Reader reader) {
return build(reader, null, null);
}
public SqlSessionFactory build(Reader reader, String environment) {
return build(reader, environment, null);
}
public SqlSessionFactory build(Reader reader, Properties properties) {
return build(reader, null, properties);
}
public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
try {
XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
return build(parser.parse());
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error building SqlSession.", e);
} finally {
ErrorContext.instance().reset();
try {
if (reader != null) {
reader.close();
}
} catch (IOException e) {
// Intentionally ignore. Prefer previous error.
}
}
}
public SqlSessionFactory build(InputStream inputStream) {
return build(inputStream, null, null);
}
public SqlSessionFactory build(InputStream inputStream, String environment) {
return build(inputStream, environment, null);
}
public SqlSessionFactory build(InputStream inputStream, Properties properties) {
return build(inputStream, null, properties);
}
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
try {
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
return build(parser.parse());
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error building SqlSession.", e);
} finally {
ErrorContext.instance().reset();
try {
if (inputStream != null) {
inputStream.close();
}
} catch (IOException e) {
// Intentionally ignore. Prefer previous error.
}
}
}
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
}
通过源码不难得出这样的结论:SqlSessionFactoryBuilder类的主要作用就是创建一个类型为SqlSessionFactory的对象。那SqlSessionFactory是什么呢?先来看一下这个类的体系结构,具体如下图所示:
从图中可以看出SqlSessionFactory是一个接口,其中DefaultSqlSessionFactory类和SqlSessionManager类是这个接口的两个实现类。下面一起来看一下SqlSessionFactory接口的源码,如下所示:
public interface SqlSessionFactory {
SqlSession openSession();
SqlSession openSession(boolean autoCommit);
SqlSession openSession(Connection connection);
SqlSession openSession(TransactionIsolationLevel level);
SqlSession openSession(ExecutorType execType);
SqlSession openSession(ExecutorType execType, boolean autoCommit);
SqlSession openSession(ExecutorType execType, TransactionIsolationLevel level);
SqlSession openSession(ExecutorType execType, Connection connection);
Configuration getConfiguration();
}
从源码可以看出这个类的主要作用就是创建SqlSession对象,其中有很多名为openSession的重载方法,它们拥有不一样的方法签名,其中一个是根据指定的boolean值创建一个是否自动提交事务的数据库会话。下面再来看看其实现类DefaultSqlSessionFactory类的源码,如下所示:
public class DefaultSqlSessionFactory implements SqlSessionFactory {
private final Configuration configuration;
public DefaultSqlSessionFactory(Configuration configuration) {
this.configuration = configuration;
}
@Override
public SqlSession openSession() {
return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
}
@Override
public SqlSession openSession(boolean autoCommit) {
return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, autoCommit);
}
@Override
public SqlSession openSession(ExecutorType execType) {
return openSessionFromDataSource(execType, null, false);
}
@Override
public SqlSession openSession(TransactionIsolationLevel level) {
return openSessionFromDataSource(configuration.getDefaultExecutorType(), level, false);
}
@Override
public SqlSession openSession(ExecutorType execType, TransactionIsolationLevel level) {
return openSessionFromDataSource(execType, level, false);
}
@Override
public SqlSession openSession(ExecutorType execType, boolean autoCommit) {
return openSessionFromDataSource(execType, null, autoCommit);
}
@Override
public SqlSession openSession(Connection connection) {
return openSessionFromConnection(configuration.getDefaultExecutorType(), connection);
}
@Override
public SqlSession openSession(ExecutorType execType, Connection connection) {
return openSessionFromConnection(execType, connection);
}
@Override
public Configuration getConfiguration() {
return configuration;
}
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); // may have fetched a connection so lets call close()
throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
private SqlSession openSessionFromConnection(ExecutorType execType, Connection connection) {
try {
boolean autoCommit;
try {
autoCommit = connection.getAutoCommit();
} catch (SQLException e) {
// Failover to true, as most poor drivers
// or databases won't support transactions
autoCommit = true;
}
final Environment environment = configuration.getEnvironment();
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
final Transaction tx = transactionFactory.newTransaction(connection);
final Executor executor = configuration.newExecutor(tx, execType);
return new DefaultSqlSession(configuration, executor, autoCommit);
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
private TransactionFactory getTransactionFactoryFromEnvironment(Environment environment) {
if (environment == null || environment.getTransactionFactory() == null) {
return new ManagedTransactionFactory();
}
return environment.getTransactionFactory();
}
private void closeTransaction(Transaction tx) {
if (tx != null) {
try {
tx.close();
} catch (SQLException ignore) {
// Intentionally ignore. Prefer previous error.
}
}
}
}
通过DefaultSqlSessionFactory类的源码,我们更加确定前面的说法——这个类的主要作用就是创建SqlSession对象——是正确的。那DefaultSqlSessionFactory是如何创建SqlSession对象的?这还要继续回到上篇文章的第二个案例。其中有这样一句代码:
SqlSession session = sqlSessionFactory.openSession();
程序执行到这行代码时的运行时状况如下图所示:
从代码及上图可以看出,程序会调用DefaultSqlSessionFactory对象的openSession()方法来创建SqlSession对象,这个方法及被调用的方法的源码可以参见上面的源码展示,为了方便阅读,这里再贴一下相关源码:
@Override
public SqlSession openSession() {
return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
}
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); // may have fetched a connection so lets call close()
throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
这里主要关注openSessionFromDataSource()方法即可,因为所有的创建逻辑都集中在这个方法中,这个方法主要做了这样几步操作:1)获取当前SqlSessionFactory对象上的Configuration属性中的environment属性;2)通过getTransactionFactoryFromEnvironment(environment)方法获取一个TransactionFactory对象;3)通过TransactionFactory对象上的newTransaction()方法创建一个Transaction对象;4)调用Configuration对象上的newExecutor()方法获得一个Executor对象;5)通过new语法创建DefaultSqlSession对象。在这个逻辑处理过程中,我有这样几点疑问:
- Environment是什么,是如何完成初始化的?
针对这个疑问,如果看过前一篇文章的第二个案例,应该可以猜出这个对象就是config.xml配置文件中environments节点下的environment节点。这个节点下我们配置了很多数据,比如事务管理器(transactionManager)、数据源信息(数据库驱动、数据库链接地址、数据库名、数据库密码等等)。这些信息被解析后的数据结构如下图所示:
openSessionFromDataSource()方法最终会调用本类(指的是DefaultSqlSessionFactory)中的getTransactionFactoryFromEnvironment()方法获取一个TransactionFactory对象。该方法最终返回的就是上图中的TransactionFactory对象。这里不再啰嗦Environment对象初始流程,后面有时间会进行补充。
- TransactionFactory和Transaction类的作用分别是什么?
在回答这个问题前,我们先来看一下这两个对象的创建流程。下面的代码来自于DefaultSessionFactory,其展示了TransactionFactory对象的创建流程:1 先判断Environment对象是否为空,如果为空,则继续判断Environment对象中的TransactionFactory对象是否为空,如果为空则直接创建ManagedTransactionFactory对象,并将其返回给上级调用者;2 如果上述判断都不成立,则直接取Environment对象中的TransactionFactory对象;3 将TransactionFactory对象返回给上级调用者。这段逻辑的具体代码如下所示:
private TransactionFactory getTransactionFactoryFromEnvironment(Environment environment) {
if (environment == null || environment.getTransactionFactory() == null) {
return new ManagedTransactionFactory();
}
return environment.getTransactionFactory();
}
注意:该方法在本案例中返回的是JdbcTransactionFactory类型的对象。这里先留一个问题:Environment对象中的TransactionFactory对象是怎么创建的呢?下面一起看一下TransactionFactory的类结构:
下面继续返回到DefaultSqlSessionFactory的openSessionFromDataSource()方法中,看这样一段代码transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit)。这段代码实际调用的是JdbcTransactionFactory中的newTransaction()方法。该方法的源码如下所示:
public Transaction newTransaction(DataSource ds, TransactionIsolationLevel level, boolean autoCommit) {
return new JdbcTransaction(ds, level, autoCommit, skipSetAutoCommitOnClose);
}
从源码可以看到该方法直接new了一个JdbcTransaction对象,该对象接收了一个DataSource类型的参数,又接收了一个TransactionIsolationLevel类型的参数,同时还接收了一个boolean类型的参数。看到这里,我们可以大胆猜测一下这个boolean类型参数的作用是指定要创建的数据库连接(Connection)是否自动提交事务,就像《MyBatis是纸老虎吗?(一)》这篇文章中的案例一写的那样,具体见下图:
接下来让我们继续深入梳理JdbcTransaction类的源码,该类中拥有这样几个属性,它们分别为:
protected Connection connection;
protected DataSource dataSource;
protected TransactionIsolationLevel level;
protected boolean autoCommit;
protected boolean skipSetAutoCommitOnClose;
而上面JdbcTransactionFactory中的newTransaction()方法中调用的JdbcTransaction的构造方法的主要作用就是为这些属性赋值,具体过程如下图所示:
梳理到这里,我们先缓一缓,爬山还讲求个休息呢,何况学习呢!我们先来看一下Transaction的体系结构,如下图所示:
从图中可以直到Transaction是一个接口,而我们上面看到的执行逻辑中用到了JdbcTransaction。那回到上面提到的问题上TransactionFactory和Transaction类的作用分别是什么呢?要回答这个问题,我们可以用一下经验主义。中文中有一种造字手法叫形声字,它是在象形字、指事字、会意字的基础上发展起来的。这种字的特点是:它由两部分组成,一部分表示意义的意符(也称为形旁),另一部分表示声音的声符(也叫做声旁)。形声字的形态多样,可以有不同的排列组合,比如常见的材、偏等等。如果按这种造字方法理解,我觉得前者是一个工厂类(Factory表意,Transaction是一个偏旁),主要用于创建Transaction对象。而后者类似于汉字中的单字,譬如王、土等,王、土这些字表示一种具象化的事务,个人认为Transaction也代表一种具象化的事物。这个事物究竟是什么呢?要分析清楚这个事物,我觉得还得对Transaction类进行深入解剖。前面说过这个事物(JdbcTransaction)由Connection、DataSource、TransactionIsolationLevel、boolean(autoCommit)、boolean(skipSetAutoCommitOnClose)等属性组成,并提供了getConnection()、commit()、rollback()、close()、openConnection()、getTimeout()、setDesiredAutoCommit(boolean desiredAutoCommit)、resetAutoCommit()等操作。下面我们按照中国特色式教学给Transaction下个定义:Transaction是一个由多个属性组成的类,其可以帮助外部使用者快速创建数据库连接、提交事务、回滚事务、关闭数据库连接、打开数据库连接、获取超时时间、重置数据库连接自动提交功能等等。好了,好了,不东施效颦了,已经不知道在说啥了。其实Transaction就是一个组件,将后续要使用的资源组合到一起,比提供一些操作接口,以方便使用。下面还是看一下这个类的源码吧:
public class JdbcTransaction implements Transaction {
private static final Log log = LogFactory.getLog(JdbcTransaction.class);
protected Connection connection;
protected DataSource dataSource;
protected TransactionIsolationLevel level;
protected boolean autoCommit;
protected boolean skipSetAutoCommitOnClose;
public JdbcTransaction(DataSource ds, TransactionIsolationLevel desiredLevel, boolean desiredAutoCommit) {
this(ds, desiredLevel, desiredAutoCommit, false);
}
public JdbcTransaction(DataSource ds, TransactionIsolationLevel desiredLevel, boolean desiredAutoCommit,
boolean skipSetAutoCommitOnClose) {
dataSource = ds;
level = desiredLevel;
autoCommit = desiredAutoCommit;
this.skipSetAutoCommitOnClose = skipSetAutoCommitOnClose;
}
public JdbcTransaction(Connection connection) {
this.connection = connection;
}
@Override
public Connection getConnection() throws SQLException {
if (connection == null) {
openConnection();
}
return connection;
}
@Override
public void commit() throws SQLException {
if (connection != null && !connection.getAutoCommit()) {
if (log.isDebugEnabled()) {
log.debug("Committing JDBC Connection [" + connection + "]");
}
connection.commit();
}
}
@Override
public void rollback() throws SQLException {
if (connection != null && !connection.getAutoCommit()) {
if (log.isDebugEnabled()) {
log.debug("Rolling back JDBC Connection [" + connection + "]");
}
connection.rollback();
}
}
@Override
public void close() throws SQLException {
if (connection != null) {
resetAutoCommit();
if (log.isDebugEnabled()) {
log.debug("Closing JDBC Connection [" + connection + "]");
}
connection.close();
}
}
protected void setDesiredAutoCommit(boolean desiredAutoCommit) {
try {
if (connection.getAutoCommit() != desiredAutoCommit) {
if (log.isDebugEnabled()) {
log.debug("Setting autocommit to " + desiredAutoCommit + " on JDBC Connection [" + connection + "]");
}
connection.setAutoCommit(desiredAutoCommit);
}
} catch (SQLException e) {
// Only a very poorly implemented driver would fail here,
// and there's not much we can do about that.
throw new TransactionException(
"Error configuring AutoCommit. " + "Your driver may not support getAutoCommit() or setAutoCommit(). "
+ "Requested setting: " + desiredAutoCommit + ". Cause: " + e,
e);
}
}
protected void resetAutoCommit() {
try {
if (!skipSetAutoCommitOnClose && !connection.getAutoCommit()) {
// MyBatis does not call commit/rollback on a connection if just selects were performed.
// Some databases start transactions with select statements
// and they mandate a commit/rollback before closing the connection.
// A workaround is setting the autocommit to true before closing the connection.
// Sybase throws an exception here.
if (log.isDebugEnabled()) {
log.debug("Resetting autocommit to true on JDBC Connection [" + connection + "]");
}
connection.setAutoCommit(true);
}
} catch (SQLException e) {
if (log.isDebugEnabled()) {
log.debug("Error resetting autocommit to true " + "before closing the connection. Cause: " + e);
}
}
}
protected void openConnection() throws SQLException {
if (log.isDebugEnabled()) {
log.debug("Opening JDBC Connection");
}
connection = dataSource.getConnection();
if (level != null) {
connection.setTransactionIsolation(level.getLevel());
}
setDesiredAutoCommit(autoCommit);
}
@Override
public Integer getTimeout() throws SQLException {
return null;
}
}
看着这些源码,突然觉得这好像并非自己想要的,那我想要的是什么呢?其实我最关心的是这个组件——JdbcTransaction——会被怎么使用。要解决这个问题,我们还需要继续梳理DefaultSqlSessionFactory中的openSessionFromDataSource()方法。在这个方法中我们可以看到这样一句代码configuration.newExecutor(tx, execType)。这段代码的主要目的就是调用DefaultSqlSessionFactory类中的Configuration属性上的newExecutor()方法创建一个Executor对象,注意这个方法接收一个Transaction类型的参数和ExecutorType类型的参数。这个过程又是怎样的呢?先来看下面一段代码:
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
executorType = executorType == null ? defaultExecutorType : 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);
}
return (Executor) interceptorChain.pluginAll(executor);
}
这段代码的运行时状态如下图所示:
看到这里我在想这样几个问题:Executor是干什么的?CachingExecutor是为Executor添加缓存吗?这里的interceptorChain是为sql语句新增mysql查询条数的责任链吗?首先我们一起来看一下Executor的继承结构:
先来看一下Executor这个接口的源码吧,具体如下图所示:
可以看出这个接口定义了许多常见的数据库操作,其中update、query、queryCursor等是我们项目开发中经常用到的操作,而commit、rollback、colose等是与数据库连接相关的操作。这里就不再扯了,再看一下BaseExecutor类的源码,如下所示:
public abstract class BaseExecutor implements Executor {
private static final Log log = LogFactory.getLog(BaseExecutor.class);
protected Transaction transaction;
protected Executor wrapper;
protected ConcurrentLinkedQueue<DeferredLoad> deferredLoads;
protected PerpetualCache localCache;
protected PerpetualCache localOutputParameterCache;
protected Configuration configuration;
protected int queryStack;
private boolean closed;
protected BaseExecutor(Configuration configuration, Transaction transaction) {
this.transaction = transaction;
this.deferredLoads = new ConcurrentLinkedQueue<>();
this.localCache = new PerpetualCache("LocalCache");
this.localOutputParameterCache = new PerpetualCache("LocalOutputParameterCache");
this.closed = false;
this.configuration = configuration;
this.wrapper = this;
}
@Override
public Transaction getTransaction() {
if (closed) {
throw new ExecutorException("Executor was closed.");
}
return transaction;
}
@Override
public void close(boolean forceRollback) {
try {
try {
rollback(forceRollback);
} finally {
if (transaction != null) {
transaction.close();
}
}
} catch (SQLException e) {
// Ignore. There's nothing that can be done at this point.
log.warn("Unexpected exception on closing transaction. Cause: " + e);
} finally {
transaction = null;
deferredLoads = null;
localCache = null;
localOutputParameterCache = null;
closed = true;
}
}
@Override
public boolean isClosed() {
return closed;
}
@Override
public int update(MappedStatement ms, Object parameter) throws SQLException {
ErrorContext.instance().resource(ms.getResource()).activity("executing an update").object(ms.getId());
if (closed) {
throw new ExecutorException("Executor was closed.");
}
clearLocalCache();
return doUpdate(ms, parameter);
}
@Override
public List<BatchResult> flushStatements() throws SQLException {
return flushStatements(false);
}
public List<BatchResult> flushStatements(boolean isRollBack) throws SQLException {
if (closed) {
throw new ExecutorException("Executor was closed.");
}
return doFlushStatements(isRollBack);
}
@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler)
throws SQLException {
BoundSql boundSql = ms.getBoundSql(parameter);
CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
@SuppressWarnings("unchecked")
@Override
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 (closed) {
throw new ExecutorException("Executor was closed.");
}
if (queryStack == 0 && ms.isFlushCacheRequired()) {
clearLocalCache();
}
List<E> list;
try {
queryStack++;
list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
if (list != null) {
handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
} else {
list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
} finally {
queryStack--;
}
if (queryStack == 0) {
for (DeferredLoad deferredLoad : deferredLoads) {
deferredLoad.load();
}
// issue #601
deferredLoads.clear();
if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
// issue #482
clearLocalCache();
}
}
return list;
}
@Override
public <E> Cursor<E> queryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds) throws SQLException {
BoundSql boundSql = ms.getBoundSql(parameter);
return doQueryCursor(ms, parameter, rowBounds, boundSql);
}
@Override
public void deferLoad(MappedStatement ms, MetaObject resultObject, String property, CacheKey key,
Class<?> targetType) {
if (closed) {
throw new ExecutorException("Executor was closed.");
}
DeferredLoad deferredLoad = new DeferredLoad(resultObject, property, key, localCache, configuration, targetType);
if (deferredLoad.canLoad()) {
deferredLoad.load();
} else {
deferredLoads.add(new DeferredLoad(resultObject, property, key, localCache, configuration, targetType));
}
}
@Override
public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {
if (closed) {
throw new ExecutorException("Executor was closed.");
}
CacheKey cacheKey = new CacheKey();
cacheKey.update(ms.getId());
cacheKey.update(rowBounds.getOffset());
cacheKey.update(rowBounds.getLimit());
cacheKey.update(boundSql.getSql());
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
TypeHandlerRegistry typeHandlerRegistry = ms.getConfiguration().getTypeHandlerRegistry();
// mimic DefaultParameterHandler logic
MetaObject metaObject = null;
for (ParameterMapping parameterMapping : parameterMappings) {
if (parameterMapping.getMode() != ParameterMode.OUT) {
Object value;
String propertyName = parameterMapping.getProperty();
if (boundSql.hasAdditionalParameter(propertyName)) {
value = boundSql.getAdditionalParameter(propertyName);
} else if (parameterObject == null) {
value = null;
} else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
value = parameterObject;
} else {
if (metaObject == null) {
metaObject = configuration.newMetaObject(parameterObject);
}
value = metaObject.getValue(propertyName);
}
cacheKey.update(value);
}
}
if (configuration.getEnvironment() != null) {
// issue #176
cacheKey.update(configuration.getEnvironment().getId());
}
return cacheKey;
}
@Override
public boolean isCached(MappedStatement ms, CacheKey key) {
return localCache.getObject(key) != null;
}
@Override
public void commit(boolean required) throws SQLException {
if (closed) {
throw new ExecutorException("Cannot commit, transaction is already closed");
}
clearLocalCache();
flushStatements();
if (required) {
transaction.commit();
}
}
@Override
public void rollback(boolean required) throws SQLException {
if (!closed) {
try {
clearLocalCache();
flushStatements(true);
} finally {
if (required) {
transaction.rollback();
}
}
}
}
@Override
public void clearLocalCache() {
if (!closed) {
localCache.clear();
localOutputParameterCache.clear();
}
}
protected abstract int doUpdate(MappedStatement ms, Object parameter) throws SQLException;
protected abstract List<BatchResult> doFlushStatements(boolean isRollback) throws SQLException;
protected abstract <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds,
ResultHandler resultHandler, BoundSql boundSql) throws SQLException;
protected abstract <E> Cursor<E> doQueryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds,
BoundSql boundSql) throws SQLException;
protected void closeStatement(Statement statement) {
if (statement != null) {
try {
statement.close();
} catch (SQLException e) {
// ignore
}
}
}
/**
* Apply a transaction timeout.
*
* @param statement
* a current statement
*
* @throws SQLException
* if a database access error occurs, this method is called on a closed <code>Statement</code>
*
* @since 3.4.0
*
* @see StatementUtil#applyTransactionTimeout(Statement, Integer, Integer)
*/
protected void applyTransactionTimeout(Statement statement) throws SQLException {
StatementUtil.applyTransactionTimeout(statement, statement.getQueryTimeout(), transaction.getTimeout());
}
private void handleLocallyCachedOutputParameters(MappedStatement ms, CacheKey key, Object parameter,
BoundSql boundSql) {
if (ms.getStatementType() == StatementType.CALLABLE) {
final Object cachedParameter = localOutputParameterCache.getObject(key);
if (cachedParameter != null && parameter != null) {
final MetaObject metaCachedParameter = configuration.newMetaObject(cachedParameter);
final MetaObject metaParameter = configuration.newMetaObject(parameter);
for (ParameterMapping parameterMapping : boundSql.getParameterMappings()) {
if (parameterMapping.getMode() != ParameterMode.IN) {
final String parameterName = parameterMapping.getProperty();
final Object cachedValue = metaCachedParameter.getValue(parameterName);
metaParameter.setValue(parameterName, cachedValue);
}
}
}
}
}
private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds,
ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
List<E> list;
localCache.putObject(key, EXECUTION_PLACEHOLDER);
try {
list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
} finally {
localCache.removeObject(key);
}
localCache.putObject(key, list);
if (ms.getStatementType() == StatementType.CALLABLE) {
localOutputParameterCache.putObject(key, parameter);
}
return list;
}
protected Connection getConnection(Log statementLog) throws SQLException {
Connection connection = transaction.getConnection();
if (statementLog.isDebugEnabled()) {
return ConnectionLogger.newInstance(connection, statementLog, queryStack);
}
return connection;
}
@Override
public void setExecutorWrapper(Executor wrapper) {
this.wrapper = wrapper;
}
private static class DeferredLoad {
private final MetaObject resultObject;
private final String property;
private final Class<?> targetType;
private final CacheKey key;
private final PerpetualCache localCache;
private final ObjectFactory objectFactory;
private final ResultExtractor resultExtractor;
// issue #781
public DeferredLoad(MetaObject resultObject, String property, CacheKey key, PerpetualCache localCache,
Configuration configuration, Class<?> targetType) {
this.resultObject = resultObject;
this.property = property;
this.key = key;
this.localCache = localCache;
this.objectFactory = configuration.getObjectFactory();
this.resultExtractor = new ResultExtractor(configuration, objectFactory);
this.targetType = targetType;
}
public boolean canLoad() {
return localCache.getObject(key) != null && localCache.getObject(key) != EXECUTION_PLACEHOLDER;
}
public void load() {
@SuppressWarnings("unchecked")
// we suppose we get back a List
List<Object> list = (List<Object>) localCache.getObject(key);
Object value = resultExtractor.extractObjectFromList(list, targetType);
resultObject.setValue(property, value);
}
}
}
从源码可以看出BaseExecutor这个类实现了Executor接口中的所有方法,但其对外提供了扩展,这样它的实现类就可以提供一些属于自己的个性化操作,这里用到了模板设计模式。下面就以查询为例来看看这里的具体执行流程吧。这个操作以下面这行代码为起点:
BaseExecutor#query(MappedStatement, Object, RowBounds, ResultHandler)
这个方法会首先拿到一个BoundSql对象,接着会将MappedStatement、Object、RowBounds、BoundSql等组成一个CacheKey对象,然后缓存起来,接着继续调用BaseExecutor中的另一个query()方法,其签名为:MappedStatement, Object, RowBounds, ResultHandler,CacheKey, BoundSql。该方法会首先检查缓存,如果缓存中存在,则进一步处理缓存数据,否则直接调用queryFromDatabase()方法,从数据库中查询。具体如下图所示:
再来看一下queryFromDatabase()这个方法的源码:
private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds,
ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
List<E> list;
localCache.putObject(key, EXECUTION_PLACEHOLDER);
try {
list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
} finally {
localCache.removeObject(key);
}
localCache.putObject(key, list);
if (ms.getStatementType() == StatementType.CALLABLE) {
localOutputParameterCache.putObject(key, parameter);
}
return list;
}
从这个方法中可以看到一个doQuery()调用,这个方法的最终实现是位于SimpleExecutor类中的。这个方法的源码为:
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler,
BoundSql boundSql) throws SQLException {
Statement stmt = null;
try {
Configuration configuration = ms.getConfiguration();
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler,
boundSql);
stmt = prepareStatement(handler, ms.getStatementLog());
return handler.query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
在这里我们看到有这样一个操作:prepareStatement(),是不是觉得很熟悉?先来看一下这个方法的源码:
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
Connection connection = getConnection(statementLog);
stmt = handler.prepare(connection, transaction.getTimeout());
handler.parameterize(stmt);
return stmt;
}
这个方法的主要操作有:1) 首先创建数据库连接,2) 然后执行StatementHandler类中的prepare()方法,3) 接着再执行StatementHandler类中的parameterize()方法。先看getConnection()这行代码,其源码如下所示(该方法位于BaseExecutor中):
protected Connection getConnection(Log statementLog) throws SQLException {
Connection connection = transaction.getConnection();
if (statementLog.isDebugEnabled()) {
return ConnectionLogger.newInstance(connection, statementLog, queryStack);
}
return connection;
}
这个方法中有这样一句transaction.getConnection()。看到这句熟悉吗?这不就是前面梳理的Transaction中的方法吗?当时梳理的具体实现类为JdbcTransaction。这不就解决了我最关心的那个问题(这个组件——JdbcTransaction——会被怎么使用)吗?这里我又有了一个新的问题:StatementHandler这个类是干什么的?还是老套路,先来看它的继承体系:
从图中可以看出StatementHandler是一个接口,根据java类名的命名规范,我们可以猜测它的主要作用就是对SQL命令执行一些预处理,比如对某些占位参数进行替换,在手工jdbc中是这样处理的,如下图所示(图中的):
既然大概知道了它的作用,那这个对象(SimpleExecutor中的prepareStatement方法接收的StatementHandler参数)是在哪里创建的呢?继续回到前面梳理的SimpleExecutor类的doQuery()方法中,里面有这样一句configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql)。也就是说这个对象的创建是通过调用Configuration类中的newStatementHandler()方法完成的,下面就看一下这个方法的源码,如下所示:
public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement,
Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject,
rowBounds, resultHandler, boundSql);
return (StatementHandler) interceptorChain.pluginAll(statementHandler);
}
这个方法最终会创建一个RoutingStatementHandler对象,可以将它理解成一个路由器,其会根据MappedStatement对象中的statementType属性来确定最终创建哪个类型的StatementHandler对象,这段逻辑需要参看RoutingStatementHandler的构造函数,具体源码如下所示:
public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds,
ResultHandler resultHandler, BoundSql boundSql) {
switch (ms.getStatementType()) {
case STATEMENT:
delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break;
case PREPARED:
delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break;
case CALLABLE:
delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break;
default:
throw new ExecutorException("Unknown statement type: " + ms.getStatementType());
}
}
从这段源码可以看出被创建的StatementHandler对象最终赋值给了RoutingStatementHandler对象中StatementHandler类型的属性delegate,所以前面梳理的prepareStatement()方法中对StatementHandler对象的各种调用最终都通过RoutingStatementHandler对象委托给了真实的StatementHandler对象。比如下面这个处理逻辑(通过delegate对象来调用实际的方法进行处理):
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
return delegate.query(statement, resultHandler);
}
而RoutingStatementHandler中query()方法的调用入口位于SimpleExecutor类的doQuery()方法中,如下图所示:
现在从这里继续向上级调用返回,会首先返回到SimpleExecutor类的queryFromDatabase()方法;接着继续向上返回,会到BaseExecutor类的query()方法中;紧接着继续向上会返回到BaseExecutor类的另一个同名的query()方法中,该方法的签名为:MappedStatement, Object, RowBounds, ResultHandler;继续向上返回,会到DefaultSqlSession类的selectList()方法中,该方法签名为:String, Object, RowBounds, ResultHandler,之后又会返回到该类中同名的另外一个selectList()方法中,该方法的签名为:String, Object;继续向上返回会到DefaultSqlSession类的selectOne(String, Object)中,接着再次向上返回,会到同类中的另一个同名方法中selectOne(String)中;继续返回,回到SpringTransactionApplication中的mybatis()方法中。
至此我们就把《MyBatis是纸老虎吗?(一)》这篇文章中的案例二的执行流程梳理清楚了。在梳理的过程中我们也弄懂了MyBatis中涉及到的几个组件,它们分别为:
- SqlSessionFactoryBuilder:该类的主要作用是创建SqlSessionFactory对象,采用了设计模式中的建造者模式
- SqlSessionFactory:该接口定义了各种创建SqlSession对象的操作。该接口的实现类主要为:DefaultSqlSessionFactory
- SqlSession:该接口定义了各种操作,比如查询、更新、添加和删除等操作,还包括其他一些数据库事务操作,比如事务回滚、事务提交等。该接口有三个实现类,它们分别为:DefaultSqlSession、SqlSessionManager、SqlSessionTemplate。这些操作最终都会委托给Executor对象
- TransactionFactory:这个接口中定义了两个创建Transaction对象的方法,其实现类有:JdbcTransactionFactory、ManagedTransactionFactory、SpringManagedTransactionFactory
- Transaction:这是一个接口,其中定义了这样几个方法getConnection()、commit()、rollback()、close()、getTimeout()等,其实现类有:JdbcTransaction、ManagedTransaction、SpringManagedTransaction。前面有梳理过JdbcTransaction类,可以翻看一下。总体来说这个接口的主要作用就是整合操作过程中用到的各种组件或属性,比如Connection、DataSource、TransactionIsolationLevel、boolean类型的autoCommit、boolean类型的skipSetAutoCommitOnClose等,以方便后续操作数据库时使用。譬如上面梳理查询时,看到过其中有这样一个逻辑:Connection connection = getConnection(statementLog),这个代码的主要作用就是从Transaction对象中获取Connection对象
- Executor:这同样是一个接口,其中定义了各种数据库操作,比如更新、查询、提交事务、回滚事务等等,其实现类有多个,比如前面介绍过的SimpleExecutor,关于这个接口的其他实现类可以参看前面的类继承结构图。这个接口完成实际的数据库操作
- MappedStatement:个人理解这是MyBatis的命令存储结构
- ResultHandler:个人理解其主要作用是对数据库查询结果进行处理
- Configuration:这是一个配置类,用于接收各种MyBatis配置项,后续会详细梳理
- StatementHandler:可以将其理解为jdbc中的PreparedStatement,用于对sql命令进行各种预处理
- ExecutorType:这时一个枚举类,其中定义了三个枚举值,它们分别为:SIMPLE、REUSE、BATCH。前面梳理RoutingStatementHandler类时,看到过这样的代码,如下图所示:
好了,本篇文章梳理到这里就算结束了。在文章中我们梳理MyBatis中很多实用的类和接口,通过这次梳理我们解决了之前开发中遇到的一些问题,但依旧有一些问题没有梳理清楚(这些问题将在下一篇文章中给出答案),比如:
- CachingExecutor是为Executor添加缓存吗?
- 这里的interceptorChain是为sql语句新增mysql查询条数的责任链吗?
- Environment的初始化画流程是怎样的?
- Transaction和SqlSession之间存在着怎样的关系?