前面我们说过, 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
处理结果