MyBatis原理初探

11 篇文章 0 订阅
3 篇文章 0 订阅

MyBatis原理初探

SqlSessionFactoryBuilder对象

image-20210313161737028

每一个mybatis的应用程序都是SqlSessionFactoryBuilder,它是根据传入的对象创建SqlSessionFactory实例。

在spring项目中,我们通常使用xml配置文件,通过xml文件创建Configuration对象,然后通过重载选取合适的build方法,进而创建SqlSessionFactory对象。此外,我们还可以通过Properties或者String对象创建SqlSessionFactory。

mybatis-config.xml中的配置文件会被解析为Configuration对象,SqlSessionFactoryBuilder根据传入的(XML)数据流生成Configuration对象,然后根据Configuration对象创建默认的SqlSessionFactory实现。具体方法如下:

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

没有必要每次访问mybatis都创建SqlSessionFactoryBuilder对象,通常是创建一个全局对象。

Configuration对象

Configuration对象位于org.apache.ibatis.session包中,它是mybatis初始化的核心。Configuration对象对应了mybatis的配置文件

image-20210314124713364

以下是它包含的字段。各字段对应的含义在https://mybatis.org/mybatis-3/zh/configuration.html#中可以查到。

	protected Environment environment;
    protected boolean safeRowBoundsEnabled;
    protected boolean safeResultHandlerEnabled;
    protected boolean mapUnderscoreToCamelCase;
    protected boolean aggressiveLazyLoading;
    protected boolean multipleResultSetsEnabled;
    protected boolean useGeneratedKeys;
    protected boolean useColumnLabel;
    protected boolean cacheEnabled;
    protected boolean callSettersOnNulls;
    protected boolean useActualParamName;
    protected boolean returnInstanceForEmptyRow;
    protected boolean shrinkWhitespacesInSql;
    protected String logPrefix;
    protected Class<? extends Log> logImpl;	
    protected Class<? extends VFS> vfsImpl;
    protected LocalCacheScope localCacheScope;
    protected JdbcType jdbcTypeForNull;
    protected Set<String> lazyLoadTriggerMethods;
    protected Integer defaultStatementTimeout;
    protected Integer defaultFetchSize;
    protected ResultSetType defaultResultSetType;
    protected ExecutorType defaultExecutorType;	
    protected AutoMappingBehavior autoMappingBehavior;
    protected AutoMappingUnknownColumnBehavior autoMappingUnknownColumnBehavior;
    protected Properties variables;
    protected ReflectorFactory reflectorFactory;
    protected ObjectFactory objectFactory;
    protected ObjectWrapperFactory objectWrapperFactory;
    protected boolean lazyLoadingEnabled;
    protected ProxyFactory proxyFactory;
    protected String databaseId;
    protected Class<?> configurationFactory;
    protected final MapperRegistry mapperRegistry;
    protected final InterceptorChain interceptorChain;
    protected final TypeHandlerRegistry typeHandlerRegistry;
    protected final TypeAliasRegistry typeAliasRegistry;
    protected final LanguageDriverRegistry languageRegistry;
    protected final Map<String, MappedStatement> mappedStatements;
    protected final Map<String, Cache> caches;
    protected final Map<String, ResultMap> resultMaps;
    protected final Map<String, ParameterMap> parameterMaps;
    protected final Map<String, KeyGenerator> keyGenerators;
    protected final Set<String> loadedResources;
    protected final Map<String, XNode> sqlFragments;
    protected final Collection<XMLStatementBuilder> incompleteStatements;
    protected final Collection<CacheRefResolver> incompleteCacheRefs;
    protected final Collection<ResultMapResolver> incompleteResultMaps;
    protected final Collection<MethodResolver> incompleteMethods;
    protected final Map<String, String> cacheRefMap;
Environment对象

environment对象主要存储了mybatis配置文件中对应的环境配置,包括DataSource以及TransactionFactory。其中DataSource是数据源的配置;而TransactionFactory是事务工厂,通过事务工厂获取对应的事务,进行事务相关的提交,回滚等操作。

public final class Environment {
  private final String id;
  private final TransactionFactory transactionFactory;
  private final DataSource dataSource;

