当前位置 博文首页 > 文章内容

    mybatis中的 JDBC处理器 StatementHandler

    作者: 栏目:未分类 时间:2020-09-06 15:00:27

    本站于2023年9月4日。收到“大连君*****咨询有限公司”通知
    说我们IIS7站长博客,有一篇博文用了他们的图片。
    要求我们给他们一张图片6000元。要不然法院告我们

    为避免不必要的麻烦,IIS7站长博客,全站内容图片下架、并积极应诉
    博文内容全部不再显示,请需要相关资讯的站长朋友到必应搜索。谢谢!

    另祝:版权碰瓷诈骗团伙,早日弃暗投明。

    相关新闻:借版权之名、行诈骗之实,周某因犯诈骗罪被判处有期徒刑十一年六个月

    叹!百花齐放的时代,渐行渐远!



    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

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

    1. 读取结果集

    2. 遍历结果集当中的行

    3. 创建对象

    4. 填充属性