mybatis中的 JDBC处理器 StatementHandler

1、StatementHandler 组件和其他组件之间的调用关系。

MyBatis一个基于JDBC的Dao框架,MyBatis把所有跟JDBC相关的操作全部都放到了StatementHandler中。

一个SQL请求会经过会话,然后是执行器,最由StatementHandler执行jdbc最终到达数据库。其关系如下图:

 

 

 这里要注意这三者之间比例是1:1:n。也就是说一个sqlsession会对应唯一的一个执行器 和N个StatementHandler。这里的N取决于通过会话调用了多少次Sql,命中缓存除外。

2、StatementHandler 的定义:

  JDBC处理器,基于JDBC构建statement,并设置参数,然后执行sql。每次调用会话当中一次sql,都会有与之相对应的且唯一的statement实例。

3、StatementHandler 的结构

 

 

 StatementHandler是接口,BaseStatementHandler是实现StatementHandler的抽象方法,其中主要放一些SimpleStatementHandler、PreparedStatementHandler、CallableStatementHandler三个子类的共性操作,比如setStatementTimeout()、setFetchSize等操作。三个子类分别对应JDBC中的Statement、PreparedStatement、CallableStatement。

public interface StatementHandler {/*statement的接口*/

  Statement prepare(Connection connection, Integer transactionTimeout)
      throws SQLException;/*声明statement*/

  void parameterize(Statement statement)
      throws SQLException;/*设置参数*/

  void batch(Statement statement)
      throws SQLException;/*添加批处理 并非执行*/

  int update(Statement statement)
      throws SQLException;/*执行更新 对用excutor执行器的update*/

  <E> List<E> query(Statement statement, ResultHandler resultHandler)
      throws SQLException;/*执行查询 对用excutor执行器的query*/

  <E> Cursor<E> queryCursor(Statement statement)
      throws SQLException;/*查询游标*/

  BoundSql getBoundSql();/**/

  ParameterHandler getParameterHandler();/*获取参数处理器*/

}
public abstract class BaseStatementHandler implements StatementHandler {

、、、、、、、
  @Override
  public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
    ErrorContext.instance().sql(boundSql.getSql());
    Statement statement = null;
    try {
      statement = instantiateStatement(connection);/*具体的statement创建交给具体的实现类 SimpleStatementHandler、PreparedStatementHandler、CallableStatementHandler*/
      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);
    }
  }

  protected abstract Statement instantiateStatement(Connection connection) throws SQLException;/*具体的statement创建交给具体的实现类 */
}
public class PreparedStatementHandler extends BaseStatementHandler {

、、、、、、、
  @Override
  protected Statement instantiateStatement(Connection connection) throws SQLException {/*具体实现,产生一个prepareStatement*/
    String sql = boundSql.getSql();
    if (mappedStatement.getKeyGenerator() instanceof Jdbc3KeyGenerator) {
      String[] keyColumnNames = mappedStatement.getKeyColumns();
      if (keyColumnNames == null) {
        return connection.prepareStatement(sql, PreparedStatement.RETURN_GENERATED_KEYS);
      } else {
        return connection.prepareStatement(sql, keyColumnNames);
      }
    } else if (mappedStatement.getResultSetType() == ResultSetType.DEFAULT) {
      return connection.prepareStatement(sql);
    } else {
      return connection.prepareStatement(sql, mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY);
    }
  }

、、、、、、

}

4、statementhandler处理流程解析

 

 

 

总共执行过程分为三个阶段:
   预处理:这里预处理不仅仅是通过Connection创建Statement,还包括设置参数。
   执行:包含执行SQL和处理结果映射两部分。
   关闭:直接关闭Statement。

 

public class SimpleExecutor extends BaseExecutor {
、、、、、、

  @Override
  public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {/*excutor此处开始进入statementhandler*/
    Statement stmt = null;
    try {
      Configuration configuration = ms.getConfiguration();
      StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);/*通过大管家Configuration 获取statementhandle*/
      stmt = prepareStatement(handler, ms.getStatementLog());/*statementhandle来创建statement,设置参数*/
      return handler.query(stmt, resultHandler);/*statementhandle来执行查询*/
    } finally {
      closeStatement(stmt);
    }
  }
、、、、、、
}


public class Configuration {
、、、、、、
  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);/*通过包装类RoutingStatementHandler类决定创建哪一种statement处理器 SimpleStatementHandler、PreparedStatementHandler、CallableStatementHandler  .该类存在的意义不大,仅仅做了一个判断,完全可以在Configuration 当前类中完成*/
    statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);/*添加插件*/
    return statementHandler;
  }
、、、、、、
}


public class RoutingStatementHandler implements StatementHandler {
、、、、、
  private final StatementHandler delegate;/包装StatementHandler /
、、、、、、
  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());
    }
