MyBatis原理:SQL执行流程

          mybatis作为一款持久层框架,最为主要的职责,当然是执行我们在mapper映射文件中写的sql语句,但是sql语句也分两种:

(1)普通sql语句

(2)动态sql语句,包含了<if | foreach | choose | when | otherwise | where | set | trim>等可以动态拼接sql的标签

         本篇博客将以普通的sql语句执行,根据debug源码的方式,跟踪sql是如何解析,以及最后是如何执行的。测试代码,将以之前博客  MyBatis入门之一对多、多对多查询  的代码为例。首先看看mapper映射文件及测试代码:

UserMapper.xml:

<mapper namespace="com.qxf.mapper.UserMapper">

    <!-- 自定义结果映射:
     id:是主键
     property:是Java对象的属性名
     column:是数据库中列名,或者查询中取的列的别名
    -->
    <resultMap id="userMap" type="User">
        <id property="id" column="id"></id>
        <result property="username" column="username"></result>
        <result property="password" column="password"></result>
        <result property="isValid" column="is_valid"></result>

        <!-- 一对多关联查询,使用collection,其中:
           collection中的property属性blogs:是User类中的属性名称,对应就可以了(private List<Blog> blogs);
           javaType:是User类中的blogs这个属性,属于哪个Java类型,这里当然是属于java.util.List
           ofType: 是List泛型中的类型,这里是Blog类型
           id中的column是t_blog表的id,只是在为了避免重名冲突,查询的时候取了别名blog_id
        -->
        <collection property="blogs" javaType="java.util.List" ofType="Blog">
            <id property="id" column="blog_id"></id>
            <result property="title" column="title"></result>
            <result property="userId" column="user_id"></result>
        </collection>
    </resultMap>

    <!--id 与接口的方法名相同,注意:
       这里的返回值类型不再是resultType了,而是resultMap
       而且resultMap的值,就是上面<resultMap>标签的id属性值
    -->
    <select id="getUserAndBlogByUserId" parameterType="string" resultMap="userMap">
        select u.id,u.username,u.password,u.is_valid,b.id as blog_id,b.title,b.user_id
         from t_user u,t_blog b
        where u.id = b.user_id
         and u.id=#{id}
    </select>

</mapper>

测试代码: 

public class One2ManyQuery {
    public static void main(String[] args) throws IOException {
        //读取配置信息
        InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
        //根据配置信息,创建SqlSession工厂
        SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(inputStream);
        //SqlSession工厂创建SqlSession
        SqlSession sqlSession = factory.openSession();
        //获取接口的代理对象
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        User user = mapper.getUserAndBlogByUserId("123");
        System.out.println(user);
    }
}

        这里,将以下面这句代码为起点,我的想法是将mybatis的原理进行划分成几个模块分析,首先从结果分析,然后再倒推。所以现在,关于mybatis如何读取并解析配置文件的、如何创建SqlSession的、如何创建接口的代理对象的,后面有时间再写,敬请关注。

User user = mapper.getUserAndBlogByUserId("123");

step into跟进去,发现会调用MapperProxy的invoke()方法,证实了上面的mapper是接口的代理对象:

再跟进去:发现execute()方法,会先判断sql的类型:INSERT、UPDATE、DELETE、SELECT、FLUSH,我们这里是select查询,所以我们只需要关注select分支语句:

      //execute()方法的2个全局变量
      //result:是方法的返回值,Object类型
      //param: 是方法参数,Object类型
      case SELECT:
            if (this.method.returnsVoid() && this.method.hasResultHandler()) {
                this.executeWithResultHandler(sqlSession, args);
                result = null;
            } else if (this.method.returnsMany()) {
                result = this.executeForMany(sqlSession, args);
            } else if (this.method.returnsMap()) {
                result = this.executeForMap(sqlSession, args);
            } else if (this.method.returnsCursor()) {
                result = this.executeForCursor(sqlSession, args);
            } else {
                //最终会走到这里,先进行参数解析,其中args是我们传入的参数,Object[]类型
                param = this.method.convertArgsToSqlCommandParam(args);
                //然后执行sqlSession.selectOne获得sql语句执行结果
                result = sqlSession.selectOne(this.command.getName(), param);
                if (this.method.returnsOptional() && (result == null || !this.method.getReturnType().equals(result.getClass()))) {
                    result = Optional.ofNullable(result);
                }
            }
            break;

