Mybatis 3.5.5的版本
按照使用方式的不同,我们在创建完成Session之后有三种执行SQL的方式,分别是调用Session接口中预先定义的多种select | insert | update | delete方法、使用mapper代理方式、使用注解方式,那么三种方式是不同的,我们分别进行分析。
本篇主要分析直接调用Session接口中的方法的执行过程(以selectOne方法为例)
一、首先我们看一下在DefaultSqlSession中的方法执行情况:
public <T> T selectOne(String statement, Object parameter) {
// Popular vote was to return null on 0 results and throw exception on too many.
List<T> list = this.selectList(statement, parameter);
if (list.size() == 1) {
return list.get(0);
}
/***** 省略部分代码 ****/
}
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();
}
}
我们看到其实我们调用的selectOne方法实际上也是通过selectList方法得到对象,只不过是取唯一的一个而已。那么selectOne方法中的注释说明了对于List的size不为1的处理方式,0个返回null,多个抛出异常。
在selectList方法中,首先根据我们传入的statement的值来找到对应的MapperStatement,那么我们传入的这个String最好可以是全称(namespace+id)如com.zxzfcsu.getUserById,也可以是这个标签的id值如getUserById。接下来就是调用Executor的query方法进行查询了。其中对parameter执行的操作wrapCollection是为了将数组或者Collection类别的parameter包装成一个Map。ruwBounds参数这是为DEFAULT。
二、Executor中的查询(如上篇四 所述,根据不同的type创建不同的Executor,这里以SimpleExecutor为例)
1.首先我们来看执行的query方法的代码:
//这段代码在BaseExecutor类中
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);
}
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
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();
}
deferredLoads.clear();
if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
clearLocalCache();
}
}
return list;
}
我们分析一下这段代码的逻辑,首先是给MapperedStatement对象传入参数得到BoundSql对象,这个时候SQL已经准备好了。然后准备一个CacheKey对象传递给query方法。
在这个方法中,第一步首先判断queryStack的大小和MapperedStatement对象是否刷新缓存,需要的话就要清除掉缓存。
第二步就是执行我们此次的查询操作。同样对queryStack加1,说明queryStack就是当前查询的任务的数量。然后根据resultHandler是否为空尝试着从缓存中取结果,在默认情况下,我们传入的参数为Executor.NO_RESULT_HANDLER为null。然后根据list是否为null为判断依据,如果不为null,则需要对拿到的list进行处理;如果是null,则需要我们从数据库中查询数据,即调用方法queryFromDatabase。在这之后需要将queryStack减1,说明当前查询完成了。
最后这一步是延迟加载相关的。如果当前的queryStack为0,说明没有任务要执行了,那么这个时候会逐个对延迟加载的对象进行加载。然后判断本地缓存的范围,LocalCacheScope是个枚举类,有两个元素代表两个级别,分别是Session和Statement代表是Session级别和Statement级别,如果是Statement级别到这里就需要清除掉缓存了,因为该MapperedStatement对象的查询已经完成了。
2.那么我们重点关注的自然就是从数据库查询数据的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方法,外面的操作是对缓存的操作。非常有意思的是,该方法在查询开始前,先通过占位符把相应的key-value放进缓存中,查询结束后再进行替换,不知道作者是怎么考虑的,有知道的小伙伴请留言告诉我,谢谢。
3.我们再来看一下doQuery的具体实现(以SimpleExecutor为例)
//这个方法在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);
}
}
在这里它创建了一个StatementHandler对象,通过简单的一个代理的方式运作,具体我们创建的类型是根据StatementType来决定的。StatementType是一个枚举类,有三个元素分别是Statement、Prepared和Callable,分别对应的StatementHandler的实现类是Simple、Prepared和Callable,我们默认的是Prepared类型(见下面代码)。
//出自XmlStatementBuilder类parseStatementNode方法
StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
然后就是需要通过prepareStatement方法(具体见下面prepareStatement方法代码的分析)生成一个Statement,最后通过StatementHandler来执行query方法进行查询了。
在finally块中最终还是要关闭Statement的。
prepareStatement方法代码的分析:
//准备Statemnet的过程
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;
}
上面准备的过程有几个部分,一是获取数据库连接,这里最终使用的是JdbcTransaction类中的openSession方法,最后交给不同的dataSource去建立连接。二是创建Statement,调用的是handler的prepaere方法,最终会根据不同的StatementType生成不同的java.sql.Statement,那么它是最终直接被jdbc使用的类,这个过程实际上是调用了不同handler的instantiateStatment方法。最后是执行一个parameterize方法对创建的Statement对象设置具体参数。
<1>我们看在PreparedStatementHandler的instantiateStatment方法和parameterize方法的处理过程。
protected Statement instantiateStatement(Connection connection) throws SQLException {
String sql = boundSql.getSql();
if (mappedStatement.getKeyGenerator() instanceof Jdbc3KeyGenerator) {
String[] keyColumnNames = mappedStatement.getKeyColumns();
if (keyColumnNames == null) {
return connection.prepareStatement(sql, PreparedStatement.RETURN_GENERATED_KEYS);
} else {
return connection.prepareStatement(sql, keyColumnNames);
}
} else if (mappedStatement.getResultSetType() == ResultSetType.DEFAULT) {
return connection.prepareStatement(sql);
} else {
return connection.prepareStatement(sql, mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY);
}
}
其实这就与原生的jdbc代码很相像了,就是根据不同的返回值、主键字段等创建一个preparedStatement。
<2>我们看一下parameterize方法最终会执行到DefaultParameterHandler中的setParameters方法,最终会根据不同的ParameterMapping中的TypeHandler来进行设置参数,见下面删减后的代码:
public void setParameters(PreparedStatement ps) {
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
if (parameterMappings != null) {
for (int i = 0; i < parameterMappings.size(); i++) {
ParameterMapping parameterMapping = parameterMappings.get(i);
if (parameterMapping.getMode() != ParameterMode.OUT) {
Object value;
/**** 这里有很多代码获取了parameterMapping其中的value值和jdbcType ****/
try {
//最终使用不同的typeHandler去处理值
typeHandler.setParameter(ps, i + 1, value, jdbcType);
} catch (TypeException | SQLException e) {
throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
}
}
}
}
}
三、StatementHandler中查询的执行(以默认的PreparedStatementHandler类型为例)
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
ps.execute();
return resultSetHandler.handleResultSets(ps);
}
调用的方法很简单,直接使用PreparedStatement的execute方法,最后是对返回值的处理,因为查询是有结果的。