mybatis源码解读(八):Statement 语句执行详解_mybatis的statement-程序员宅基地

技术标签: java  mybatis  mybatis jar 源码  

功能

mybatis执行语句的操作是由StatementHandler完成的,它会去获取连接并且根据你配置的一些参数来准备好连接数据库的statment语句,然后通过StatementHandler执行语句并且将结果根据你配置的ResultMap封装成为你想要的实体类。
StatementHandler完成的操作:

  • 获取连接,根据配置准备数据库可执行的statement语句。
  • 执行语句
  • 通过ResultSetHandler根据resultMapping封装成为你想要的的实体对象

UML

在这里插入图片描述
StatementHandler的子类主要有三个,这三个是根据Configuration配置的StatementType策略去分别生成的

  • SimpleStatementHandler StatementType.STATEMENT 普通的statment,有sql注入的风险
  • PreparedStatementHandler StatementType.PREPARED 预处理statment
  • CallableStatementHandler StatementType.CALLABLE 可使用执行器

实现StatementHandler的还有BaseStatementHandler,这是一个基础类,这个类中包含了一些公用的功能
RoutingStatementHandler 只是一个路由,它内部有一个策略然后去执行不同的statment

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 是在 configuration.newStatementHandler()这个方法中完成的,最终会生成一个RoutingStatementHandler 代理类,delegate 是具体的StatementHandler ,会根据MappedStatement配置的策略去生成,方法最后会将handler放到插件中去拦截

public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
    
	// 生成一个routing类   
    StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
    statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
    return statementHandler;
  }

prepareStatement()方法中,逻辑也很清晰,第一步是获取连接,第二步是生成statment语句,第三步是将入参放入到生成的statement语句中。

private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
    
    Statement stmt;
    Connection connection = getConnection(statementLog);
    stmt = handler.prepare(connection, transaction.getTimeout());
    handler.parameterize(stmt);
    return stmt;
  }

getConnection()方法中,其实是调用了JdbcTransaction的getConnection()方法,而在这个方法中又调用了openConnection(),在openConnection()中会去调用java的API 原生datasource去获取连接,在连接成功后就是设置事务的隔离级别以及是否自动提交事务等属性。

  protected Connection getConnection(Log statementLog) throws SQLException {
    
    Connection connection = transaction.getConnection();
  }
  
  // JdbcTransaction
  public Connection getConnection() throws SQLException {
    
    if (connection == null) {
    
      openConnection();
    }
    return connection;
  }
   // JdbcTransaction
   protected void openConnection() throws SQLException {
    
    // 原生api
    connection = dataSource.getConnection();
    // 隔离级别
    if (level != null) {
    
      connection.setTransactionIsolation(level.getLevel());
    }
    // 事务是否自动提交
    setDesiredAutoCommit(autoCommit);
  }

handler.prepare()方法完成的功能就是初始化statment语句

  public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
    
      /.../
      // 初始化
      statement = instantiateStatement(connection);
      // 设置超时时间
      setStatementTimeout(statement, transactionTimeout);
      // 设置每次获取的大小
      setFetchSize(statement);
      return statement;
      /.../
  }

instantiateStatement()方法中,会根据你的配置去设置一些statment语句配置,比如如果你配置了keyGenerate,他会去根据你配置的要自动生成的数据库列名来为你自动生成值并且设置主键值,然后就是如果你设置了ResultSetType类型,则会根据你的类型去生成statement,这些都是java原生操作。

protected Statement instantiateStatement(Connection connection) throws SQLException {
    
    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);
      }
    // 是否配置了resultSetType
    } else if (mappedStatement.getResultSetType() == ResultSetType.DEFAULT) {
    
      return connection.prepareStatement(sql);
    } else {
    
      return connection.prepareStatement(sql, mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY);
    }
  }

设置超时时间,这里的超时时间有个生效的优先级,首先是在事务管理器中配置的超时时间,然后是在mapper中配置的超时时间,最后才是configuration中配置的超时时间,可以从逻辑中看到,最终会有个 transactionTimeout < queryTimeout 这样的比较,这是为了保证事务中设置的超时时间优先。