所以这里又分为2步:

(1)解析sql参数为Object类型

(2)执行sqlSession.selectOne获得sql语句执行结果 

一、sql参数是如何解析的?

      重点关注这句代码:

param = this.method.convertArgsToSqlCommandParam(args);

一路跟进去,发现最终执行的是ParamNameResolver的getNamedParams()方法

    public Object getNamedParams(Object[] args) {
        int paramCount = this.names.size();
        if (args != null && paramCount != 0) {
            //没有参数注解,并且只有一个参数,则直接返回第一个参数
            if (!this.hasParamAnnotation && paramCount == 1) {
                return args[(Integer)this.names.firstKey()];
            } else {
                //否则,返回一个ParamMap对象
                Map<String, Object> param = new ParamMap();
                int i = 0;

                for(Iterator var5 = this.names.entrySet().iterator(); var5.hasNext(); ++i) {
                   
                    //下面的意思是,将传进来的参数封装成Map对象
                    //其中key为:arg0、arg1....以及param1、param2...
                    //而value就是我们依次传入的参数值 
                    Entry<Integer, String> entry = (Entry)var5.next();
                    param.put((String)entry.getValue(), args[(Integer)entry.getKey()]);
                    String genericParamName = "param" + String.valueOf(i + 1);
                    if (!this.names.containsValue(genericParamName)) {
                        param.put(genericParamName, args[(Integer)entry.getKey()]);
                    }
                }

                return param;
            }
        } else {
            return null;
        }
    }

那么,ParamMap又是什么鬼?其源码如下:

    public static class ParamMap<V> extends HashMap<String, V> {
        private static final long serialVersionUID = -2212268410512043556L;

        public ParamMap() {
        }

        public V get(Object key) {
            if (!super.containsKey(key)) {
                throw new BindingException("Parameter '" + key + "' not found. Available parameters are " + this.keySet());
            } else {
                return super.get(key);
            }
        }
    }

根据源码,ParamMap继承了HashMap,只是重写了get()方法,所以完全可以将它看做是一个HashMap。

根据上面分析:

(1)如果只有一个参数的话,则直接返回一个Object参数

(2)如果有多个参数的话,则会返回一个Map对象,其中key为:arg0、arg1....以及param1、param2...

        而value就是我们依次传入的参数值 

 

二、sqlSession.selectOne()方法是如何执行的?

result = sqlSession.selectOne(this.command.getName(), param);

其中,第一个参数是namespace+id组成的字符串,这里是"com.qxf.mapper.UserMapper.getUserAndBlogByUserId"

第二个参数,是我们传入的字符串参数,"123"

先看看selectOne的样子:

发现,它最终还是调用了DefaultSqlSession的selectList()方法,然后根据返回结果判断:

(1)如果返回的list大小只有一个,则符合预期,直接返回list的第一个元素

(2)如果list的大小,大于1个,那么就会抛出异常

(3)返回null值

 

所以,现在的目标,自然是转向了DefaultSqlSession的selectList()方法,最终执行重载的方法如下:

    public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
        List var5;
        try {
            //根据statement=namespace+id=com.qxf.mapper.UserMapper.getUserAndBlogByUserId
            //在配置信息中查找对应的MappedStatement
            //每个MappedStatement,其实就是我们的<select|update|insert|delete>等标签及其内容
            MappedStatement ms = this.configuration.getMappedStatement(statement);
            //通过执行器executor的query()方法执行
            var5 = this.executor.query(ms, this.wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
        } catch (Exception var9) {
            throw ExceptionFactory.wrapException("Error querying database.  Cause: " + var9, var9);
        } finally {
            ErrorContext.instance().reset();
        }

        return var5;
    }

其中,RowBounds是一个内存分页对象,包含offset和limit两个属性。

目光转移到,executor的query()方法:

    public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
       //通过传入的参数,构造BoundSql对象,就是给它相应的属性赋值,这里不细究
        BoundSql boundSql = ms.getBoundSql(parameterObject);
       //创建缓存的key值
        CacheKey key = this.createCacheKey(ms, parameterObject, rowBounds, boundSql);
       //执行重载方法
        return this.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
    }

这里边有很多重载的方法,就不一一列举了,因为是第一次查询,不存在缓存,所以后面会执行到BaseExecutor的queryFromDatabase()方法,顾名思义,就是从数据库查询

继而转向doQuery()方法:

    public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
        Statement stmt = null;

        List var9;
        try {
            Configuration configuration = ms.getConfiguration();
            //构建StatementHandler对象
            StatementHandler handler = configuration.newStatementHandler(this.wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
            //创建Statement
            stmt = this.prepareStatement(handler, ms.getStatementLog());
           //执行查询
            var9 = handler.query(stmt, resultHandler);
        } finally {
            this.closeStatement(stmt);
        }

        return var9;
    }

这里有几个关键的地方:

(1)StatementHandler 是什么?

(2)参数是如何设置的?

(3)sql是怎样执行的?

(4)结果集是怎样处理的?

(一)StatementHandler 是什么?

    对这行代码进行跟踪:

//构建StatementHandler对象
StatementHandler handler = configuration.newStatementHandler(this.wrapper, ms, parameter, rowBounds, resultHandler, boundSql);

先看下继承关系(图片来源于https://www.cnblogs.com/cxuanBlog/p/11295488.html):


 

StatementHandler的默认实现类是RoutingStatementHandler,而RoutingStatementHandler持有一个StatementHandler的对象,也就是变量delegate,而变量delegate则会根据statementType来创建,这里会创建PreparedStatementHandler:

  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主要负责操作 Statement 对象与数据库进行交互。

而创建PreparedStatementHandler对象,会调用父类的构造方法:

  protected BaseStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
    this.configuration = mappedStatement.getConfiguration();
    this.executor = executor;
    this.mappedStatement = mappedStatement;
    this.rowBounds = rowBounds;

    this.typeHandlerRegistry = configuration.getTypeHandlerRegistry();
    this.objectFactory = configuration.getObjectFactory();

    if (boundSql == null) { // issue #435, get the key before calculating the statement
      generateKeys(parameterObject);
      boundSql = mappedStatement.getBoundSql(parameterObject);
    }

    this.boundSql = boundSql;
    //创建参数处理器对象ParameterHandler
    this.parameterHandler = configuration.newParameterHandler(mappedStatement, parameterObject, boundSql);
    //创建结果处理器对象ResultSetHandler
    this.resultSetHandler = configuration.newResultSetHandler(executor, mappedStatement, rowBounds, parameterHandler, resultHandler, boundSql);
  }

重点关注参数处理器和结果处理器,后面都会用到:

    //创建参数处理器对象ParameterHandler
    this.parameterHandler = configuration.newParameterHandler(mappedStatement, parameterObject, boundSql);
    //创建结果处理器对象ResultSetHandler
    this.resultSetHandler = configuration.newResultSetHandler(executor, mappedStatement, rowBounds, parameterHandler, resultHandler, boundSql);

 

(二)参数是如何设置的?

回到doQuery方法:

    public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
        Statement stmt = null;

        List var9;
        try {
            Configuration configuration = ms.getConfiguration();
            //构建StatementHandler对象
            StatementHandler handler = configuration.newStatementHandler(this.wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
            //创建Statement
            stmt = this.prepareStatement(handler, ms.getStatementLog());
           //执行查询
            var9 = handler.query(stmt, resultHandler);
        } finally {
            this.closeStatement(stmt);
        }

        return var9;
    }

