继续sqlSession运行过程分析,上一篇文章讲到通过sqlSession中executor对象的query方法执行的查询,在分析这个方法的源码之前,先分析下SqlSession执行sql中用到的比较重要的几个对象。SqlSession中是通过Executor 、StatementHandler、ParameterHandler、ResultHandler来完成数据库操作和结果返回的。先稍微来了解下这几个对象大致的作用:
下面详细来研究上面提到的四个对象。
执行器起(Executor)了至关重要的作用。它是一个真正执行Java和数据库交互的组件,在Mybatis中存在三种类型的执行器,可以在setting属性中配置:
int update(MappedStatement ms, Object parameter) throws SQLException;
List query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey cacheKey, BoundSql boundSql) throws SQLException;
List query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException;
Cursor queryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds) throws SQLException;
List flushStatements() throws SQLException;
void commit(boolean required) throws SQLException;
void rollback(boolean required) throws SQLException;
下面先不看这些方法的具体实现,而是先来了解下如何来创建Executor,首先sqlSession中关联的Executor对象是在调用SqlSessionFactory.openSession()的时候创建的,具体的代码如下:
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);
//execType是执行器的类型,在openSession()方法中可以传入; tx是指定的事务类型
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();
}
}
可以看到Executor对象是通过configuration.newExecutor()方法创建的,传入的两个参数是事务类型和传入的执行器类型;下面来看下这个方法的具体实现:
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);
}
//如果配置了二级缓存,则会创建一个CachingExecutor对当前Executor进行一层包装,这个对象负责执行缓存逻辑
if (cacheEnabled) {
executor = new CachingExecutor(executor);
}
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
这里defaultExecutorType就是我们在setting属性中配置的执行器类型,这里需要注意下,从上面的代码逻辑中可以看到如果我们配置了defaultExecutorType,但在openSession()的时候又指定了Executor的类型,那么setting中的配置是不会生效的。
这段代码的逻辑其实很简单,就是根据不同的ExecutorType来创建不同类型的Executor对象,如果在配置中启用了二级缓存,则这里会对创建出的executor对象进行一层包装,得到一个CachingExecutor对象,由其来负责执行缓存逻辑。在返回executor对象之前,还有一句interceptorChain.pluginAll(executor)
这样的代码,这其实是mybatis的插件,这个方法里将会构建层层的动态代理对象,这样在执行真实的executor之前会先执行插件的代码。
现在来看下具体查询的实现,下面是SimpleExecutor对象的doQuery()方法:
Override
public List 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 {//最后需要关闭Statement对象,防止资源泄露
closeStatement(stmt);
}
}
可以看到里面先通过configuration.newStatementHandler()来创建了一个StatementHandler对象,然后将其作为参数调用prepareStatement()得到了Statement对象,最后将Statement对象和处理结果的resultHandler对象作为参数传给其query()方法实现sql的执行和结果集的包装。
下面是prepareStatement方法的实现:
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
//从transaction中取到数据
Connection connection = getConnection(statementLog);
stmt = handler.prepare(connection, transaction.getTimeout());
handler.parameterize(stmt);
return stmt;
}
是通过StatementHandler的prepare()方法拿到预编译的statement对象,然后再通过其parameterize()来为预编译好的sql设置参数。
通过上面的分析可以看到sql的预编译、参数设置、sql执行、返回结果的处理都是通过StatementHandler进行处理的,Executor只是起到了一个调度作用(实际上Executor中还是做了很多事情的,比如说二级缓存的处理)。
数据库会话器顾名思义就是用来处理数据库会话的,从上面已经可以看到StatementHandler的重要作用了。先来看下Configuration是如何创建statementHandler对象的,下面是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);
statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
return statementHandler;
}
可以看到真实创建的是一个RoutingStatementHandler对象,这不是一个真实提供服务的对象,而是通过适配模式找到对应的StatementHandler来执行。在mybatis中StatementHandler和Executor一样也分三种:SimpleStatementHandler PreparedStatementHandler CallableStatementHandler,分别对应前面说的三种类型的执行器。
在初始化RoutingStatementHandler对象的时候,会根据上下文环境创建哪个StatementHandler对象,我们看下RoutingStatementHandler的源码。
public class RoutingStatementHandler implements StatementHandler {
//delegate是真正提供服务的SimpleStatementHandler对象
private final StatementHandler delegate;
public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
//根据MappedStatement中不同的StatementType类型,构造不同类型的代理对象.
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());
}
}
//所有方法都是转发到代理对象delegate上去
@Override
public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
return delegate.prepare(connection, transactionTimeout);
}
@Override
public void parameterize(Statement statement) throws SQLException {
delegate.parameterize(statement);
}
@Override
public void batch(Statement statement) throws SQLException {
delegate.batch(statement);
}
@Override
public int update(Statement statement) throws SQLException {
return delegate.update(statement);
}
@Override
public List query(Statement statement, ResultHandler resultHandler) throws SQLException {
return delegate.query(statement, resultHandler);
}
@Override
public Cursor queryCursor(Statement statement) throws SQLException {
return delegate.queryCursor(statement);
}
@Override
public BoundSql getBoundSql() {
return delegate.getBoundSql();
}
@Override
public ParameterHandler getParameterHandler() {
return delegate.getParameterHandler();
}
}
可以看到RoutingStatementHandler完全是一个代理对象,内部的所有实际操作比如update query等都是直接转发给内部代理对象delegate的相关方法。
delegate的具体的值是在RoutingStatementHandler的构造器中根据传入MappedStatement对象中的StatementType来构造的。
下面以最常用的PrepareStatementHandler为例来分析下sql的执行过程,前面介绍执行器的时候重点用到了prepare()、parameterize()和query()方法,下面就从这三个方法的源码入手。
先来看下prepare()方法的实现,这个方法是在其父类BaseStatementHandler,prepare()中实现的:
@Override
public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
ErrorContext.instance().sql(boundSql.getSql());
Statement statement = null;
try {
//从Connection中获取statement对象
statement = instantiateStatement(connection);
//设置statement的超时时间,这里的超时时间可能有多个配置,优先级是:
//事务的超时时间设置>mapper.xml中单条sql语句超时时间配置>mybatis-config.xml中配置的超时时间
//如果事务的超时时间大于后两种,则仍会选择后两种中优先级高的那个.
setStatementTimeout(statement, transactionTimeout);
//设置可获取的最大行数
setFetchSize(statement);
return statement;
} catch (SQLException e) {
closeStatement(statement);
throw e;
} catch (Exception e) {
closeStatement(statement);
throw new ExecutorException("Error preparing statement. Cause: " + e, e);
}
}
获取statement对象是在instantiateStatement()中实现的,而instantiateStatement()在BaseStatementHandler是个抽象方法,需要各个子类中进行具体实现。下面是PrepareStatementHandler中对这个方法的实现:
@Override
protected Statement instantiateStatement(Connection connection) throws SQLException {
String sql = boundSql.getSql();
//如果设置了主键(keyGenerator),那么则需要调用connection的对应prepareStatement方法
//获取支持回填主键的PrepareStatement对象
if (mappedStatement.getKeyGenerator() instanceof Jdbc3KeyGenerator) {
String[] keyColumnNames = mappedStatement.getKeyColumns();
if (keyColumnNames == null) { //只设置了keyGenerator没有指定具体主键,则使用主键
return connection.prepareStatement(sql, PreparedStatement.RETURN_GENERATED_KEYS);
} else {
return connection.prepareStatement(sql, keyColumnNames);
}
//设置PrepareStatement的resultSetType属性
} else if (mappedStatement.getResultSetType() != null) {
return connection.prepareStatement(sql, mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY);
} else {
//没有设置,获取默认情况的prepareStatement
return connection.prepareStatement(sql);
}
}
总的来说PrepareStatementHandler的prepare()方法完成了对sql的预编译,针对mappedStatement中不同的设置(是否设置了主键 返回值ResultSet的类型等),获得不同的PrepareStatement对象,然后给这个对象进行超时设置和可以获取最大结果行数的设定。
下面看下parameterize()方法,这个方法负责完成对方法参数的设置:
@Override
public void parameterize(Statement statement) throws SQLException {
parameterHandler.setParameters((PreparedStatement) statement);
}
这个方法中是借助的ParameterHandler的setParameters方法来对参数进行的设定,入参是PreparedStatement类型的对象。后面会对ParameterHandler进行介绍,所以这里暂时跳过。
最后看下一个具体的查询方法:PrepareStatementHandler的query()方法:
@Override
public List query(Statement statement, ResultHandler resultHandler) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
ps.execute();
return resultSetHandler. handleResultSets(ps);
}
可以看到逻辑非常简单,这是因为前面已经获得了预编译的PreparedStatement参数也已经设置完成,所以执行的时候只需要简单的调用PreparedStatement.execute()就行了.执行完成之后,就通过ResultSetHandler的handleResultSets()方法来对返回结果进行包装。
分析完StatementHandler的源码之后,一条查询sql的执行流程就很清楚了:
Executor负责将传入的参数转换为parameterObject对象,然后调用StatementHandler的prepare()获取Statement对象并设置一些参数(超时时间等),
然后通过parameterize()方法为statement设置参数,这一步实际上是通过ParameterHandler实现的,然后就是执行查询这里就是直接调用statement的
execute()方法来执行,最后通过resultSetHandler对查询返回的结果进行包装.update等操作流程也是类似的。
可以看到在StatementHandler中是通过ParameterHandler的setParameters给预编译好的sql设置参数的.其实整个ParameterHandler的作用就是完成参数的设置。ParameterHandler是一个接口,下面看下其定义:
public interface ParameterHandler {
Object getParameterObject();
void setParameters(PreparedStatement ps)
throws SQLException;
}
主要就是两个方法,getParameter用来获取传入的参数和setParameters给传入的PrepareStatement对象设置参数。mybatis提供的默认实现是DefaultParameterHandler,下面主要看下其setParameters的实现:
@Override
public void setParameters(PreparedStatement ps) {
ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
//parameterMappings上参数名称是按设置顺序存储的,所以只需要依次拉取出这些参数名称对应的参数值设置到PreparedStatement指定位置上去就行了
List 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;
String propertyName = parameterMapping.getProperty();//sql中以#{}形式设置的参数名,下面主要是确定这个参数对应的值
//依次从BoundSql对象中的additionalParameter parameterObject metaObject中取值.
if (boundSql.hasAdditionalParameter(propertyName)) { // issue #448 ask first for additional params
value = boundSql.getAdditionalParameter(propertyName);
} else if (parameterObject == null) {
value = null;
} else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
value = parameterObject;
} else {
MetaObject metaObject = configuration.newMetaObject(parameterObject);
value = metaObject.getValue(propertyName);
}
//使用typeHandler对参数进行处理,这里的typeHandler可以是mapper.xml中配置的.如果进行配置,
//则会使用mybatis默认提供的typeHandler进行处理
TypeHandler typeHandler = parameterMapping.getTypeHandler();
JdbcType jdbcType = parameterMapping.getJdbcType();
if (value == null && jdbcType == null) {
jdbcType = configuration.getJdbcTypeForNull();
}
try {
typeHandler.setParameter(ps, i + 1, value, jdbcType);
} catch (TypeException e) {
throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
} catch (SQLException e) {
throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
}
}
}
}
}
ResultSetHandler的主要作用就是对查询之后的结果进行处理;前面在StatementHandler中通过PrepareStatement的execute()方法执行完查询之后,就调用ResultSetHandler的handleResultSets()方法来处理的结果。下面是这个接口的定义:
public interface ResultSetHandler {
List handleResultSets(Statement stmt) throws SQLException;
Cursor handleCursorResultSets(Statement stmt) throws SQLException;
void handleOutputParameters(CallableStatement cs) throws SQLException;
}
前两个方法都是用来对结果集进行处理的,handleOutputParameters()是用来处理存储过程输入输出参数的。mybatis为这个接口提供的默认实现类是DefaultResultSetHandler。这些方法的实现都比较繁琐,先是使用JAVASSIST或者CGLIB进行延迟加载,然后使用TypeHandler和ObjectFactory进行结果组装返回,这里就不再展说了,有兴趣的可以自己去看下源码。
经过上面四个对象的介绍,其实整个SqlSession运行的原理应该已经非常清楚了,下面再通过一张图来回顾下SqlSession执行一次查询和更新的逻辑:
SqlSession是通过Executor创建StatementHandler来运行的,而StatementHandler要经过下面三步: