MyBatis运行流程源码解析

本文详细解析了MyBatis的执行流程,包括配置文件解析、SqlSessionFactory构建、SqlSession生成、SQL执行过程及结果缓存机制等核心环节。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1、读取MyBatis配置文件,构造SqlSessionFactory (实际默认实现是DefaultSqlSessionFactory )

    String configLocation= "mybatis-config.xml";
    InputStream inputStream = Resources.getResourceAsStream(configLocation);
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

2、在SqlSessionFactoryBuilder中构造SqlSessionFactory,最终返回的是SqlSessionFactory的实现类DefaultSqlSessionFactory

     public SqlSessionFactory build(InputStream inputStream) {
        return build(inputStream, null, null);
      }

下面方法有几步:
1、先用MyBatis配置文件对应的输入流(inputStream)构造一个XMLConfigBuilder的实例(parser )
2、用parser来进一步解析MyBatis配置文件来给对应的Java类Configuration实例填充属性
3、用Configuration实例作为参数构造一个DefaultSqlSessionFactory实例作为整个方法的返回

     public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
        try {
           // 用MyBatis配置文件来构造XMLConfigBuilder 
          XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
          // 1、parser.parse()==》解析MyBatis配置文件,转化为对应的Java类Configuration的实例
          // 2、 build(parser.parse())==》实际执行的是new DefaultSqlSessionFactory(config),构造并返回一个DefaultSqlSessionFactory的实例
          return build(parser.parse());
        } catch (Exception e) {
          throw ExceptionFactory.wrapException("Error building SqlSession.", e);
        } finally {
          ErrorContext.instance().reset();
          try {
            inputStream.close();
          } catch (IOException e) {
            // Intentionally ignore. Prefer previous error.
          }
        }
      }

3、最后在DefaultSqlSessionFactory中调用openSession()中生成SqlSession,最终调用到openSessionFromDataSource,实际返回的是DefaultSqlSession。

      @Override
      public SqlSession openSession() {
      	// 方法调用
        return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
      }
      
      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);
          final Executor executor = configuration.newExecutor(tx, execType);
          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();
        }
      }

4、执行SQL

    SqlSession session = sqlSessionFactory.openSession();
    Demo demo = (Demo) session.selectOne("com.insomsia.dao.mapper.DemoMapper.selectDemo");
    session.close();

selectOne方法实现如下:

      @Override
      public <T> T selectOne(String statement) {
        return this.<T>selectOne(statement, null);
      }
      
      @Override
      public <T> T selectOne(String statement, Object parameter) {
        // Popular vote was to return null on 0 results and throw exception on too many.
        List<T> list = this.<T>selectList(statement, parameter);
        if (list.size() == 1) {
          return list.get(0);
        } else if (list.size() > 1) {
          throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size());
        } else {
          return null;
        }
      }

实际调用的是selectList:

      @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 {
          // 1、根据statement获取到要执行的SQL,statement就是你查询时传入的SQLID
          MappedStatement ms = configuration.getMappedStatement(statement);
          // 2、用执行器去执行相应SQL并返回结果
          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();
        }
      }

看下MappedStatement ms = configuration.getMappedStatement(statement)的实现逻辑:

    protected final Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>("Mapped Statements collection");
    
      public MappedStatement getMappedStatement(String id) {
        return this.getMappedStatement(id, true);
      }
      
      public MappedStatement getMappedStatement(String id, boolean validateIncompleteStatements) {
        if (validateIncompleteStatements) {
          buildAllStatements();
        }
        return mappedStatements.get(id);
      }

可以看到主要实现逻辑在public MappedStatement getMappedStatement(String id, boolean validateIncompleteStatements)方法中。而重点又在mappedStatements.get(id)这一句。

mappedStatements是configuration的一个属性,类型为StrictMap,key为String的SQLID,继承自HashMap,value为要执行的SQL体对应的Java类实例。

程序在解析mapper文件时会先将各个SQL节点的内容以key-value的方式缓存到mappedStatements中:

    public V put(String key, V value) {
      // 是否有相同key已存在,即是否有相同SQLID的值已存在
      if (containsKey(key)) {
        throw new IllegalArgumentException(name + " already contains value for " + key);
      }
      // 如果输入的是namespace+SQLID形式的,也就是SQLID的全路径,类似com.insomsia.dao.mapper.DemoMapper.selectDemo这种
      if (key.contains(".")) {
      	// 以.来划分,把SQLID(不包含namespace,即selectDemo)截出来
        final String shortKey = getShortName(key);
        // 如果mappedStatements中还没有这个shortKey
        if (super.get(shortKey) == null) {
          // 把shortKey和value以key-value的形式存到mappedStatements里面去
          super.put(shortKey, value);
        } else {
          // 若mappedStatements中已有shortKey,将shortKey和new Ambiguity(shortKey)以key-value的形式存入map
          super.put(shortKey, (V) new Ambiguity(shortKey));
        }
      }
      // 只要key在mappedStatements中不存在,则把key和value以key-value的形式存到mappedStatements里面去
      // 所以是用全路径SQLID(namespace+SQLID)和短路径SQLID(不包含namespace)存了两次?
      return super.put(key, value);
    }