我们重点关注这句:

//创建Statement
stmt = this.prepareStatement(handler, ms.getStatementLog());

跟进去,会发现调用一个方法 handler.parameterize(stmt);

  private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
    Statement stmt;
   //获取连接
    Connection connection = getConnection(statementLog);
    //创建一个Statement对象,这里创建的是PreparedStatement对象
    stmt = handler.prepare(connection, transaction.getTimeout());
    //处理参数
    handler.parameterize(stmt);
    return stmt;
  }

那么,我们现在看看, handler.parameterize(stmt)方法的实现,点进去发现有很多重载的方法,后面会调用在

(一)StatementHandler 是什么? 中,指到的参数处理器的setParameters()方法:

  @Override
  public void parameterize(Statement statement) throws SQLException {
    parameterHandler.setParameters((PreparedStatement) statement);
  }



    public void setParameters(PreparedStatement ps) {
        ErrorContext.instance().activity("setting parameters").object(this.mappedStatement.getParameterMap().getId());
        List<ParameterMapping> parameterMappings = this.boundSql.getParameterMappings();
        if (parameterMappings != null) {
            for(int i = 0; i < parameterMappings.size(); ++i) {
                ParameterMapping parameterMapping = (ParameterMapping)parameterMappings.get(i);
                if (parameterMapping.getMode() != ParameterMode.OUT) {
                    //属性名
                    String propertyName = parameterMapping.getProperty();
                    Object value;
                    if (this.boundSql.hasAdditionalParameter(propertyName)) {
                        value = this.boundSql.getAdditionalParameter(propertyName);
                    } else if (this.parameterObject == null) {
                        value = null;
                    } else if (this.typeHandlerRegistry.hasTypeHandler(this.parameterObject.getClass())) {
                        value = this.parameterObject;
                    } else {
                        MetaObject metaObject = this.configuration.newMetaObject(this.parameterObject);
                        value = metaObject.getValue(propertyName);
                    }

                    TypeHandler typeHandler = parameterMapping.getTypeHandler();
                    JdbcType jdbcType = parameterMapping.getJdbcType();
                    if (value == null && jdbcType == null) {
                        jdbcType = this.configuration.getJdbcTypeForNull();
                    }

                    try {
                        //设置参数,value是属性值
                        typeHandler.setParameter(ps, i + 1, value, jdbcType);
                    } catch (SQLException | TypeException var10) {
                        throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + var10, var10);
                    }
                }
            }
        }

    }

重点关注:

//设置参数,value是属性值
typeHandler.setParameter(ps, i + 1, value, jdbcType);

最终会发现,它调用的JDBC中PreparedStatement的对应的setXXX(i, parameter);

至此,我们传递的参数就设置好了。

 

(三)sql是怎样执行的?

还是回到doQuery方法:

    public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
        Statement stmt = null;

        List var9;
        try {
            Configuration configuration = ms.getConfiguration();
            //构建StatementHandler对象
            StatementHandler handler = configuration.newStatementHandler(this.wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
            //创建Statement
            stmt = this.prepareStatement(handler, ms.getStatementLog());
           //执行查询
            var9 = handler.query(stmt, resultHandler);
        } finally {
            this.closeStatement(stmt);
        }

        return var9;
    }

重点看这句:

//执行查询
var9 = handler.query(stmt, resultHandler);

可以看到,调用的是StatementHandler的query方法,更为具体的来说,是调用PreparedStatementHandler的query方法:

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

层层跟进之后,发现,还是调用JDBC中PreparedStatement的execute()方法执行sql语句的。

至此,sql语句也已经执行完了。

 

(四)结果集是怎样处理的?