、、、、、、、
  }

}
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
Connection connection = getConnection(statementLog);
stmt = handler.prepare(connection, transaction.getTimeout());/*创建statement,首先是basestatementhandler来处理一些共性参数设置,然后是对应的preparestatementhandler等其他处理器来执行具体的创建工作*/
handler.parameterize(stmt);/*设置参数*/
return stmt;
}
 

其中 statementhandle来执行查询

public class PreparedStatementHandler extends BaseStatementHandler {

、、、、、、
  @Override
  public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
    PreparedStatement ps = (PreparedStatement) statement;
    ps.execute();/*执行statement*/
    return resultSetHandler.handleResultSets(ps);/最终使用结果集处理器来处理执行结果/
  }

、、、、、、

}

 

其中statementhandler的类型的设置过程。

UserMapper:

@Select({" select * from users where name='${name}'"})
@Options(statementType = StatementType.PREPARED)
List<User> selectByName(User user);

具体背后的代码逻辑

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Options {
、、、、、、
  boolean useCache() default true;

  FlushCachePolicy flushCache() default FlushCachePolicy.DEFAULT;

  ResultSetType resultSetType() default ResultSetType.DEFAULT;

  StatementType statementType() default StatementType.PREPARED;/*参数*/

  int fetchSize() default -1;

  int timeout() default -1;

  boolean useGeneratedKeys() default false;

  String keyProperty() default "";

  String keyColumn() default "";

  String resultSets() default "";
}


public enum StatementType {
  STATEMENT, PREPARED, CALLABLE  /*三种类型*/
}

5、

参数处理
参数处理即将Java Bean转换成数据类型。总共要经历过三个步骤,参数转换、参数映射、参数赋值。

 

 

 

参数转换

即将JAVA 方法中的普通参数,封装转换成Map,以便map中的key和sql的参数引用相对应。

@Select({"select * from users where name=#{name} or age=#{user.age}"})
@Options
User selectByNameOrAge(@Param("name") String name, @Param("user") User user);
  • 单个参数的情况下且没有设置@param注解会直接转换,勿略SQL中的引用名称。

  • 多个参数情况:优先采用@Param中设置的名称,如果没有则用参数预号代替 即"param1、parm2...."

  • 如果javac编译时设置了 -parameters 编译参数,也可以直接获取源码中的变量名称作为key

以上所有转换逻辑均在ParamNameResolver中实现。

 (1)单个参数:

  

测试代码:

@Select({"select * from users where name=#{name} or age=#{user.age}"})
@Options
User selectByNameOrAge(@String name, @Param("user") User user);
public void test2() {
    mapper.selectByNameOrAge("肥仔", Mock.newUser());
}

 

public class ParamNameResolver {

  private static final String GENERIC_NAME_PREFIX = "param";
    private final SortedMap<Integer, String> names;
  、、、、、、
  public ParamNameResolver(Configuration config, Method method) {
    final Class<?>[] paramTypes = method.getParameterTypes();
    final Annotation[][] paramAnnotations = method.getParameterAnnotations();
    final SortedMap<Integer, String> map = new TreeMap<>();
    int paramCount = paramAnnotations.length;
    // get names from @Param annotations
    for (int paramIndex = 0; paramIndex < paramCount; paramIndex++) {
      if (isSpecialParameter(paramTypes[paramIndex])) {
        // skip special parameters
        continue;
      }
      String name = null;
      for (Annotation annotation : paramAnnotations[paramIndex]) {
        if (annotation instanceof Param) {
          hasParamAnnotation = true;
          name = ((Param) annotation).value();
          break;
        }
      }
      if (name == null) {
        // @Param was not specified.
        if (config.isUseActualParamName()) {
          name = getActualParamName(method, paramIndex);
        }
        if (name == null) {
          // use the parameter index as the name ("0", "1", ...)
          // gcode issue #71
          name = String.valueOf(map.size());
        }
      }
      map.put(paramIndex, name);
    }
    names = Collections.unmodifiableSortedMap(map);
  }

  、、、、、、
  public Object getNamedParams(Object[] args) {//args就是CRUD函数入参传入的值[0,肥仔]
    final int paramCount = names.size();//names就是CRUD函数的入参的名字[1,name],如果没有使用参数注解@param(name),就是[0,arg0;1,user]
    if (args == null || paramCount == 0) {
      return null;
    } else if (!hasParamAnnotation && paramCount == 1) {
      return args[names.firstKey()];//单个参数不需要和sql语句中的缺省值对应,直接返回
    } else {
      final Map<String, Object> param = new ParamMap<>();
      int i = 0;
      for (Map.Entry<Integer, String> entry : names.entrySet()) {
        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
        if (!names.containsValue(genericParamName)) {
          param.put(genericParamName, args[entry.getKey()]);//
        }
        i++;
      }
      return param;//param 就是解析之后的值[arg0,肥仔;parame1,肥仔]    //最终基于该map去映射到sql的占位符参数中
    }
  }
、、、、、、
}

 

 

 

arg0基于反射来的,因为parameters 编译参数没有打开,所以直接映射成arg0,arg1。