  public Environment(String id, TransactionFactory transactionFactory, DataSource dataSource) {
    if (id == null) {
      throw new IllegalArgumentException("Parameter 'id' must not be null");
    }
    if (transactionFactory == null) {
      throw new IllegalArgumentException("Parameter 'transactionFactory' must not be null");
    }
    this.id = id;
    if (dataSource == null) {
      throw new IllegalArgumentException("Parameter 'dataSource' must not be null");
    }
    this.transactionFactory = transactionFactory;
    this.dataSource = dataSource;
  }

  public static class Builder {
    private final String id;
    private TransactionFactory transactionFactory;
    private DataSource dataSource;

    public Builder(String id) {
      this.id = id;
    }

    public Builder transactionFactory(TransactionFactory transactionFactory) {
      this.transactionFactory = transactionFactory;
      return this;
    }

    public Builder dataSource(DataSource dataSource) {
      this.dataSource = dataSource;
      return this;
    }

    public String id() {
      return this.id;
    }

    public Environment build() {
      return new Environment(this.id, this.transactionFactory, this.dataSource);
    }

  }
	/*省略了get方法*/

}
解析Configuration对象

我们选取使用InputStream进行创建SqlSessionFactory对象,查看其中如何解析Configuration对象。

当我们使用InputStream构造对象的时候,真正调用的方法是如下方法。

public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
    try {
      XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
      // 调用重载方法
      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.
      }
    }
  }

而XMLConfigBuilder对象调用了它的parse方法

// 解析mybatis的xml配置文件时,调用如下构造函数,通过InputStream构造了XPathParser对象,该对象就是用于解析xml文件的
public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) {
    this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props);
  }

// 上述构造函数最终调用了如下构造函数进行了XMLConfigBuilder对象的构建
private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
    super(new Configuration());
    ErrorContext.instance().resource("SQL Mapper Configuration");
    this.configuration.setVariables(props);
    this.parsed = false;
    this.environment = environment;
    this.parser = parser;
  }
  // 该方法解析我们的配置文件,并生成Configuration对象
  public Configuration parse() {
    // 如果对象解析过就不需要再解析了,因为程序的配置文件是统一的,只需要加载一次,单例模式
    if (parsed) {
      throw new BuilderException("Each XMLConfigBuilder can only be used once.");
    }
    parsed = true;
    parseConfiguration(parser.evalNode("/configuration"));
    return configuration;
  }

// 该方法执行真实的解析,分别对应xml配置文件中不同的标签结点,将这些标签结点的属性设置给configuration对象,最后供程序使用
  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);
    }
  }

通过上述分析我们知道了parser.parse()的作用,就是根据配置文件生成我们的Configuration对象。此时xml配置文件的解析已经完成了,接下来再调用如下build方法,生成了DefaultSqlSessionFactory对象,接下来就是SqlSessionFactory对象上场的时间。

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

SqlSessionFactory对象

SqlSessionFactory接口,定义了基本操作

image-20210313163115934

DefaultSqlSessionFactory实现了SqlSessionFactory接口,并且新增了通过数据源、连接打开SqlSession,通过Environment获取事务工厂,关闭事务的方法。

image-20210313163131594

SqlSessionFactory的主要功能是创建SqlSession对象,和SqlSessionFactoryBuilder对象一样,没有必要每次访问mybatis都创建,所以只需要创建一个全局对就可以。

SqlSessionFactory有一个属性就是Configuration,它是保存mybatis全局配置的一个配置对象(上面已经介绍过)。就是SqlSessionFactoryBuilder从xml文件创建的Configuration对象,在build是传递给了SqlSessionFactory对象。这个对象保存着全局的配置。

获取SqlSession的具体实现

// 通过executor类型、事务级别以及是否开启事务来获取对应的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();
    }
  }

