BaseExecutor.query(ms, ... , boundSql)方法执行流程
query()它的逻辑很简单:
- 尝试从缓存获取,获取不到或不具备从缓存获取条件(即resultHandler != null),则从数据库中获取
- 方法的核心在于queryFromeDataBase(ms, parameter, rowBounds, resultHandler, key, boundSql)。
- 查完之后放入本地缓存
queryFromeDataBase(ms, param, ... ,cacheKey, boundSql)
这个方法目前没什么好说的,且看doQuery(...)方法吧
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 {
//todo:重点关注
list = this.doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
} finally {
this.localCache.removeObject(key);
}
// 放到缓存
this.localCache.putObject(key, list);
// CALLABLE的sql语句, 作为存放到参数缓存。也就是说,该语句的执行结果可以作为参数,被其他的语句使用。
if (ms.getStatementType() == StatementType.CALLABLE) {
this.localOutputParameterCache.putObject(key, parameter);
}
return list;
}
queryFromeDataBase(ms, ... , boundSql)的核心在于doQuery(ms, ... , boundSql),重点来了。
可以看到这个方法的就做了四件事:
- 创建StatementHandler
- 通过statementHandler解析出statement
- 利用statementHandler完成查询
- closeStatement.
其主体部分都离不开StatementHandler, 因此可以说,搞明白了StatementHandler , 也就搞明白了doQuery.我们知道StatementHandler、ParameterHandler、ResultSetHandler和Executor是mybatis的四大核心对象,所以在这里应当留心StatementHandler是如何被创建的、内部有哪些重要属性,以及它有哪些作用。
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;
}
1. 方法创建statementHandler时的参数包含了哪些信息,可以让我们与数据库交互并查到数据
configuration.newStatementHandler(this.wrapper, ms, parameter, rowBounds, resultHandler, boundSql)
-
this.wrapper: 注意,我们虽然在这里执行的是父类的实例方法,但当前对象是simpleExecutor.前面提到过,cachingExecutor的delegate属性是当前的simpleExecutor对象,而当前的simpleExecutor对象的wrapper则是这个cachingExecutor。executor内封装了执行该sql所需的事务信息transaction,transaction内部又包含了数据库连接信息和事务是否自动提交、事务隔离级别,如dataSource。所以通过wrapper可以来完成数据库访问和事务控制。
当前系统中cachingExecurot与simpleExecutor的关系
-
-
- MappedStatement: ms中有哪些值得关注的属性呢
- statementType: STATEMENT| PREPARED | CALLABLE. 分别对应原生jdbc中的stmt、preparedStmt和CallableStatement。前两个好理解,CallableStatement主要是调用数据库中的存储过程,接收存储过程的返回值。存储过程case
-
rowBounds: rowBounds, 分页参数,包含offset和limit两个属性
rowBounds
-
- ResultHandler: resultHandler
-
- BoundSql: boundSql
- sql: 将#{}或${}替换成"?"之后的sql语句
- parameterMappings: ParameterMapping类型的数组,他包含对sql语句中参数的描述信息,如参数名,javaType, jdbcType等信息,但是并不包含参数的取值。参数在sql中的位置与在parameterMappings中的位置是一致的。
-
parameterObject: parameterMap. mapperMethod将方法的参数解析完成之后,以paramName: paramValue的形式存放在了该paramMap中。
boundSql属性
总之,数据库连接,事务控制,执行sql所需的语句,sql中参数的位置、名称和取值信息现在都有了。
2. StatementHandler的创建过程
- 首先创建一个简单的RoutingStatementhandler
- 再通过拦截器链为他添加插件,与对simpleExecutor的添加插件原理是一样的:迭代每一个插件,为当前的target创建代理对象,并将代理对象重新赋值给target.
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 = (StatementHandler)this.interceptorChain.pluginAll(statementHandler);
return statementHandler;
}
创建RoutingStatementhandler
判断当前statement的类类型,调用对应的构造方法,将结果作为当前RoutingStatementhandler的代理。此后实际干活的都是这个delegate.
public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
switch(ms.getStatementType()) {
case STATEMENT:
this.delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break;
case PREPARED:
this.delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break;
case CALLABLE:
this.delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break;
default:
throw new ExecutorException("Unknown statement type: " + ms.getStatementType());
}
}
这三种statementHandler从构造方法上来讲都是一样的,只是最终创建出来的handler类型不一样。
public class PreparedStatementHandler extends BaseStatementHandler {
public PreparedStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
super(executor, mappedStatement, parameter, rowBounds, resultHandler, boundSql);
}
...
}
statementHandler的构造方法都是调父类构造方法super(xxxxx)。
protected BaseStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
this.configuration = mappedStatement.getConfiguration();
this.executor = executor;
this.mappedStatement = mappedStatement;
this.rowBounds = rowBounds;
this.typeHandlerRegistry = this.configuration.getTypeHandlerRegistry();
this.objectFactory = this.configuration.getObjectFactory();
if (boundSql == null) {
this.generateKeys(parameterObject);
boundSql = mappedStatement.getBoundSql(parameterObject);
}
this.boundSql = boundSql;
this.parameterHandler = this.configuration.newParameterHandler(mappedStatement, parameterObject, boundSql);
this.resultSetHandler = this.configuration.newResultSetHandler(executor, mappedStatement, rowBounds, this.parameterHandler, resultHandler, boundSql);
}
创建该对象过程中重要的点在于parameterHandler和resultSetHandler.(他们也属于四个核心对象,这下核心对象就全部创建完了)。ParameterHandler负责为 PreparedStatement 的 sql 语句参数动态赋值。 ResultSetHandler负责处理两件事:(1)处理Statement执行后产生的结果集,生成结果列表(2)处理存储过程执行后的输出参数。关于这两个组件,等有时间另外拧出来讲。
- 在创建完stmtHandler之后,就要为他添加插件了。
添加插件pluginAll伪代码
//pluginAll伪代码
foreach interceptor in chain{
target = interceptor.plugin(target);
}
return target;
interceptor.plugin(target)创建代理对象
// interceptor.plugin(target)
default Object plugin(Object target) {
return Plugin.wrap(target, this);
}
public static Object wrap(Object target, Interceptor interceptor) {
Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
Class<?> type = target.getClass();
Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
return interfaces.length > 0 ? Proxy.newProxyInstance(type.getClassLoader(), interfaces, new Plugin(target, interceptor, signatureMap)) : target;
}
-
statementHandler内部结构
如图:
statementHandler -
statementHandler有什么作用?query(stmt, resultHandler)是如何工作的?
StatementHandler 是四大组件中最重要的一个对象,负责操作 Statement 对象与数据库进行交流,在工作时还会使用 ParameterHandler 和 ResultSetHandler 对参数进行映射,对结果进行实体类的绑定。这句话总感觉干巴巴的,为什么呢?且先不说ParameterHandler 和 ResultSetHandler。问几个问题:Statement对象是谁啊?statementHandler凭什么能操作它与跟数据库的交流?交流的过程是什么?
在上面说executor.doQuery()的时候,有这样一段代码来完成数据的查询。它创建了StatementHandler,利用handler创建了Statement对象,最后由handler去根据该statement完成数据的查询。
2. Statement的创建过程
重新post一份doQuery代码, 现在我们完成了StatementHandler的创建。
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();
// here we are
StatementHandler handler = configuration.newStatementHandler(this.wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
// the next thing to do
stmt = this.prepareStatement(handler, ms.getStatementLog());
var9 = handler.query(stmt, resultHandler);
} finally {
this.closeStatement(stmt);
}
return var9;
}
接下来就要解析sql语句了,之前的sql语句信息在boundSql中。我们来看看如何解析吧。
//executor.prepareStatement()
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
// 获取连接对象
Connection connection = this.getConnection(statementLog);
// 创建statement对象
Statement stmt = handler.prepare(connection, this.transaction.getTimeout());
// statement对象参数化
handler.parameterize(stmt);
return stmt;
}
- 1.获取连接
之前讲过,数据库连接信息封装在了transaction中,包括connection和dataSource.那么如果transaction.connection != null,也就是创建了,就直接返回该对象。如果transaction内的connection对象还没创建,那就创建一个connection对象。
executor.getConnection()
protected Connection getConnection(Log statementLog) throws SQLException {
Connection connection = this.transaction.getConnection();
return statementLog.isDebugEnabled() ? ConnectionLogger.newInstance(connection, statementLog, this.queryStack) : connection;
}
transaction.getConnection(): 如果connection已经创建直接返回该对象,没创建则利用dataSource去创建一个,然后设置隔离级别和是否自动提交。
public Connection getConnection() throws SQLException {
if (this.connection == null) {
this.openConnection();
}
return this.connection;
}
protected void openConnection() throws SQLException {
if (log.isDebugEnabled()) {
log.debug("Opening JDBC Connection");
}
this.connection = this.dataSource.getConnection();
if (this.level != null) {
this.connection.setTransactionIsolation(this.level.getLevel());
}
this.setDesiredAutoCommit(this.autoCommit);
}
- 创建Statement
核心在于instantiateStatement
- 创建Statement
public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
ErrorContext.instance().sql(this.boundSql.getSql());
Statement statement = null;
try {
// Look here
statement = this.instantiateStatement(connection);
this.setStatementTimeout(statement, transactionTimeout);
this.setFetchSize(statement);
return statement;
} catch (SQLException var5) {
this.closeStatement(statement);
throw var5;
} catch (Exception var6) {
this.closeStatement(statement);
throw new ExecutorException("Error preparing statement. Cause: " + var6, var6);
}
}
// BaseStatementHandler中的该方法为抽象方法,由其子类实现
protected abstract Statement instantiateStatement(Connection var1) throws SQLException;
BaseStatementHandler中的instantiateStatement为抽象方法,咱现在用的是他的子类PreparedStatementHandler,来看看是如何实现的吧。
//PreparedStatementHandler.instantiateStatement(connection)
protected Statement instantiateStatement(Connection connection) throws SQLException {
String sql = this.boundSql.getSql();
if (this.mappedStatement.getKeyGenerator() instanceof Jdbc3KeyGenerator) {
String[] keyColumnNames = this.mappedStatement.getKeyColumns();
return keyColumnNames == null ? connection.prepareStatement(sql, 1) : connection.prepareStatement(sql, keyColumnNames);
} else {
return this.mappedStatement.getResultSetType() == ResultSetType.DEFAULT ? connection.prepareStatement(sql) : connection.prepareStatement(sql, this.mappedStatement.getResultSetType().getValue(), 1007);
}
}
从boundSql中获取其sql属性,然后connection.prepareStatement(...)
public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
ErrorContext.instance().sql(this.boundSql.getSql());
Statement statement = null;
try {
statement = this.instantiateStatement(connection);
this.setStatementTimeout(statement, transactionTimeout);
this.setFetchSize(statement);
return statement;
} catch (SQLException var5) {
this.closeStatement(statement);
throw var5;
} catch (Exception var6) {
this.closeStatement(statement);
throw new ExecutorException("Error preparing statement. Cause: " + var6, var6);
}
}
经过这一步操作,statement对象就实例化了,有了数据库连接的connection和sql语句信息,但是sql语句还是没有参数化。
- 3.statement参数化
public void setParameters(PreparedStatement ps) {
ErrorContext.instance().activity("setting parameters").object(this.mappedStatement.getParameterMap().getId());
List<ParameterMapping> parameterMappings = this.boundSql.getParameterMappings();
if (parameterMappings != null) {
for(int i = 0; i < parameterMappings.size(); ++i) {
ParameterMapping parameterMapping = (ParameterMapping)parameterMappings.get(i);
if (parameterMapping.getMode() != ParameterMode.OUT) {
String propertyName = parameterMapping.getProperty();
Object value;
if (this.boundSql.hasAdditionalParameter(propertyName)) {
value = this.boundSql.getAdditionalParameter(propertyName);
} else if (this.parameterObject == null) {
value = null;
} else if (this.typeHandlerRegistry.hasTypeHandler(this.parameterObject.getClass())) {
value = this.parameterObject;
} else {
MetaObject metaObject = this.configuration.newMetaObject(this.parameterObject);
value = metaObject.getValue(propertyName);
}
TypeHandler typeHandler = parameterMapping.getTypeHandler();
JdbcType jdbcType = parameterMapping.getJdbcType();
if (value == null && jdbcType == null) {
jdbcType = this.configuration.getJdbcTypeForNull();
}
try {
typeHandler.setParameter(ps, i + 1, value, jdbcType);
} catch (SQLException | TypeException var10) {
throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + var10, var10);
}
}
}
}
参数化的过程简单来说就是遍历parameterMappings数组,数组中第i个元素对应的就是sql语句中第i个参数的信息(不包含参数值),从中获取property(即参数名)。再根据参数名去boundSql的parameterObject(本质是一个map)中获取该参数名对应的参数值。最后,将该参数值拼接到sql语句后面的对应位置。参数化完成之后的statement:
com.mysql.cj.jdbc.ClientPreparedStatement: select * from article_share_info where article_id = 2
查询过程handler.query(stmt, resultHandler)
将statement转为preparedStatement, 再由它执行,最后由ResultSetHandler完成结果集的封装。
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
//转ps
PreparedStatement ps = (PreparedStatement)statement;
//执行sql语句并将结果以字节数组的形式存放在ps.results中
ps.execute();
// ResultSetHandler解析ps内的结果集,封装结果集为指定的resultMap类型并将其返回
return this.resultSetHandler.handleResultSets(ps);
}
网友评论