user是基于注解来的@param

param1,param2是基于顺序来的。

 

mybatis中获取参数名称的方式是反射。

获取参数的其他方法:

如果是函数的直接通过字节码插装方式可以获得。代码编译后,使用IDEA---->show bytecode 可以看到字节码。如下;可以获取参数名称 。

public class ParamTest {
    private SqlSession sqlSession;
    private UserMapper mapper;
    public static void main(String[] args1,int args2){

    }
}

编译后的字节码:
 public static main([Ljava/lang/String;I)V //编译入参的参数名称变了,但是下边的局部变量表中还是有的
   L0
    LINENUMBER 27 L0
    RETURN
   L1
    LOCALVARIABLE args1 [Ljava/lang/String; L0 L1 0 //参数名称1
    LOCALVARIABLE args2 I L0 L1 1   //参数名称2
    MAXSTACK = 0
    MAXLOCALS = 2

参数映射

映射是指Map中的key如何与SQL中绑定的参数相对应。以下这几种情况

  • 单个原始类型:直接映射,勿略SQL中引用名称

  • Map类型:基于Map key映射

  • Object:基于属性名称映射,支持嵌套对象属性访问

在Object类型中,支持通过“.”方式映射属中的属性。如:user.age

参数赋值

通过TypeHandler 为PrepareStatement设置值,通常情况下一般的数据类型MyBatis都有与之相对应的TypeHandler

测试代码:

@Select({"select * from users where name=#{name} or age=#{user.age}"})
@Options
User selectByNameOrAge(@String name, @Param("user") User user);
public void test2() {
    mapper.selectByNameOrAge("肥仔", Mock.newUser());
}

 

 

public class DefaultParameterHandler implements ParameterHandler {
  private final Object parameterObject;//参数解析器,解析的map对象
、、、、、、、
  public DefaultParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
    this.mappedStatement = mappedStatement;
    this.configuration = mappedStatement.getConfiguration();
    this.typeHandlerRegistry = mappedStatement.getConfiguration().getTypeHandlerRegistry();
    this.parameterObject = parameterObject; //入参,参数解析器,解析的map对象
    this.boundSql = boundSql;
  }

  @Override
  public void setParameters(PreparedStatement ps) {
    ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
    List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();//获取sql中的占位符参数列表
    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();//获取占位符的名称 name ; user.age
          if (boundSql.hasAdditionalParameter(propertyName)) { // issue #448 ask first for additional params
            value = boundSql.getAdditionalParameter(propertyName);//hasAdditionalParameter主要用于多数据库的情况,比如mysql数据库用一个参数,orcal用另一个参数。也就是给参数增加其他的的操作。较少使用
   } else if (parameterObject == null) { value = null; } 
else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {//因为只有一个参数时,才能找到对应的类型处理器,map类型没有对应的类型处理器,所以此处能判断是不是单个。
value
= parameterObject;//单个参数直接赋值,不管sql中参数名是神马。 } else { MetaObject metaObject = configuration.newMetaObject(parameterObject);//封装参数解析器解析的参数列表
value = metaObject.getValue(propertyName);//通过metaObject工具类获取参数的值,其中对象的嵌套查询(user.age)也是metaobject完成 //使用sql中的参数去 javabean的参数解析列表map中去获取值。
} TypeHandler typeHandler = parameterMapping.getTypeHandler();//获取类型处理器 。xml中没有配置时,默认使用sql中参数类型对应的类型处理器
JdbcType jdbcType = parameterMapping.getJdbcType();//和javabean 也就是CRUD的入参所对应的数据库中的类型
if (value == null && jdbcType == null) { jdbcType = configuration.getJdbcTypeForNull(); }
try { typeHandler.setParameter(ps, i + 1, value, jdbcType);//类型处理器通过statement来设置参数。最终就是prestatement的设置操作 }
catch (TypeException | SQLException e) {
throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e); } } } } } //结束 }

public class StringTypeHandler extends BaseTypeHandler<String> {

@Override
public void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType)
throws SQLException {
ps.setString(i, parameter);//prestatement设置参数
}
 

 

 

 

 5、结果集封装

指读取ResultSet数据,并将每一行转换成相对应的对象。用户可在转换的过程当中可以通过ResultContext来控制是否要继续转换。转换后的对象都会暂存在ResultHandler中最后统一封装成list返回给调用方

image-20200609173114311

resultsethandler一行一行解析处理(行),解析一行将结果发送给resultcontext(一个一个的对象),context主要可以判断是否继续往下解析,可以作为分页使用。最后将结果发送给resulthandle,存放到list中

结果集转换中99%的逻辑DefaultResultSetHandler 中实现。整个流程可大致分为以下阶段:

  1. 读取结果集

  2. 遍历结果集当中的行

  3. 创建对象

  4. 填充属性

posted @ 2020-09-06 14:54  小南的歌  阅读(794)  评论(0编辑  收藏  举报