// 通过连接和executor类型获取sqlsession
  private SqlSession openSessionFromConnection(ExecutorType execType, Connection connection) {
    try {
      boolean autoCommit;
      try {
        autoCommit = connection.getAutoCommit();
      } catch (SQLException e) {
        // Failover to true, as most poor drivers
        // or databases won't support transactions
        autoCommit = true;
      }
      final Environment environment = configuration.getEnvironment();
      final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
      final Transaction tx = transactionFactory.newTransaction(connection);
      final Executor executor = configuration.newExecutor(tx, execType);
      return new DefaultSqlSession(configuration, executor, autoCommit);
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }
// 获取事务工厂,用于控制事务,这就是上文介绍的Environment
  private TransactionFactory getTransactionFactoryFromEnvironment(Environment environment) {
    if (environment == null || environment.getTransactionFactory() == null) {
      return new ManagedTransactionFactory();
    }
    return environment.getTransactionFactory();
  }

SqlSession

SqlSession是一个接口。

SqlSession主要实现了如下方法:获取连接Connection,获取Mapper,数据库的增删查改操作,数据库的提交、回滚操作,刷新批处理语句flushStatements,清空缓存、关闭本次session、获取配置文件Configuration操作。

image-20210313164045269

通过其中包含的方法,我们可以知道SqlSession对象的主要功能就是完成一次数据库的访问和结果映射(通俗来说就是执行我们写好的SQL语句并处理返回的数据),它类似于数据库的session概念。SqlSession的默认实现类是DefaultSqlSession,此外它还有两个实现类分别是SqlSessionManager和SqlSessionTemplate。

image-20210313164555264

DefaultSqlSession

DefaultSqlSession对应的字段和构造函数如下,官方给出的注释表示它不是线程安全的。

它有两个必须配置的属性Configuration以及Executor,Configuration就是全局配置文件,SqlSession对数据库的操作都是通过Executor执行的。

/**
 * The default implementation for {@link SqlSession}.
 * Note that this class is not Thread-Safe.
 *
 * @author Clinton Begin
 */
public class DefaultSqlSession implements SqlSession {

  private final Configuration configuration;
  private final Executor executor;

  private final boolean autoCommit;
  private boolean dirty;
  private List<Cursor<?>> cursorList;

  public DefaultSqlSession(Configuration configuration, Executor executor, boolean autoCommit) {
    this.configuration = configuration;
    this.executor = executor;
    this.dirty = false;
    this.autoCommit = autoCommit;
  }
}

我们来看看其中的selectList方法是如何执行的,这里首先获取了MappedStatement对象,然后使用executor的query进行执行,我们先介绍下MappedStatement方法。

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

MappedStatement

MappedStatement中的SqlSource用于存储从注解或者xml文件得到的sql语句。

它与mapper配置文件中的select|update|insert|delete节点相对应,mapper中配置的标签都被封装到了这个对象中。主要用于描述一条sql语句。

public final class MappedStatement {
  private String resource;
  private Configuration configuration;
  private String id;
  private Integer fetchSize;
  private Integer timeout;
  private StatementType statementType;
  private ResultSetType resultSetType;
  private SqlSource sqlSource;
  private Cache cache;
  private ParameterMap parameterMap;
  private List<ResultMap> resultMaps;
  private boolean flushCacheRequired;
  private boolean useCache;
  private boolean resultOrdered;
  private SqlCommandType sqlCommandType;
  private KeyGenerator keyGenerator;
  private String[] keyProperties;
  private String[] keyColumns;
  private boolean hasNestedResultMaps;
  private String databaseId;
  private Log statementLog;
  private LanguageDriver lang;
  private String[] resultSets;
}

下述的select标签在初始化配置文件时会被解析为MappedStatement对象,存储在Configuration对象的MappedStatements属性中

<select id="getUserById" parameterType="java.lang.Integer" resultType="user">
    select * from user where id=#{id}
</select>
// 配置文件中的mappedStatements属性
protected final Map<String, MappedStatement> mappedStatements

而这个属性的配置是在XMLConfigBuilder中解析的

// 解析mappers标签
private void parseConfiguration(XNode root) {
    try {
      // 省略其他节点的解析
      mapperElement(root.evalNode("mappers"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    }
  }
// 解析mappers标签中的节点信息
private void mapperElement(XNode parent) throws Exception {
    if (parent != null) {
      for (XNode child : parent.getChildren()) {
        // 如果是package标签,直接调用addMappers将其对应的类包名加入到mapperRegistry中,否则就逐个解析mapper标签的内容
        if ("package".equals(child.getName())) {
          String mapperPackage = child.getStringAttribute("name");
          configuration.addMappers(mapperPackage);
        } else {
          String resource = child.getStringAttribute("resource");
          String url = child.getStringAttribute("url");
          String mapperClass = child.getStringAttribute("class");
          if (resource != null && url == null && mapperClass == null) {
            ErrorContext.instance().resource(resource);
            InputStream inputStream = Resources.getResourceAsStream(resource);
            XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
            mapperParser.parse();
          } else if (resource == null && url != null && mapperClass == null) {
            ErrorContext.instance().resource(url);
            InputStream inputStream = Resources.getUrlAsStream(url);
            XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
            mapperParser.parse();
          } else if (resource == null && url == null && mapperClass != null) {
            Class<?> mapperInterface = Resources.classForName(mapperClass);
            configuration.addMapper(mapperInterface);
          } else {
            throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
          }
        }
      }
    }
  }
mapperRegistry

MapperRegistry是Configuration中的一个属性,它内部维护一个HashMap用于存放mapper接口的代理工厂类,每个接口对应一个代理工厂类。mappers中可以配置接口的包路径,或者某个具体的接口类。

  // 属性
  private final Configuration config;
  private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<>();

  public <T> void addMapper(Class<T> type) {
    if (type.isInterface()) {
      if (hasMapper(type)) {
        throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
      }
      boolean loadCompleted = false;
      try {
        knownMappers.put(type, new MapperProxyFactory<>(type));
        // It's important that the type is added before the parser is run
        // otherwise the binding may automatically be attempted by the
        // mapper parser. If the type is already known, it won't try.
        MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
        parser.parse();
        loadCompleted = true;
      } finally {
        if (!loadCompleted) {
          knownMappers.remove(type);
        }
      }
    }
  }
 public void addMappers(String packageName, Class<?> superType) {
    ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<>();
    resolverUtil.find(new ResolverUtil.IsA(superType), packageName);
    Set<Class<? extends Class<?>>> mapperSet = resolverUtil.getClasses();
    for (Class<?> mapperClass : mapperSet) {
      addMapper(mapperClass);
    }
  }

Configuration getMapper方法

SqlSession有一个重要的方法就是获取Mapper对象,mapper对象的获取主要使用Configuration的getMapper方法。

使用过mybatis都知道,我们除了要配置mybatis以外,还需要定义接口,在接口中定义访问数据库的方法,此外还要在resources路径或者同级下存放名称一致的mapper.xml文件。这样才能调用这个接口进行数据库的操作。(这里省略了注解机制)

为什么我们只写了接口没有实现就可以调用这个方法呢?这里的奥秘就是使用了动态代理。

SqlSession中的getMapper方法是连接应用程序和mybatis的桥梁。当应用程序访问getMapper方法时,Mybatis会根据传入的接口类型和对应的XML配置文件生成一个代理对象,这个代理对象就叫Mapper对象。应用程序获得Mapper对象后,就应该通过这个Mapper对象来访问Mybatis的SqlSession对象,这样就达到里插入到Mybatis流程的目的。

@Override
public Configuration getConfiguration() {
  return configuration;
}

@Override
public <T> T getMapper(Class<T> type) {
  return configuration.getMapper(type, this);
}

configuration对象主要调用了MapperRegistry对象的方法获取Mapper对象。

	public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
		return mapperRegistry.getMapper(type, sqlSession);
  	}	

下面是getMapper的实现方法

@SuppressWarnings("unchecked")
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
  // 根据类型type获得对应的代理工厂
  final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
  if (mapperProxyFactory == null) {
    throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
  }
  try {
    // 根据代理工厂获得对应的实例
    return mapperProxyFactory.newInstance(sqlSession);
  } catch (Exception e) {
    throw new BindingException("Error getting mapper instance. Cause: " + e, e);
  }
}

MapperProxyFactory对象

MapperProxyFactory对象就是通过动态代理获得mapper接口对应的代理类。

public class MapperProxyFactory<T> {

  private final Class<T> mapperInterface;
  private final Map<Method, MapperMethodInvoker> methodCache = new ConcurrentHashMap<>();

  public MapperProxyFactory(Class<T> mapperInterface) {
    this.mapperInterface = mapperInterface;
  }

  public Class<T> getMapperInterface() {
    return mapperInterface;
  }

  public Map<Method, MapperMethodInvoker> getMethodCache() {
    return methodCache;
  }

  @SuppressWarnings("unchecked")
  protected T newInstance(MapperProxy<T> mapperProxy) {
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
  }

  public T newInstance(SqlSession sqlSession) {
    final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
    return newInstance(mapperProxy);
  }
}

获取到实例以后,就可以调用mapper中的方法了,MapperProxy中是实现了invoke方法

  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
      if (Object.class.equals(method.getDeclaringClass())) {
        return method.invoke(this, args);
      } else {
        return cachedInvoker(method).invoke(proxy, method, args, sqlSession);
      }
    } catch (Throwable t) {
      throw ExceptionUtil.unwrapThrowable(t);
    }
  }

