Mybatis 源码剖析

传统方式源码剖析

源码剖析-初始化,点进build方法可以看下

InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
//这一行代码正是初始化工作的开始
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(inputStream);

进入源码分析:

// 1.最初调用的build
   public SqlSessionFactory build(InputStream inputStream) {
// 调用了重载方法
        return this.build((InputStream)inputStream, (String)null, (Properties)null);
    }
//2.调用的重载方法
    public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
        SqlSessionFactory var5;
        try {
//XMLConfigBuilder是专门解析mybatis的配置文件的类
            XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
//这里又调用了一个重载方法,parser.parse()返回的是configuration对象
            var5 = this.build(parser.parse());
        } catch (Exception var14) {
            throw ExceptionFactory.wrapException("Error building SqlSession.", var14);
        } finally {
            ErrorContext.instance().reset();

            try {
                inputStream.close();
            } catch (IOException var13) {
            }

        }

        return var5;
    }

Mybatis在初始化的时候,会将Mybatis的配置信息全部加载到内存中,使用org.apache.ibatis.session.Configuration实例来维护。
下面进入对配置文件解析部分:
首先对Configuration对象进行介绍:
Configuration对象的结构和xml配置文件的对象基本相同。
回顾一下xml在配置有哪些:
properties(属性),settings(设置),typeAliass(类型别名),typeHandless(类型处理器),objectFactory(对象工厂),mapper(映射器)等。Configuration也有相应的对象属性来封装它们。
也就是说,初始化配置文件信息的本质就是创建Configuration对象,将解析的xml数据封装到Configuration内部属性中。

观察一下上面源码中的parser.parse() (XMLConfigBuilder#parse

//解析xml成Configuration对象
    public Configuration parse() {
//若已解析,抛出BuilderException异常
        if (this.parsed) {
            throw new BuilderException("Each XMLConfigBuilder can only be used once.");
        } else {
//标识已解析
            this.parsed = true;
//解析 xml configuration 节点            this.parseConfiguration(this.parser.evalNode("/configuration"));
            return this.configuration;
        }
    }

XMLConfigBuilder#parseConfiguration

private void parseConfiguration(XNode root) {
        try {
/*
*下边的代码基本都是都是解析标签
*/ 
            this.propertiesElement(root.evalNode("properties"));
            Properties settings = this.settingsAsProperties(root.evalNode("settings"));
            this.loadCustomVfs(settings);
            this.typeAliasesElement(root.evalNode("typeAliases"));
            this.pluginElement(root.evalNode("plugins"));
            this.objectFactoryElement(root.evalNode("objectFactory"));
            this.objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
            this.reflectorFactoryElement(root.evalNode("reflectorFactory"));
            this.settingsElement(settings);
            this.environmentsElement(root.evalNode("environments"));
            this.databaseIdProviderElement(root.evalNode("databaseIdProvider"));
            this.typeHandlerElement(root.evalNode("typeHandlers"));
            this.mapperElement(root.evalNode("mappers"));
        } catch (Exception var3) {
            throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + var3, var3);
        }
    }

介绍一下MappedStatement:
作用:MappedStatement和Mapper配置文件中的一个select/update/insert/delete节点相对应。mapper中配置的标签都被封装到了此对象中,主要用途是描述一条SQL语句。
初始化过程:回顾刚开始介绍的加载配置文件的过程中,会对mybatis-config.xml中的各个标签进行解析,其中mappers标签都用来引入mapper.xml文件或者配置mapper接口的目录。

<select id ="getUser" resultType="user">
 select * from user where id=#{id}
</select>

这样一个select标签会在初始化配置文件时被封装成一个MappedStatement对象,然后存储在Configuration对象的mappedStatements中

protected final Map<String, MappedStatement> mappedStatements;
...
this.mappedStatements = new Configuration.StrictMap("Mapped Statements collection");

mappedStatements 是一个HashMap,存储时key=全限定类名+方法名,value=对应的MappedStatement对象。

  • 在XMLConfigBuilder中处理:(XMLConfigBuilder#parseConfiguration
            this.mapperElement(root.evalNode("mappers"));

到此对xml配置文件的解析就结束了,回到步骤2调用的重载build方法(SqlSessionFactoryBuilder#build

    public SqlSessionFactory build(Configuration config) {
        return new DefaultSqlSessionFactory(config);
    }

源码剖析-执行SQL流程

先简单介绍一下SqlSession

SqlSession是一个接口,它有两个实现类: DefaultSqlSession(默认)和SqlSessionManager(弃用不做介绍)
SqlSessions是MyBatis用于和数据库交互的顶层类,通常它与ThreadLocal绑定,一个会话使用一个SqlSession,并且在使用完毕后需要close

Executor

Executor是一个接口,他有三个常用的实现类
BatchExecutor(重用语句执行并执行批量更新)
ReuseExecutor(重用预处理语句prepared statements)
SimpleExecutor(普通的执行器)

继续分析,初始化完毕后,我们就要执行SQL了

SqlSession sqlSession = factory.openSession();
List<User> list = 
sqlSession.selectList("com.lagou.mapper.UserMapper.getUserByName");

SqlSessionFactory#openSession

  public SqlSession openSession() {
//getDefaultExecutorType 传递的是SimpleExecutor
        return this.openSessionFromDataSource(this.configuration.getDefaultExecutorType(), (TransactionIsolationLevel)null, false);
    }

...
// 进入openSessionFromDataSource
// ExecutorType 为Executor的类型,TransactionIsolationLevel 为事务隔离级别,autoCommit为是否开启事务
//openSessionFromDataSource的重载方法可以指定获得的SqlSession类型和事务处理
  private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
    Transaction tx = null;
    try {
      final Environment environment = configuration.getEnvironment();
      final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
      tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
//根据参数创建指定类型的Executor
      final Executor executor = configuration.newExecutor(tx, execType);
//返回的是DefaultSqlSession
      return new DefaultSqlSession(configuration, executor, autoCommit);
    } catch (Exception e) {
      closeTransaction(tx); // may have fetched a connection so lets call close()
      throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

执行sqlsession的api - selectList

  @Override
  public <E> List<E> selectList(String statement) {
    return this.selectList(statement, null);
  }

  @Override
  public <E> List<E> selectList(String statement, Object parameter) {
    return this.selectList(statement, parameter, RowBounds.DEFAULT);
  }

  @Override
  public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
    try {
//根据传入的全限定名+方法从映射的Map中取出MappedStatenent对象
      MappedStatement ms = configuration.getMappedStatement(statement);
//调用Executor中的方法处理
//RowBounds是用来逻辑分页的
//wrapCollection用来装饰集合或者数组参数
      return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

源码剖析-executor
继续源码中的步骤,进入BaseExecutor#query()

@Override
  public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
//根据传入的参数动态获取sql,最后返回用BoundSql对象表示
    BoundSql boundSql = ms.getBoundSql(parameter);
//为本次查询创建查询缓存
    CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
    return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
 }

//进入query的重载方法
  @SuppressWarnings("unchecked")
  @Override
  public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
    if (closed) {
      throw new ExecutorException("Executor was closed.");
    }
    if (queryStack == 0 && ms.isFlushCacheRequired()) {
      clearLocalCache();
    }
    List<E> list;
    try {
      queryStack++;
      list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
      if (list != null) {
        handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
      } else {
//如果缓存中没有本次查询的值,那么从数据库中查询
        list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
      }
    } finally {
      queryStack--;
    }
    if (queryStack == 0) {
      for (DeferredLoad deferredLoad : deferredLoads) {
        deferredLoad.load();
      }
      // issue #601
      deferredLoads.clear();
      if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
        // issue #482
        clearLocalCache();
      }
    }
    return list;
  }

去数据库查询

  private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    List<E> list;
    localCache.putObject(key, EXECUTION_PLACEHOLDER);
    try {
//查询的方法
      list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
    } finally {
      localCache.removeObject(key);
    }
//查询结果放入缓存中
    localCache.putObject(key, list);
    if (ms.getStatementType() == StatementType.CALLABLE) {
      localOutputParameterCache.putObject(key, parameter);
    }
    return list;
  }

doQuery方法(SimpleExecutor#doQuery)

  @Override
  public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
    Statement stmt = null;
    try {
      Configuration configuration = ms.getConfiguration();
//传入参数创建StatementHandler对象来执行查询
      StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
//创建jdbc的statement对象
      stmt = prepareStatement(handler, ms.getStatementLog());
//statement进行处理
      return handler.<E>query(stmt, resultHandler);
    } finally {
      closeStatement(stmt);
    }
  }

创建Statement的方法(SimpleExecutor#prepareStatement

  private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
    Statement stmt;
//这条代码中的getConnection方法经过重重调用最后会调用openConnection方法,从连接池中获取到连接
    Connection connection = getConnection(statementLog);
    stmt = handler.prepare(connection, transaction.getTimeout());
    handler.parameterize(stmt);
    return stmt;
  }