protected void setStatementTimeout(Statement stmt, Integer transactionTimeout) throws SQLException {
    
    Integer queryTimeout = null;
    // mapper中设置的超时时间为第二优先级
    if (mappedStatement.getTimeout() != null) {
    
      queryTimeout = mappedStatement.getTimeout();
    // 最后是configuration的默认超时时间
    } else if (configuration.getDefaultStatementTimeout() != null) {
    
      queryTimeout = configuration.getDefaultStatementTimeout();
    }
    if (queryTimeout != null) {
    
      stmt.setQueryTimeout(queryTimeout);
    }
    // 第一优先级是事务中设置的超时时间
    StatementUtil.applyTransactionTimeout(stmt, queryTimeout, transactionTimeout);
  }

public static void applyTransactionTimeout(Statement statement, Integer queryTimeout, Integer transactionTimeout) throws SQLException {
    
    if (transactionTimeout == null) {
    
      return;
    }
    if (queryTimeout == null || queryTimeout == 0 || transactionTimeout < queryTimeout) {
    
      statement.setQueryTimeout(transactionTimeout);
    }
  }

prepare()处理完之后,回到上个方法中,可以看到 parameterize() 方法内部最终会去调用setParameters()方法,这个方法其实就是根据你的paramtermappings配置去将入参通过typehandler去精准映射, typeHandler.setParameter 方法就是往statment中塞参数。

public void setParameters(PreparedStatement ps) {
    
    ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
    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 {
    
            typeHandler.setParameter(ps, i + 1, value, jdbcType);
          } catch (TypeException | SQLException e) {
    
            throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
          }
        }
      }
    }
  }

query()方法内部,其实就是调用execute()方法,然后将结果通过ResultSetHandler结果映射到反射生成的实体类中。

public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
    
    PreparedStatement ps = (PreparedStatement) statement;
    ps.execute();
    return resultSetHandler.handleResultSets(ps);
  }

handleResultSets()方法中主要逻辑是遍历resultmap,然后将获取的结果通过 metaObject 反射赋值,首先会去获取resultSet的第一个值,这是循环的开始,然后验证一下结果是否为空,便进入循环,每次通过getNextResultSet()方法获取下一个值,然后通过handleResultSet()去赋值,退出循环的条件就是getNextResultSet()方法获取不到值。

  public List<Object> handleResultSets(Statement stmt) throws SQLException {
    
    ErrorContext.instance().activity("handling results").object(mappedStatement.getId());

    final List<Object> multipleResults = new ArrayList<>();

    int resultSetCount = 0;
    // 从statement中获取 结果
    ResultSetWrapper rsw = getFirstResultSet(stmt);
    // 获取resultMap
    List<ResultMap> resultMaps = mappedStatement.getResultMaps();
    int resultMapCount = resultMaps.size();
    // 验证resultmap是否为空
    validateResultMapsCount(rsw, resultMapCount);
    // 循环直到所有的结果全部处理完
    while (rsw != null && resultMapCount > resultSetCount) {
    
      ResultMap resultMap = resultMaps.get(resultSetCount);
      // 设值,通过metaObject
      handleResultSet(rsw, resultMap, multipleResults, null);
      rsw = getNextResultSet(stmt);
      cleanUpAfterHandlingResultSet();
      resultSetCount++;
    }

    // 解析resultSet,如果有复杂嵌套map也去解析,一般没有resultset,这里所以不走
    String[] resultSets = mappedStatement.getResultSets();
    if (resultSets != null) {
    
      while (rsw != null && resultSetCount < resultSets.length) {
    
        ResultMapping parentMapping = nextResultMaps.get(resultSets[resultSetCount]);
        if (parentMapping != null) {
    
          String nestedResultMapId = parentMapping.getNestedResultMapId();
          ResultMap resultMap = configuration.getResultMap(nestedResultMapId);
          handleResultSet(rsw, resultMap, null, parentMapping);
        }
        rsw = getNextResultSet(stmt);
        cleanUpAfterHandlingResultSet();
        resultSetCount++;
      }
    }

    return collapseSingleResultList(multipleResults);
  }

赋值是在handleResultSet()方法中完成的,在这个方法内部,最终会去执行handleRowValues()这个方法,handleRowValues()内部,有个嵌套map的逻辑分支,如果有复杂映射则会递归去处理,如果没有则直接调用handleRowValuesForSimpleResultMap()方法。

