一、StatementHandler
我们在分析Executor的最后发现,对数据库的实际操作都是通过StatementHandler来实现,本文接着Executor中调用StatementHandler开始分析,一步步的走向最底层。
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();
//实例化handler对象
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
//初始化Statement对象
stmt = prepareStatement(handler, ms.getStatementLog());
//调用handler的查询方法。
return handler.query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
handler的实例是通过Configuration对象来初始化。查看newStatementHandler,我们得到的handler实例是RoutingStatementHandler对象(route即路由的意思,也就是说这个对象只是一个中间对象,会根据实际的需求路由到具体的handler上,所以查看其源码发现,其有个StatementHandler delegate,即委托对象,它是构造方法来决定具体使用的是什么类型的Handler,有simple、prepare、callable三种类型,可以通过statementType来配置,默认是prepare,他们分别对应着普通Statment、PreparedStatement、CallableStatement),实例化完成后会有一个插件调用的过程,所以如果我们有自定义的插件,会在这里执行一次(最早的调用应该是在实例化Executor的时候),对于插件我们就先不展开。
实例化完StatementHandler,就会调用prepareStatement得到Statement实例
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
//获取链接,从数据源DataSource中获取连接
Connection connection = getConnection(statementLog);
//调用handler的prepare方法获取Statement对象
stmt = handler.prepare(connection, transaction.getTimeout());
//参数赋值
handler.parameterize(stmt);
return stmt;
}
所以handler.prepare默认调用的是PreparedStatementHandler的prepare方法,具体的实现在父类BaseStatementHandler中
public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
ErrorContext.instance().sql(boundSql.getSql());
Statement statement = null;
try {
//初始化Statement对象
statement = instantiateStatement(connection);
//设置超时时间
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(connection),同样有三种不同的实现,普通Statement、PreparedStatement、CallableStatement。但是这里有一些属性值需要注意。
先简单介绍一下ResultSetType有四种取值:
- DEFAULT:默认值,也就是不需要做任何设置
- FORWARD_ONLY:对应着ResultSet.TYPE_FORWARD_ONLY,结果集只能一行一行的取,不能回滚到之前和之后。
- SCROLL_INSENSITIVE:对应着ResultSet.TYPE_SCROLL_INSENSITIVE,结果集可以进行回滚向前或向后
- SCROLL_SENSITIVE:对应着ResultSet.TYPE_SCROLL_SENSITIVE,结果集可以进行回滚向前或向后,并且如果数据库在这中间修改了数据,也可以反映到结果集上(需要具体的驱动支持)
ConcurrencyType这是伴随着ResultSetType设置的
- CONCUR_READ_ONLY:只读,不可以进行增删改
- CONCUR_UPDATABLE:可以利用结果集去进行增删改操作
初始化Statement对象是设置过期时间,以及fetchSize大小(也就是ResultSet每次next的时候可以取多少条数据,对于需要批量读取的,设置这个值可以提升执行效率)。
获取到Statement对象后,接下来就是进行参数值的设置,然后执行语句,处理结果集。
二、参数赋值
第一部分我们分析到获取Statement对象,接下来就是进行参数赋值的过程,源码中直接调用的就是handler.parameterize(stmt)方法-> parameterHandler.setParameters((PreparedStatement) statement)所以参数的处理有专门的ParameterHandler接口。
他的实例化是在BaseStatementHandler的构造方法里。
//最后得到的是DefaultParameterHandler实现
this.parameterHandler = configuration.newParameterHandler(mappedStatement, parameterObject, boundSql);
ParameterHandler中最重要的方法就是setParameters()方法。
public void setParameters(PreparedStatement ps) {
//首先我们从BoundSql实例中获取到参数的映射集合
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;
//获取参数名
String propertyName = parameterMapping.getProperty();
//是否有额外参数
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 = parameterMapping.getTypeHandler();
JdbcType jdbcType = parameterMapping.getJdbcType();
if (value == null && jdbcType == null) {
jdbcType = configuration.getJdbcTypeForNull();
}
try {
//根据具体的类型转换器去执行set操作,
typeHandler.setParameter(ps, i + 1, value, jdbcType);
} catch (TypeException | SQLException e) {
throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
}
}
}
}
}
从上述代码看,参数赋值比较简单,大致来说就是从BoundSql对象中获取参数映射集合,然后遍历该集合进行参数赋值。如果有特殊的类型处理器,就会调用具体的类型处理器来设置参数值,没有就是默认类型。之所以这么简单,是因为映射文件中的sql语句已经进行处理过了的,具体后续再分析。
三、ResultSetHandler
参数赋值完成,就该执行具体sql语句,然后处理结果集。结果集的处理是通过ResultSetHandler接口来实现的,他的实例也是在BaseStatementHandler的构造方法中完成。
this.resultSetHandler = configuration.newResultSetHandler(executor, mappedStatement, rowBounds, parameterHandler, resultHandler, boundSql);
最后得到的是DefaultResultSetHandler实例,通过调用handleResultSets()方法,完成结果集的处理工作。至于ResultSetHandler是怎么工作的,以及怎么一步步映射的我们将单独列出来分析。
四、总结
Executor最后都会调用StatementHandler来执行具体的数据库操作,configuration对象会实例化一个RoutingStatementHandler然后在委派到具体的StatementHandler(Prepared、Simple、Callable)。此时,具体的StatementHandler会在构造方法中实例化两个Handler,一个是ParameterHandler,用来处理参数赋值,另一个是ResultSetHandler,用来处理结果集映射,这样整个执行逻辑就算是完成。
但是结果集是怎么映射的?sql语句又是怎么解析的?为什么直接调用接口方法就可以执行数据库操作?又是怎么和spring结合的?后续我们将一一进行分析,然后再不断地拓展。
以上,有任何不对的地方,请指正,敬请谅解。