@Override
public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable {
      return mapperMethod.execute(sqlSession, args);
    }

这里又调用了MapperMethod的execute方法,从这里我们可以看到最终是使用sqlSession执行的相关sql语句

public Object execute(SqlSession sqlSession, Object[] args) {
    Object result;
    switch (command.getType()) {
      case INSERT: {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.insert(command.getName(), param));
        break;
      }
      case UPDATE: {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.update(command.getName(), param));
        break;
      }
      case DELETE: {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.delete(command.getName(), param));
        break;
      }
      case SELECT:
        if (method.returnsVoid() && method.hasResultHandler()) {
          executeWithResultHandler(sqlSession, args);
          result = null;
        } else if (method.returnsMany()) {
          result = executeForMany(sqlSession, args);
        } else if (method.returnsMap()) {
          result = executeForMap(sqlSession, args);
        } else if (method.returnsCursor()) {
          result = executeForCursor(sqlSession, args);
        } else {
          Object param = method.convertArgsToSqlCommandParam(args);
          result = sqlSession.selectOne(command.getName(), param);
          if (method.returnsOptional()
              && (result == null || !method.getReturnType().equals(result.getClass()))) {
            result = Optional.ofNullable(result);
          }
        }
        break;
      case FLUSH:
        result = sqlSession.flushStatements();
        break;
      default:
        throw new BindingException("Unknown execution method for: " + command.getName());
    }
    if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
      throw new BindingException("Mapper method '" + command.getName()
          + " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
    }
    return result;
  }