private void handleResultSet(ResultSetWrapper rsw, ResultMap resultMap, List<Object> multipleResults, ResultMapping parentMapping) throws SQLException {
    
    try {
    
      // 这里为空
      if (parentMapping != null) {
    
        handleRowValues(rsw, resultMap, null, RowBounds.DEFAULT, parentMapping);
      } else {
    
        if (resultHandler == null) {
    
          DefaultResultHandler defaultResultHandler = new DefaultResultHandler(objectFactory);
          handleRowValues(rsw, resultMap, defaultResultHandler, rowBounds, null);
          multipleResults.add(defaultResultHandler.getResultList());
        } else {
    
          handleRowValues(rsw, resultMap, resultHandler, rowBounds, null);
        }
      }
    } finally {
    
      // issue #228 (close resultsets)
      closeResultSet(rsw.getResultSet());
    }
  }

  public void handleRowValues(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException {
    
    if (resultMap.hasNestedResultMaps()) {
    
      ensureNoRowBounds();
      checkResultHandler();
      handleRowValuesForNestedResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
    } else {
    
      handleRowValuesForSimpleResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
    }
  }

handleRowValuesForSimpleResultMap()方法内部,重要的其实就两行逻辑,getRowValue()与storeObject()。

private void handleRowValuesForSimpleResultMap(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException {
    
   	  // 反射赋值就是在getRowValue中做的
      Object rowValue = getRowValue(rsw, discriminatedResultMap, null);
      // 将结果放到resultHandler中存储起来
      storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet);
  }

getRowValue()方法完成反射赋值调用相应对象的set()方法这个过程,虽然代码很乱,但是其实都是一些准备条件,具体的赋值逻辑是在 applyPropertyMappings() 方法中,这个方法中有个metaObject.setValue(property, value);,这个方法就是mybatis反射赋值的一个处理点。

private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap, String columnPrefix) throws SQLException {
    
    final ResultLoaderMap lazyLoader = new ResultLoaderMap();
    Object rowValue = createResultObject(rsw, resultMap, lazyLoader, columnPrefix);
    if (rowValue != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
    
      final MetaObject metaObject = configuration.newMetaObject(rowValue);
      boolean foundValues = this.useConstructorMappings;
      if (shouldApplyAutomaticMappings(resultMap, false)) {
    
        foundValues = applyAutomaticMappings(rsw, resultMap, metaObject, columnPrefix) || foundValues;
      }
      foundValues = applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, columnPrefix) || foundValues;
      foundValues = lazyLoader.size() > 0 || foundValues;
      rowValue = foundValues || configuration.isReturnInstanceForEmptyRow() ? rowValue : null;
    }
    return rowValue;
  }

循环处理完成后,mybatis已经完成了将数据库中的数据查出来放到对应的实体类中这个过程,到这里preparestatment 的执行也就结束了。
mybatis中关于反射有一个非常不错的设计,是在reflect包下,包括赋值、拷贝对象都是由他去完成的,下一章会介绍这个。

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/qq_41063182/article/details/107147723

智能推荐

SpringBoot实现注册操作_springboot不同身份注册-程序员宅基地

文章浏览阅读2.1k次,点赞3次,收藏10次。这段时间开始做后台接口的测试都是在自己电脑上做的一些简单的界面来测试自己写的接口有没有出现什么问题。_springboot不同身份注册

oracle em-application.log 占用空间_oracle中em-application.log-程序员宅基地