紧接着上面这行代码:

  @Override
  public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
    PreparedStatement ps = (PreparedStatement) statement;
    //执行预编译的sql语句
    ps.execute();
    //处理结果集
    return resultSetHandler.handleResultSets(ps);
  }

所以,我们要看的是:

//处理结果集
return resultSetHandler.handleResultSets(ps);

看看它的具体实现:

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;
    //获取第一个结果集(ps.getResultSet()即可获取),并将结果集进行包装
    ResultSetWrapper rsw = getFirstResultSet(stmt);
    //获取我们配置的ResultMap
    List<ResultMap> resultMaps = mappedStatement.getResultMaps();
    int resultMapCount = resultMaps.size();
    validateResultMapsCount(rsw, resultMapCount);
    while (rsw != null && resultMapCount > resultSetCount) {
      ResultMap resultMap = resultMaps.get(resultSetCount);
      //处理结果集
      handleResultSet(rsw, resultMap, multipleResults, null);
      rsw = getNextResultSet(stmt);
      cleanUpAfterHandlingResultSet();
      resultSetCount++;
    }

    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(rsw, resultMap, multipleResults, null);

看看它的具体实现:

  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());
    }
  }

然后又会调用    handleRowValues(rsw, resultMap, defaultResultHandler, rowBounds, null);

  public void handleRowValues(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException {
    //这里会判断,有没有嵌套的resultMap
    if (resultMap.hasNestedResultMaps()) {
      ensureNoRowBounds();
      checkResultHandler();
      //代码会执行到这里
      handleRowValuesForNestedResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
    } else {
      handleRowValuesForSimpleResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
    }
  }

执行到        handleRowValuesForNestedResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);

该方法内部会调用:

rowValue = getRowValue(rsw, discriminatedResultMap, rowKey, null, partialObject);

继而调用applyPropertyMappings()方法,对属性进行赋值:

private boolean applyPropertyMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, ResultLoaderMap lazyLoader, String columnPrefix)
      throws SQLException {
    final List<String> mappedColumnNames = rsw.getMappedColumnNames(resultMap, columnPrefix);
    boolean foundValues = false;
    final List<ResultMapping> propertyMappings = resultMap.getPropertyResultMappings();
    for (ResultMapping propertyMapping : propertyMappings) {
      //获取列名
      String column = prependPrefix(propertyMapping.getColumn(), columnPrefix);
      if (propertyMapping.getNestedResultMapId() != null) {
        // the user added a column attribute to a nested result map, ignore it
        column = null;
      }
      if (propertyMapping.isCompositeResult()
          || (column != null && mappedColumnNames.contains(column.toUpperCase(Locale.ENGLISH)))
          || propertyMapping.getResultSet() != null) {
        //根据列名获取该列的值,resultSet.getXXX(列名) 获取
        Object value = getPropertyMappingValue(rsw.getResultSet(), metaObject, propertyMapping, lazyLoader, columnPrefix);
        // issue #541 make property optional
        final String property = propertyMapping.getProperty();
        if (property == null) {
          continue;
        } else if (value == DEFERRED) {
          foundValues = true;
          continue;
        }
        if (value != null) {
          foundValues = true;
        }
        if (value != null || (configuration.isCallSettersOnNulls() && !metaObject.getSetterType(property).isPrimitive())) {
          // gcode issue #377, call setter on nulls (value is not 'found')
          //给属性赋值,property是实体类的属性名,value则是查询的值
          metaObject.setValue(property, value);
        }
      }
    }
    return foundValues;
  }

具体表现为:

(1)获取列值:

//根据列名获取该列的值,resultSet.getXXX(列名) 获取
Object value = getPropertyMappingValue(rsw.getResultSet(), metaObject, propertyMapping, lazyLoader, columnPrefix);

(2)给属性赋值:

//给属性赋值,property是实体类的属性名,value则是查询的值
metaObject.setValue(property, value);

基本上是这样了。

第一次写原理类的东西,感觉一下子追源码追得太深,并不好。

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值