MaBatis运行原理浅析

对MaBatis源码的运行原理的浅谈分析

作用:简单来讲就是替换JDBC

ORM:在面向对象的编程语言中,实现不同类型编程语言和不同类型数据库之间的转换。

JDBC执行数据库操作的过程

连接数据库connection
写入prepareStatement
返回结果resultSet

原理浅谈步骤

1,搭建分析源码的demo

2,对maven指定的jar,进行源码下载

3,编写运行的简易程序

4,MyBatis进行执行分析

5,总结

1 搭建环境

1.1 maven 引入对应的jar

 		<dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.5.6</version>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.22</version>
        </dependency>

1.2 配置文件mybatis.xml



<configuration>
    <environments default="mysql">
        <environment id="mysql">
            <transactionManager type="JDBC">transactionManager>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url"
                          value="jdbc:mysql://localhost:3306/labor_money?characterEncoding=utf8"/>
                <property name="username" value="root"/>
                <property name="password" value="root"/>
            dataSource>
        environment>
    environments>
    <mappers>
        <mapper resource="mapper/WalletUnionconfigMapper.xml"/>
    mappers>
configuration>

2 下载mybatis的源码

MaBatis运行原理浅析_第1张图片
通过idea工具,找到对应的maven引用,直接点击下载就好了

3 简易的示例代码

package com.moonl.jvm.orm;

import com.moonl.jvm.beans.WalletUnionconfig;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

import java.io.IOException;
import java.io.InputStream;

public class MyBatisSrc {
     

    public static void main(String[] args) throws IOException {
     

        String resource = "mybatis-config.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        //
        SqlSession sqlSession = sqlSessionFactory.openSession();
        WalletUnionconfig walletUnionconfig =  sqlSession.selectOne("com.moonl.jvm.dao.WalletUnionconfigMapper.queryObject","2");
        System.out.println(walletUnionconfig.getValues());
    }
}

4 对源码进行分析

首先我们需要拿到,我们的mybatis的配置源文件名称,再对它进行inputStream转换
接着,我们通过SqlSessionFactoryBuilder对配置文件中的信息进行构建

构建过程:

4.1,对配置文件进行XMLConfigBuilder 的转换

4.2,对配置文件中的核心节点:configuration进行解析

4.3,主要解析的内容

  private void parseConfiguration(XNode root) {
     
    try {
     
      // issue #117 read properties first
      propertiesElement(root.evalNode("properties"));
      Properties settings = settingsAsProperties(root.evalNode("settings"));
      loadCustomVfs(settings);
      loadCustomLogImpl(settings);
      typeAliasesElement(root.evalNode("typeAliases"));
      pluginElement(root.evalNode("plugins"));
      objectFactoryElement(root.evalNode("objectFactory"));
      objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
      reflectorFactoryElement(root.evalNode("reflectorFactory"));
      settingsElement(settings);
      // read it after objectFactory and objectWrapperFactory issue #631
      environmentsElement(root.evalNode("environments"));
      databaseIdProviderElement(root.evalNode("databaseIdProvider"));
      typeHandlerElement(root.evalNode("typeHandlers"));
      mapperElement(root.evalNode("mappers"));
    } catch (Exception e) {
     
      throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    }
  }

4.4,重点解析的内容

environments和mappers两个节点
environments是对我们dataSource和transactionManager中的信息进行解析
mappers是对我们的定义的数据库持久化xml进行解析
mappers支持的三种方式 resource(默认方式)、url、class

这时,我们的SqlSessionFactoryBuilder步骤就已经完成了

4.5 开始获取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);
      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();
    }
  }

从源代码中,我们可以看到,首先获取到刚解析并构建好的配置环境信息,再获取TransactionFactory 事务信息,再创建我们的Executor 执行者

  public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
     
    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);
    }
    executor = (Executor) interceptorChain.pluginAll(executor);
    return executor;
  }

ExecutorType也分为三种类型:SIMPLE(默认), REUSE(复用), BATCH(批量)

同时Mybatis是默认开启一级缓存的,具体是如何进行使用,我会在执行查询的过程中进行讲解

4.6 执行查询语句

这里使用sqlSession中的selectOne进行演示

  @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.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;
    }
  }

通过源码,我们可以看到我们经常查询一条数据时,遇到的TooManyResultsException就是这里抛出来的,遇到这个问题,不是因为我们的代码有问题,而是我们的查询语句中查询出来了多条数据,这个时候,应该去检查我们的数据表和我们的查询语句及查询条件。

  @Override
  public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
     
    try {
     
      MappedStatement ms = configuration.getMappedStatement(statement);
      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();
    }
  }

selectList的源码如上所示,首先通过我们要查询的方法:
com.moonl.jvm.dao.WalletUnionconfigMapper.queryObject 获取我们对应的mapper.xml信息定义queryObject 查询

下面我们进入到executor的query方法中去

  @Override
  public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
     
    BoundSql boundSql = ms.getBoundSql(parameter);
    CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
    return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
  }

首先是和我们的查询参数相结合,翻译我们的查询语句BoundSql
格式:select id,name,sex from domo where id = ?
和我们使用jdbc进行查询的时候定义的语法格式一致。
第二步, 创建我们的一级缓存key ,createCacheKey 方法

  @Override
  public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {
     
    if (closed) {
     
      throw new ExecutorException("Executor was closed.");
    }
    CacheKey cacheKey = new CacheKey();
    cacheKey.update(ms.getId());
    cacheKey.update(rowBounds.getOffset());
    cacheKey.update(rowBounds.getLimit());
    cacheKey.update(boundSql.getSql());
    List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
    TypeHandlerRegistry typeHandlerRegistry = ms.getConfiguration().getTypeHandlerRegistry();
    // mimic DefaultParameterHandler logic
    for (ParameterMapping parameterMapping : parameterMappings) {
     
      if (parameterMapping.getMode() != ParameterMode.OUT) {
     
        Object value;
        String propertyName = parameterMapping.getProperty();
        if (boundSql.hasAdditionalParameter(propertyName)) {
     
          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);
        }
        cacheKey.update(value);
      }
    }
    if (configuration.getEnvironment() != null) {
     
      // issue #176
      cacheKey.update(configuration.getEnvironment().getId());
    }
    return cacheKey;
  }

源码中,使用如下6个参数,共同生成了我们的一级缓存key
id(执行请求的方法id)
offset(页码)
limit(行)
sql(转义后的sql)
value(查询条件)
environment(环境配置id)

接下来,我们进入到
query(ms, parameter, rowBounds, resultHandler, key, boundSql);
开始了我们查询

  @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;
  }

关键代码

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

通过刚才定义好的cacheKey,先查询我们的localCache中,我们的缓存是否存在,如果不存在的话,则进行数据库查询,也就是进入到我们的queryFromDatabase中方法中去。

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

进入代码中后,首先看到的是 localCache.putObject(key, EXECUTION_PLACEHOLDER);
这段代码,将我们的key,首先放入我们的一级本地缓存中,value是一个占位符

再开始jdbc数据库查询。
接着是finally 将刚才的缓存key进行清理。

最后将我们的查询结果put到我们的localCache当中去。

5,总结

上述的步骤就完成了mybatis对数据库的查询,同时将我们的查询结果默认的写入到了localCache当中去。

执行流程:
1,配置文件转inputStream
2,SqlSessionFactoryBuilder构建SqlSessionFactory
3,openSqlSession
4,Executor
4.1,StatementHandler
4.2, ResultSetHandler
4.3,LocalCache
5,返回结果

你可能感兴趣的:(java,mybatis,mybatis,java)