文章浏览阅读2.3k次。运行一段时间后,发现磁盘空间剧减,除了数据占用空间外,日志文件也占用空间.如下两个文件占用空间较大home/oracle/rda/outpu/*.loghome/oracle/product/10.2.0/db/oc4j/j2ee/OC4J_DBConsole_gxdb1_gxdb1/log/http-web-access.loghttp-web-access.log_oracle中em-application.log

mini2440 简单按键中断模式驱动程序_make -c $(kern_dir) m=`pwd` modules clean-程序员宅基地

文章浏览阅读853次。MakefileKERN_DIR = /home/grh/kernel_source_code/linux-2.6.32.2all : make -C $(KERN_DIR) M=`pwd` modules arm-linux-gcc key_interrupt_app.c -o key_interrupt_appclean : make -C $(KERN_DIR) M=`pwd_make -c $(kern_dir) m=`pwd` modules clean

subprocess.Popen BrokenPipeError: [Errno 32] Broken pipe-程序员宅基地

文章浏览阅读5.1k次,点赞3次,收藏9次。BrokenPipeError: [Errno 32] Broken pipe在向管道中写入参数的时候,一段时间正常写入数据后,若长时间未写入数据,则会报错BrokenPipeError: [Errno 32] Broken pipe,且该进程会进入睡眠原代码 # 定义管道及ffmpeg命令,输出rtmp流的时候使用command = ['ffmpeg', '-y', '-v', '24', # 日志显示等级 '-f', 'rawv_brokenpipeerror: [errno 32] broken pipe

身价1亿日元的“机器人观音”问世,为普罗大众讲解佛义 ...-程序员宅基地

文章浏览阅读81次。只有人们想不到的,没有机器人做不到的。 日前,日本高台寺展示了智能机器人观音“Minder”,旨在向现代人简单易懂的阐明佛教的教义。 据了解,“Minder”由大阪大学教授石黑浩等人协助研发,研发费用为1亿日元(约600万人民币)。外形方面,“Minder”高约195厘米、重约60公斤,头部、手臂和躯体可以转动,左眼内装有摄像头。 研究..._养猪起家身价1296亿

pandas获得指定行_pandas取dataframe特定行/列_pandas选取特定值所在的行-程序员宅基地

文章浏览阅读1.6w次,点赞13次,收藏96次。转自他人博客:https://blog.csdn.net/weixin_39586825/article/details/1117585061.按列取、按索引/行取、按特定行列取import numpy as npfrom pandas import DataFrameimport pandas as pddf=DataFrame(np.arange(12).reshape((3,4)),index=[‘one’,‘two’,‘thr’],columns=list(‘abcd’))df[‘a’]_pandas选取特定值所在的行

随便推点

php 判断post编码格式,php如何判断get或post的值是否存在-程序员宅基地

文章浏览阅读717次。php如何判断get或post的值是否存在,这个问题困扰了我很长时间,是用isset还是empty还是is_array啥的,请大神给一个指点回复内容:php如何判断get或post的值是否存在,这个问题困扰了我很长时间,是用isset还是empty还是is_array啥的,请大神给一个指点isset()是判断这个变量是否定义,empty()是在这个变量已经定义的情况下(如果变量没定义,将报错var..._isset empty $_get

计算机系统课程 笔记总结 CSAPP第七章 链接(7.1-7.13)_csapp 7.6.1-程序员宅基地

文章浏览阅读2.6k次,点赞16次,收藏45次。GitHub计算机系统CSAPP课程资源 计算机系统课程 笔记总结 CSAPP第二章 信息的表示和处理(2.1-2.2) 计算机系统课程 笔记总结 CSAPP第二章 信息的表示和处理(2.3-2.4) 计算机系统课程 笔记总结 CSAPP第三章 程序的机器级表示(3.2-3.4) 计算机系统课程 笔记总结 CSAPP第三章 程序的机器级表示(3.5-3.7) 计算机系统课程 笔记总结 ..._csapp 7.6.1

Redis总结-程序员宅基地

文章浏览阅读248次。目录概述Redis是什么?简述它的优缺点?为什么要使用Redis/缓存Redis为什么这么快?Redis相比Memcached有哪些优势?Redis的常用场景有哪些?数据类型Redis的数据类型有哪些?线程模型Redis为何选择单线程?Redis真的是单线程?Redisv6.0为何引入多线程?过期键的删除策略Redis过期键的删除策略?过期键的删除策略都有哪些?内存相关MySQL里有2000w数据,redis中只能存20w的数据,如何保证redis中的数据都是热点数据?Redis内存淘汰机制?Redis如何

nginx平滑重启和升级-程序员宅基地

文章浏览阅读77次。平滑重启 kill -HUP `cat /usr/local/www/nginx/logs/nginx.pid`平滑升级nginxcd /yujialinwget http://nginx.org/download/nginx-1.0.6.tar.gztar zxvf nginx-1.0.6.tar.gzcd nginx-1.0.6/usr/local/www/nginx/sbin/nginx -..._php 平滑升级

什么是ip6-程序员宅基地

文章浏览阅读3.9k次。相比于IPv4来说,IPv6的地址更加充足,安全性也更高,可以解决如今IP地址枯竭的问题,为物联网时代的发展提供网络基础;Pv6具有更大的地址空间,使用更小的路由表,加入了对自动配置的支持。ipv6是什么Pv6是第六代互联网协议,被设计用于替代使用了30多年的第四代互联网协议的下一代IP协议。由于IPv4最大的问题在于网络地址资源有限,严重制约了互联网的应用和发展,所以出现了替代版本的IP协议。..._ip6

android bluetooth stack-enable_android bluetooth stack-enable-程序员宅基地

文章浏览阅读2.5k次。android bluetooth stack_android bluetooth stack-enable