JdbcTransaction#openConnection

  protected void openConnection() throws SQLException {
    if (log.isDebugEnabled()) {
      log.debug("Opening JDBC Connection");
    }
//从连接池获得连接
    connection = dataSource.getConnection();
    if (level != null) {
      connection.setTransactionIsolation(level.getLevel());
    }
    setDesiredAutoCommit(autoCommmit);
  }

上述的Executor.query()方法几经转折,最后会创建一个StatementHandler对象,然后将必要的参数传递给StatementHandler,使用StatementHandler来完成对数据库的查询,最终返回List结果集。
从上述的代码我们可以看除,Executor的功能和作用:

    1. 根据传递的参数,完成对SQL语句的动态解析,生成BoundSql对象,供StatementHandler使用
    1. 为查询创建缓存,以提高性能
    1. 创建JDBC的statement连接对象,传递给 StatementHandler对象,返回List查询结果

源码剖析-StatementHandler
StatementHandler对象主要完成两个工作:

  • 对JDBC的preparedStatement类型的对象,创建的过程中,我们使用的SQL语句字符串会包含若干个“?”占位符,我们在其后再对占位符进行设值。StatementHandler通过parametize(statement)方法对Statenebr进行设值。
  • StatementHandler通过List query(Statement statement, ResultHandler resultHandler) 方法来完成执行statement,和将statement对象返回的resultSet封装成List;

进入到StatementHandler的parameterize(statement)方法的实现(PreparedStatementHandler#parameterize

  @Override
  public void parameterize(Statement statement) throws SQLException {
    parameterHandler.setParameters((PreparedStatement) statement);
  }
//对某一个Statement设置参数
  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);
          }
//每一个mapping都有一个TypeHandler,根据TypeHandler来对preparedStatement进行设置参数
          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 e) {
            throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
          } catch (SQLException e) {
            throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
          }
        }
      }
    }
  }

从上述代码可以看到,StatementHandler的parameterize(Statement)方法调用了ParameterHandler.setParameters方法
ParameterHandler的setParameters方法负责根据我们输入的参数,对statement对象的“?”占位符处进行赋值。
进入到StatementHandler的<E> List<E> query(Statement statement, ResultHandler resultHandler)方法的实现
PreparedStatementHandler#query

  public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
//调用PreparedStatement.execute方法,然后将resultSet交给resultSetHandler处理
    PreparedStatement ps = (PreparedStatement) statement;
    ps.execute();
//使用resultSetHandler来处理resultSet
    return resultSetHandler.<E> handleResultSets(ps);
  }

resultSetHandler的handleResultSets方法会将Statement语句执行后生成的resultSet结果集转换成List结果集,源码
DefaultResultSetHandler#handleResultSets

public List<Object> handleResultSets(Statement stmt) throws SQLException {
    ErrorContext.instance().activity("handling results").object(mappedStatement.getId());
 //多resultSet结果集合,每一个ResultSet对应一个Object对象,而实际上,每一个Object是List<Object>对象
//在不考虑存储过程的多resultSet的情况,普通的查询,实际就是一个resultSet,也就是说multipleResults 最多就一个元素
    final List<Object> multipleResults = new ArrayList<Object>();

    int resultSetCount = 0;
//获得首个resultSet对象,并封装成ResultSetWrapper 对象
    ResultSetWrapper rsw = getFirstResultSet(stmt);
//获取resultMaps数组
    List<ResultMap> resultMaps = mappedStatement.getResultMaps();
    int resultMapCount = resultMaps.size();
//校验
    validateResultMapsCount(rsw, resultMapCount);
    while (rsw != null && resultMapCount > resultSetCount) {
//获得ResultMap对象
      ResultMap resultMap = resultMaps.get(resultSetCount);
//处理ResultSet,将结果添加multipleResults中
      handleResultSet(rsw, resultMap, multipleResults, null);
//获得下一个ResultSet对象,并封装成ResultSetWrapper对象
      rsw = getNextResultSet(stmt);
//清理
      cleanUpAfterHandlingResultSet();
//resultSetCount++
      resultSetCount++;
    }
//mappedStatement的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);
  }
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 200,527评论 5 470
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,314评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 147,535评论 0 332
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,006评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,961评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,220评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,664评论 3 392
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,351评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,481评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,397评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,443评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,123评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,713评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,801评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,010评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,494评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,075评论 2 341

推荐阅读更多精彩内容