Executor对象

我们来看看DefaultSqlSession中的某个方法

  @Override
  public void select(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {
    try {
      MappedStatement ms = configuration.getMappedStatement(statement);
      executor.query(ms, wrapCollection(parameter), rowBounds, handler);
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

通过上述方法我们可以知道,select方法的执行首先是从configuration中获取到MappedStatement,然后再使用executor.query方法执行。

Executor是一个接口,它包含了基本的增删查改方法,和对结果集的操作,是否进行缓存,清空本地缓存,获取事务并进行提交回滚,关闭会话等操作,它有如下六种基本的实现。从它的函数来看,我们可以知道,Executor主要是通过StatementHandler来访问数据库进行实际的操作。

Execotur接口

image-20210313171038574

以下是BaseExecutor的query方法,BaseExecutor是抽象类

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

protected abstract <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql)
      throws SQLException;

SimpleExecutor继承自BaseExecutor,其中的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 handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
      stmt = prepareStatement(handler, ms.getStatementLog());
      return handler.query(stmt, resultHandler);
    } finally {
      closeStatement(stmt);
    }
  }

doQuery又调用了StatementHandler的query方法,其源码如下所示(以下是BaseStatementHandler的源码,它继承自StatementHandler),从这里我们可以看出来,最终执行方法使用jdbc中的包。

@Override
  public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
    // 通过boundSql获取sql语句
    String sql = boundSql.getSql();
    // 执行sql语句
    statement.execute(sql);
    // 使用resultHandler处理执行结果
    return resultSetHandler.handleResultSets(statement);
  }

SqlSource

/*
代表从xml文件或者注解读取的映射statement内容,它将从用户接收的输入参数创建为SQL语句传递到数据库
*/
public interface SqlSource {

  BoundSql getBoundSql(Object parameterObject);

}

SqlSource有如下四种实现,我将选择使用DynamicSqlSource进行讲解。

image-20210313185953751

以下是DynamicSqlSource的源码

public class DynamicSqlSource implements SqlSource {

  private final Configuration configuration;
  private final SqlNode rootSqlNode;

  public DynamicSqlSource(Configuration configuration, SqlNode rootSqlNode) {
    this.configuration = configuration;
    this.rootSqlNode = rootSqlNode;
  }

  @Override
  public BoundSql getBoundSql(Object parameterObject) {
    DynamicContext context = new DynamicContext(configuration, parameterObject);
    rootSqlNode.apply(context);
    SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
    Class<?> parameterType = parameterObject == null ? Object.class : parameterObject.getClass();
    SqlSource sqlSource = sqlSourceParser.parse(context.getSql(), parameterType, context.getBindings());
    BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
    context.getBindings().forEach(boundSql::setAdditionalParameter);
    return boundSql;
  }

}

BoundSql