然后在执行SQL的时候,再以输入的SQLID作为key,从mappedStatements取出对应的mappedStatement:

    public V get(Object key) {
      // 获取要要执行的SQL对应的Java类实例
      V value = super.get(key);
      // 为空抛出异常
      if (value == null) {
        throw new IllegalArgumentException(name + " does not contain value for " + key);
      }
      // 若类型为Ambiguity(也就是在put时发现已有shortKey时存入的value)则抛出异常
      if (value instanceof Ambiguity) {
        throw new IllegalArgumentException(((Ambiguity) value).getSubject() + " is ambiguous in " + name
            + " (try using the full name including the namespace, or rename one of the entries)");
      }
      return value;
    }

可能有人会问为什么要这么设计?为什么不在put时发现已有重复shortKey的时候就抛出异常呢,非要等到真正执行SQL才抛出异常?
个人觉得可能是为了程序的兼容性和可执行性吧。

比如现在有这么一种场景,两张数据库表A和B,对应两个mapper的namespace分别为com.insomsia.dao.mapper.AMapper和com.insomsia.dao.mapper.BMapper。然后mapper文件内都有一个select节点,ID为"selectById"。

在解析这两个mapper文件将对应select节点的SQL内容缓存到mappedStatements时的过程如下:
1、假设先解析AMapper,则会先在mappedStatements放入key为com.insomsia.dao.mapper.AMapper.selectById和selectById的两个键值对。
2、然后轮到BMapper,先将selectById这个shortKey截出来,然后判断发现mappedStatements已存在selectById的key了,则覆盖再存一次,只不过value的变成了new Ambiguity(shortKey)。然后再往mappedStatements存入key为com.insomsia.dao.mapper.BMapper的键值对。

然后到了程序调用执行SQL的阶段,有两种场景:
1、假设你输入的SQLID是全路径,即namespace+SQLID,那么程序能正常执行
2、假设你输入的单单的一个shortKey(就是一个selectById),那么才会抛出异常
其实正常情况下,我们一般都是会输入全路径的~

接着再分析executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER)这一句:

查看query方法实现,发现Executor是个接口,那么要先看下这里具体调用的是哪个子类的query方法。

代码往上找,可以看到在DefaultSqlSessionFactory构造DefaultSqlSession的openSessionFromDataSource方法中有这么一行:

    final Executor executor = configuration.newExecutor(tx, execType, autoCommit);

可得知是由configuration构造的,点进去看看:

    public Executor newExecutor(Transaction transaction, ExecutorType executorType, boolean autoCommit) {
        executorType = executorType == null ? defaultExecutorType : executorType;
        executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
        Executor executor;
        if (ExecutorType.BATCH == executorType) {
          executor = new BatchExecutor(this, transaction);
        } else if (ExecutorType.REUSE == executorType) {
          executor = new ReuseExecutor(this, transaction);
        } else {
          executor = new SimpleExecutor(this, transaction);
        }
        if (cacheEnabled) {
          executor = new CachingExecutor(executor, autoCommit);
        }
        executor = (Executor) interceptorChain.pluginAll(executor);
        return executor;
    }

executor是根据executorType来创建的,最常用的是SimpleExecutor。然后会再判断cacheEnabled是否为true,这个就是MyBatis配置文件中cacheEnabled的值,默认为true。所以默认情况下会在SimpleExecutor的上面再包装一层CachingExecutor。

然后去看CachingExecutor的query方法:

    public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
        Cache cache = ms.getCache();
        if (cache != null) {
          flushCacheIfRequired(ms);
          if (ms.isUseCache() && resultHandler == null) { 
            ensureNoOutParams(ms, parameterObject, boundSql);
            if (!dirty) {
              cache.getReadWriteLock().readLock().lock();
              try {
                @SuppressWarnings("unchecked")
                List<E> cachedList = (List<E>) cache.getObject(key);
                if (cachedList != null) return cachedList;
              } finally {
                cache.getReadWriteLock().readLock().unlock();
              }
            }
            List<E> list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
            tcm.putObject(cache, key, list); // issue #578. Query must be not synchronized to prevent deadlocks
            return list;
          }
        }
        return delegate.<E>query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
    }

ms.getCache()是获取二级缓存,默认情况下是不开启的,所以为空,然后代码直接走到return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql)。这里的delegate实际就是SimpleExecutor,但是SimpleExecutor并没有实现query方法,所以会执行它的父类BaseExecutor的query方法:

      @SuppressWarnings("unchecked")
      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();
          }
          deferredLoads.clear(); // issue #601
          if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
            clearLocalCache(); // issue #482
          }
        }
        return list;
      }

这里大概的逻辑就是会先用key从一级缓存里面取value,看看是否有值。没有再从数据库查询,然后将查询的结果加入到一级缓存里面再返回。

所以MyBatis的大概运行流程就这样的~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值