前面我们说过, Executor
的主要职责是执行底层映射语句。
但是通过源码我们可以发现, Executor
执行的这些功能,都是通过 StatementHandler
来完成的, Executor
只是负责缓存或者选择调用 StatmentHandler
的具体的方法。
下面来看看 StatementHandler
的具体实现
//新建StatmentHandler
public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
//创建RoutingStatementHandler
StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
//添加插件
statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
return statementHandler;
}
这个方法是所有创建 StatementHandler
的方法,可以看到都是统一的创建 RoutingStatementHandler
.
RoutingStatementHandler
是一个代理类,里面会根据参数调用真正的 StatmentHandler
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
中,一共返回了3中 StatementHandler
,分别是:
SimpleStatementHandler
:简单的 Statement
,对应 JDBC
中的 SimpleStatement
PreparedStatementHandler
: 预编译的 Statement
,对应 JDBC
中的 PreparedStatement
CallableStatementHandler
: 存储过程 Statement
, 对应 JDBC
中的 CallableStatement
看到这里,可以发现,只要知道 JDBC
中这些 Statment
的区别,就能明白这些 Handler
的区别,在 MyBatis
中,默认使用的是 PreparedStatmentHandler
,此 Statement
会预编译 SQL
,使得执行速度更快。
在 MyBatis
中,使用 StatmentHandler
分为3步
新建 StatmentHandler
– -> prepare()
–> parameterize()
–> doAction()
@Override
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);
}
}
其中, fetchSize
是用来设置数据库一次获取的数据量
对于 MySQL
(其他数据库不一定,可能是10)默认情况下,比如 select *
,数据库会直接返回所有的数据,而这个时候可能会使得 JVM
OOM
,而此时则可以通过设置 fetchSize
,使得 Java
程序可以一点一点的处理。
对于其他数据库,是支持 fetchSize
的时候, fetchSize
太小可能会影响性能,因此可以通过设置此值进行调试
想要此值生效,必须开启事务
https://blog.csdn.net/seven_3306/article/details/9303979
@Override
protected Statement instantiateStatement(Connection connection) throws SQLException {
String sql = boundSql.getSql();
//如果设置了需要获取自增长id
if (mappedStatement.getKeyGenerator() instanceof Jdbc3KeyGenerator) {
String[] keyColumnNames = mappedStatement.getKeyColumns();
//设置JDBC需要返回id
if (keyColumnNames == null) {
return connection.prepareStatement(sql, PreparedStatement.RETURN_GENERATED_KEYS);
} else {
return connection.prepareStatement(sql, keyColumnNames);
}
//如果没有设置ResultSet 则使用默认的ResultSet
} else if (mappedStatement.getResultSetType() == ResultSetType.DEFAULT) {
return connection.prepareStatement(sql);
} else {
//否则使用用户设置色`ResultSet`
return connection.prepareStatement(sql, mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY);
}
}
有关 ResultSetType
详细信息,可以见 JDBC 查询的三大参数
ParameterSize
大概的含义就是处理参数,主要在 PreparedStatementHandler
中,用于 setString() / setInteger()
等等
@Override
public void setParameters(PreparedStatement ps) {
//添加日志信息
ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
//获取此行命令需要的ParamMapping
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 typeHandler = parameterMapping.getTypeHandler();
JdbcType jdbcType = parameterMapping.getJdbcType();
if (value == null && jdbcType == null) {
jdbcType = configuration.getJdbcTypeForNull();
}
//调用typeHandler处理参数
try {
typeHandler.setParameter(ps, i + 1, value, jdbcType);
} catch (TypeException | SQLException e) {
throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
}
}
}
}
}
可以看到上面代码虽然比较多,但是其实就是循环处理需要的参数而已。
而对于参数分为两种:
typeHandler
这里有一个小问题,在于上面的代码中,参数始终为 parameterObject
那如果存在多个参数的情况怎么办?
我们知道 MyBatis
的接口是通过动态代理实现的。
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
} else if (method.isDefault()) {
if (privateLookupInMethod == null) {
return invokeDefaultMethodJava8(proxy, method, args);
} else {
return invokeDefaultMethodJava9(proxy, method, args);
}
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
final MapperMethod mapperMethod = cachedMapperMethod(method);
return mapperMethod.execute(sqlSession, args);
}
以上是动态代理接口的方法,可以看见参数其实是一个数组,也可以是一个可变参数,因次不管接口中包含多少个参数, MyBatis
都会收到一个数组。
当拿到数组后, MyBatis
会结合注解进行处理:
public Object getNamedParams(Object[] args) {
final int paramCount = names.size();
if (args == null || paramCount == 0) {
return null;
}
//如果没有注解,并且参数数量为1,直接返回
else if (!hasParamAnnotation && paramCount == 1) {
return args[names.firstKey()];
}
//否者使用HashMap进行包装
else {
final Map<String, Object> param = new ParamMap<>();
int i = 0;
for (Map.Entry<Integer, String> entry : names.entrySet()) {
//添加key value
//例如@Param("name") 则 key 为 name , value 为实际的值
param.put(entry.getValue(), args[entry.getKey()]);
// add generic param names (param1, param2, ...)
final String genericParamName = GENERIC_NAME_PREFIX + String.valueOf(i + 1);
// ensure not to overwrite parameter named with @Param
// 同时也会添加类似 key param1 value 为实际的值到map中
if (!names.containsValue(genericParamName)) {
param.put(genericParamName, args[entry.getKey()]);
}
i++;
}
return param;
}
}
这里的代码便对应了 MyBatis
的两种参数标记方法,第一种为 @Param(name)
,第二种为 param1
值得注意的是, paramSize()
只有在 Statement
为 PREPARED
才会有参数设置,否则 MyBatis
将不会解析对应的参数。
剩下的操作便是执行真正的操作:
@Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
//获取Statement
PreparedStatement ps = (PreparedStatement) statement;
//执行
ps.execute();
//处理结果
return resultSetHandler.handleResultSets(ps);
}
这里看到执行便是通过 JDBC
执行 Statement
,然后调用 ResultSetHandler
处理结果。
对于 ResultSetHandler
在下一节介绍
回忆前面几篇,我们可以分析出来, MyBatis
执行过程调用的组件:
首先,通过 SqlSessionFactory
获取 SqlSession
然后,在获取 SqlSession
的过程中,会通过配置文件配置 Transaction
然后,通过以上几个参数,构建出一个 Executor
, Executor
分为3种,对于3中不同的处理/新建 StatmentHandler
的行为
再然后, Executor
会调用 StatementHandler
各个方法,配置参数,调用 ParameterHandler
处理参数等,最后再进行执行,其中 StatementHnalder
分为3中。 STATEMENT
, PREPARED
, CALLBACK
,每个方法配置一个,默认为 PREPARED
最后, StatementHandler
调用 statement
执行 SQL
,然后调用 ResultSetHandler
处理结果