在处理了动态内容之后从SqlSource获取的实际SQL字符串,这些SQL可能有SQL占位符"?"以及一个有序的参数映射列表,其中包含每个参数的信息

还存在一些被动态语言创建的其他参数

public class BoundSql {

  private final String sql;
  private final List<ParameterMapping> parameterMappings;
  private final Object parameterObject;
  private final Map<String, Object> additionalParameters;
  private final MetaObject metaParameters;

  public BoundSql(Configuration configuration, String sql, List<ParameterMapping> parameterMappings, Object parameterObject) {
    this.sql = sql;
    this.parameterMappings = parameterMappings;
    this.parameterObject = parameterObject;
    this.additionalParameters = new HashMap<>();
    this.metaParameters = configuration.newMetaObject(additionalParameters);
  }

  public String getSql() {
    return sql;
  }

  public List<ParameterMapping> getParameterMappings() {
    return parameterMappings;
  }

  public Object getParameterObject() {
    return parameterObject;
  }

  public boolean hasAdditionalParameter(String name) {
    String paramName = new PropertyTokenizer(name).getName();
    return additionalParameters.containsKey(paramName);
  }

  public void setAdditionalParameter(String name, Object value) {
    metaParameters.setValue(name, value);
  }

  public Object getAdditionalParameter(String name) {
    return metaParameters.getValue(name);
  }
}

StatementHandler

StatementHandler是真正访问数据库的地方,此处通过java.sql.Statement执行sql语句,并通过ResultHandler处理结果。以下是StatementHandler实现的方法。

StatementHandler是对Statement的封装

image-20210313184806008

StatementHandler有以下五种实现。其中SimpleStatementHandler继承自BaseStatementHandler

image-20210313184847661

以下是SimpleStatementHandler的源码:

通过源码可以知道resultHandler会调用handleResultSets去处理查询返回的结果,而handleResultSets又会调用ResultSetWrapper去处理这个结果集,这个结果集又会通过Configuration获取typeHandlerRegistry,最后通过typeHandlerRegistry获取typeHandler进行类型转换。typeHandler是通过TypeHandlerRegistry进行注册的,需要的时候我们会直接从里面获取。

public class SimpleStatementHandler extends BaseStatementHandler {

  public SimpleStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
    super(executor, mappedStatement, parameter, rowBounds, resultHandler, boundSql);
  }

  @Override
  public int update(Statement statement) throws SQLException {
    String sql = boundSql.getSql();
    Object parameterObject = boundSql.getParameterObject();
    KeyGenerator keyGenerator = mappedStatement.getKeyGenerator();
    int rows;
    if (keyGenerator instanceof Jdbc3KeyGenerator) {
      statement.execute(sql, Statement.RETURN_GENERATED_KEYS);
      rows = statement.getUpdateCount();
      keyGenerator.processAfter(executor, mappedStatement, statement, parameterObject);
    } else if (keyGenerator instanceof SelectKeyGenerator) {
      statement.execute(sql);
      rows = statement.getUpdateCount();
      keyGenerator.processAfter(executor, mappedStatement, statement, parameterObject);
    } else {
      statement.execute(sql);
      rows = statement.getUpdateCount();
    }
    return rows;
  }

  @Override
  public void batch(Statement statement) throws SQLException {
    String sql = boundSql.getSql();
    statement.addBatch(sql);
  }

  // resultHandler会调用handleResultSets去处理查询返回的结果
  @Override
  public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
    String sql = boundSql.getSql();
    statement.execute(sql);
    return resultSetHandler.handleResultSets(statement);
  }

  @Override
  public <E> Cursor<E> queryCursor(Statement statement) throws SQLException {
    String sql = boundSql.getSql();
    statement.execute(sql);
    return resultSetHandler.handleCursorResultSets(statement);
  }

  @Override
  protected Statement instantiateStatement(Connection connection) throws SQLException {
    if (mappedStatement.getResultSetType() == ResultSetType.DEFAULT) {
      return connection.createStatement();
    } else {
      return connection.createStatement(mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY);
    }
  }

  @Override
  public void parameterize(Statement statement) {
    // N/A
  }

}

ResultHandler

ResultHandler是一个接口,主要用于处理查询结果。ResultHandler配合Executor进行数据库操作并处理操作返回的结果。

public interface ResultHandler<T> {

  void handleResult(ResultContext<? extends T> resultContext);

}

DefaultMapResultHandler通过泛型的方式实现了ResultHandler接口,以下是其源码

public class DefaultMapResultHandler<K, V> implements ResultHandler<V> {

  private final Map<K, V> mappedResults;
  private final String mapKey;
  private final ObjectFactory objectFactory;
  private final ObjectWrapperFactory objectWrapperFactory;
  private final ReflectorFactory reflectorFactory;

  @SuppressWarnings("unchecked")
  public DefaultMapResultHandler(String mapKey, ObjectFactory objectFactory, ObjectWrapperFactory objectWrapperFactory, ReflectorFactory reflectorFactory) {
    this.objectFactory = objectFactory;
    this.objectWrapperFactory = objectWrapperFactory;
    this.reflectorFactory = reflectorFactory;
    this.mappedResults = objectFactory.create(Map.class);
    this.mapKey = mapKey;
  }

  @Override
  public void handleResult(ResultContext<? extends V> context) {
    final V value = context.getResultObject();
    final MetaObject mo = MetaObject.forObject(value, objectFactory, objectWrapperFactory, reflectorFactory);
    // TODO is that assignment always true?
    final K key = (K) mo.getValue(mapKey);
    mappedResults.put(key, value);
  }

  public Map<K, V> getMappedResults() {
    return mappedResults;
  }
}

TypeHandler

typeHandler主要用于类型转换,是mybatis中用于实现java类型和JDBC类型的相互转换。typehandler是在xml配置文件中配置的,所以在初始化加载配置文件的时候就将typehandler加载了进来。它是通过typehandlerRegistry进行注册的,然后存放在configuration之中。

typehandler是被ResultSetWrapper使用的,而ResultSetWrapper又是DefaultResultSetHandler(继承自ResultSetHandler)所使用的,而ResultSetHandler又是SimpleStatementHandler(继承自StatementHandler)所使用的,而StatementHandler又被SimpleExecutor(Executor)所使用,sqlsession使用executor执行查询。这样一条链就串联起来了。

image-20210314155307988

总结

mybatis流程分析:

  1. mybatis在启动的时候,首先解析用户定义的mybatis.xml配置文件,将配置文件的所有信息加载到configuration中,并且还会将对应的mapper加载进来,使用MappedStatement存储获得的sql文件,这里会使用XMLConfigBuilder解析xml文件
  2. 当获取到configuration后,就经过SqlSessionFactoryBuilder的builder方法创建SqlSessionFactory对象。
  3. 通过sqlsessionfactory方法创建sqlsession对象,sqlsession包含执行数据库查询的方法
  4. sqlsession通过调用executor进行实际的查询操作,在sqlsession中获取configuration的MappedStatement(mapper中select|update|insert|delete节点映射的语句)属性,后面会通过mappedStatement创建对应的SimpleStatementHandler对象,协助执行sql语句
  5. 在executor执行的时候又会使用boundsql以及sqlsource获取对应的sql语句,将sql语句传递到statementhandler对象,此时executor会创建缓存
  6. statementhandler对象又使用statement进行数据库的查询操作,并且使用resulthandler进行结果的处理
  7. resulthandler会通过typehandler进行结果的转换操作,将jdbc的类型转换为Java的类型,其中已经为我们提供了许多typehandler,同样,我们也可以自己实现typehandler,进行自己的类型转换。

当我们使用接口开发的时候,只实现mapper.xml文件,不需要实现接口,这是怎么实现的?

  1. 主要是依赖configuration的getMapper方法,而getMapper方法使用mapperRegistry对象获取其中的mapper对象,这个mapperRegistry在解析mybatis.xml文件的时候会调用mapperRegistry.addMapper将解析的mapper进行注册,在需要的时候将其取出使用即可。
  2. 当去mapper时,会调用mapperProxyFactory对象生成新的代理对象MapperProxy,在我们执行接口的方法时,会调用该代理类MapperProxy对象的invoke方法
  3. invoke方法会调用MapperMethod的execute方法,该方法会根据SqlCommand的类型选取不同的SqlSession中的方法,然后处理执行后的结果。SqlSession方法会选择其实现类执行对应的方法。

Mybatis最重要的一点就是启动时读取配置文件,然后存入Configuration,然后的所有操作都要使用Configuration

参考

消失er

Coder648

mybatis参考文档

引用

文章首发于个人博客,欢迎来访。
风在哪个人博客

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值