Mybatis源码分析

184 阅读8分钟

一、配置文件解析过程

String resource = "mybatis-config.xml";
InputStream stream = Resources.getResourceAsStream(resource);

Resources.getResourceAsStream(resource)读取文件

public static InputStream getResourceAsStream(String resource) throws IOException {
  return getResourceAsStream(null, resource);
}

public static InputStream getResourceAsStream(ClassLoader loader, String resource) throws IOException {
  InputStream in = classLoaderWrapper.getResourceAsStream(resource, loader);
  if (in == null) {
    throw new IOException("Could not find resource " + resource);
  }
  return in;
}

public InputStream getResourceAsStream(String resource, ClassLoader classLoader) {
  return getResourceAsStream(resource, getClassLoaders(classLoader));
}


InputStream getResourceAsStream(String resource, ClassLoader[] classLoader) {
  for (ClassLoader cl : classLoader) {
    if (null != cl) {

      // try to find the resource as passed
      InputStream returnValue = cl.getResourceAsStream(resource);

      // now, some class loaders want this leading "/", so we'll add it and try again if we didn't find the resource
      if (null == returnValue) {
        returnValue = cl.getResourceAsStream("/" + resource);
      }

      if (null != returnValue) {
        return returnValue;
      }
    }
  }
  return null;
}

实际上,由ClassLoader.getResourceAsStream(resource)加载资源文件。

二、通过SqlSessionFactoryBuilder创建SqlSessionFactory

sqlSessionFactory = new SqlSessionFactoryBuilder().build(stream);

image.png

在这里,Mybatis使用了建造者(Builder)模式。

建造者模式:使用多个简单的对象一步一步构建成一个复杂的对象,这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。

image.png

场景介绍:关于建造者模式在 Mybatis 框架里的使用,那真是纱窗擦屁股,给你漏了一手。到处都是 XxxxBuilder,所有关于 XML 文件的解析到各类对象的封装,都使用建造者以及建造者助手来完成对象的封装。它的核心目的就是不希望把过多的关于对象的属性设置,写到其他业务流程中,而是用建造者的方式提供最佳的边界隔离。

同类场景SqlSessionFactoryBuilder 、 XMLConfigBuilder 、 XMLMapperBuilder 、 XMLStatementBuilder 、 CacheBuilder

public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
  try {
    //XMLConfigBuilder 顾名思义,这个类是用来解析xml配置文件的
    //返回的parser引用调用parse方法开始解析mybatis的配置文件
    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 {
     if (inputStream != null) {
       inputStream.close();
     }
    } catch (IOException e) {
      // Intentionally ignore. Prefer previous error.
    }
  }
}
public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) {
  //初始化XPathParser对象
  this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props);
}
public XPathParser(InputStream inputStream, boolean validation, Properties variables, EntityResolver entityResolver) {
  commonConstructor(validation, variables, entityResolver);
  //创建document对象
  this.document = createDocument(new InputSource(inputStream));
}
public Document parse(InputSource is) throws SAXException, IOException {
    if (is == null) {
        throw new IllegalArgumentException(
            DOMMessageFormatter.formatMessage(DOMMessageFormatter.DOM_DOMAIN,
            "jaxp-null-input-source", null));
    }
    if (fSchemaValidator != null) {
        if (fSchemaValidationManager != null) {
            fSchemaValidationManager.reset();
            fUnparsedEntityHandler.reset();
        }
        resetSchemaValidator();
    }
    //开始解析配置文件
    domParser.parse(is);
    //得到标签的key value
    Document doc = domParser.getDocument();
    domParser.dropDocumentReferences();
    return doc;
}

image.png

接下来开始初始化Configuration对象

Configuration在Mybatis源码非常重要,是一个单例Bean,贯穿整个会话的生命周期,所有的配置对象;映射、缓存、入参、出参、拦截器、注册机、对象工厂等,都在 Configuration 配置项中初始化。并随着 SqlSessionFactoryBuilder 构建阶段完成实例化操作。

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

parser.parse()开始解析xml

public Configuration parse() {
  //判断是否重复解析
  if (parsed) {
    throw new BuilderException("Each XMLConfigBuilder can only be used once.");
  }
  parsed = true;
  //解析mybatis配置文件中的一级标签<configuration>,得到所有的子标签
  parseConfiguration(parser.evalNode("/configuration"));
  return configuration;
}
private void parseConfiguration(XNode root) {
  //此处root的值是我的配置文件的内容
  /**
   *  <configuration>
   *     <environments default="development">
   *         <environment id="development">
   *             <transactionManager type="JDBC">
   *             </transactionManager>
   *             <dataSource type="POOLED">
   *                 <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
   *                 <property name="url" value="jdbc:mysql://127.0.0.1:3306/glkt_user?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai"/>
   *                 <property name="username" value="root"/>
   *                 <property name="password" value="root"/>
   *             </dataSource>
   *         </environment>
   *     </environments>
   *     <mappers>
   *         <mapper resource="mapper/UserMapper.xml"/>
   *     </mappers>
   * </configuration>
   *
   * */
  try {
    // 以下开始解析mybatis配置文件中的各个标签
    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
    //<dataSource>标签在<environments>下,所以该方法能获取到数据源
    environmentsElement(root.evalNode("environments"));
    databaseIdProviderElement(root.evalNode("databaseIdProvider"));
    typeHandlerElement(root.evalNode("typeHandlers"));
    //解析mappers标签
    mapperElement(root.evalNode("mappers"));
  } catch (Exception e) {
    throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
  }
}

environmentsElement方法用来解析environments标签,我们配置的连接数据库的数据源就在该标签下,所有Mybatis是在这里拿到数据库连接信息的。

private void environmentsElement(XNode context) throws Exception {
  if (context != null) {
    if (environment == null) {
      environment = context.getStringAttribute("default");
    }
    for (XNode child : context.getChildren()) {
      String id = child.getStringAttribute("id");
      if (isSpecifiedEnvironment(id)) {
        //JdbcTransactionFactory
        TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));
        //PooledDataSourceFactory
        DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));
        DataSource dataSource = dsFactory.getDataSource();
        Environment.Builder environmentBuilder = new Environment.Builder(id)
            .transactionFactory(txFactory)
            .dataSource(dataSource);
        //把id,事务,数据源set进Configuration类,后面执行sql会从Configuration中拿数据源
        configuration.setEnvironment(environmentBuilder.build());
        break;
      }
    }
  }
}

解析mapper标签

private void mapperElement(XNode parent) throws Exception {
/**
 * parent:
 * <mappers>
 *     <mapper resource="mapper/UserMapper.xml"/>
 * </mappers>
 * **/
  if (parent != null) {
    for (XNode child : parent.getChildren()) {
      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);
          try(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);
          try(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.");
        }
      }
    }
  }
}

mapperParser.parse()解析mapper映射器的方法入口

public void parse() {
  if (!configuration.isResourceLoaded(resource)) {
    //解析mapper.xml文件,把所有的配置对象都放进configuration属性中。
    configurationElement(parser.evalNode("/mapper"));
    configuration.addLoadedResource(resource);
    //通过namespace绑定mapper
    bindMapperForNamespace();
  }

  parsePendingResultMaps();
  parsePendingCacheRefs();
  parsePendingStatements();
}

configurationElement(parser.evalNode("/mapper"))

private void configurationElement(XNode context) {
  try {
    String namespace = context.getStringAttribute("namespace");
    if (namespace == null || namespace.isEmpty()) {
      throw new BuilderException("Mapper's namespace cannot be empty");
    }
    builderAssistant.setCurrentNamespace(namespace);
    cacheRefElement(context.evalNode("cache-ref"));
    cacheElement(context.evalNode("cache"));
    parameterMapElement(context.evalNodes("/mapper/parameterMap"));
    resultMapElements(context.evalNodes("/mapper/resultMap"));
    sqlElement(context.evalNodes("/mapper/sql"));
    buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
  } catch (Exception e) {
    throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);
  }
}

bindMapperForNamespace()

private void bindMapperForNamespace() {
  String namespace = builderAssistant.getCurrentNamespace();
  if (namespace != null) {
    Class<?> boundType = null;
    try {
      boundType = Resources.classForName(namespace);
    } catch (ClassNotFoundException e) {
      // ignore, bound type is not required
    }
    if (boundType != null && !configuration.hasMapper(boundType)) {
      // Spring may not know the real resource name so we set a flag
      // to prevent loading again this resource from the mapper interface
      // look at MapperAnnotationBuilder#loadXmlResource
      configuration.addLoadedResource("namespace:" + namespace);
      //把interface com.wkt.mybatis.mapper.UserMapper 放到Configuration中
      configuration.addMapper(boundType);
    }
  }
}
public <T> void addMapper(Class<T> type) {
  mapperRegistry.addMapper(type);
}
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 {
      //代理工厂map interface mapper.UserMapper->工厂类
      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);
      }
    }
  }
}

Configuration几乎包含了mapper所有的信息,作为参数,创建SqlSessionFactory

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

三、SqlSession会话的创建过程

到这里,开始创建SqlSession会话。 mybatis操作的时候跟数据库的每一次连接,都需要创建一个会话,我们用openSession()方法来创建。这个会话里面需要包含一个Executor用来执行 SQL。Executor又要指定事务类型和执行器的类型。

3.1 创建Transaction(两种方式)

属性产生工厂类产生事务
JDBCJbdcTransactionFactoryJdbcTransaction
MANAGEDManagedTransactionFactoryManagedTransaction
  • 如果配置的是 JDBC,则会使用Connection 对象的 commit()、rollback()、close()管理事务。
  • 如果配置成MANAGED,会把事务交给容器来管理,比如 JBOSS,Weblogic。
SqlSession sqlSession = sqlSessionFactory.openSession();
@Override
public SqlSession openSession() {
  return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
}
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
  Transaction tx = null;
  try {
    //前面有过set,从这里拿
    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();
  }
}

Mybatis的执行器Executor

image.png

四、获得Mapper对象

UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
@Override
public <T> T getMapper(Class<T> type) {
  return configuration.getMapper(type, this);
}
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
  //从MapperRegistry的knownMappers中
  return mapperRegistry.getMapper(type, sqlSession);
}

从knownMappers的Map里根据接口类型(interface mapper.UserMapper)取出对应的工厂类。

public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
  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);
  }
}
public T newInstance(SqlSession sqlSession) {
  final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
  return newInstance(mapperProxy);
}
protected T newInstance(MapperProxy<T> mapperProxy) {
  //通过JDK动态代理返回代理对象MapperProxy
  return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}

五、执行sql

User user = userMapper.selectById(1L);

所有的Mapper都为MapperProxy的代理对象,所以任何方法都会执行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);
  }
}

最终会执行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;
}

我的测试方法会调用selectOne(),实际上调用的也是selectList()

@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;
  }
}
private <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {
  try {
    MappedStatement ms = configuration.getMappedStatement(statement);
    return executor.query(ms, wrapCollection(parameter), rowBounds, handler);
  } catch (Exception e) {
    throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
  } finally {
    ErrorContext.instance().reset();
  }
}

5.1 执行query方法

5.2 创建CacheKey

从 BoundSql 中获取SQL信息,创建 CacheKey。这个CacheKey就是缓存的Key。

@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
  BoundSql boundSql = ms.getBoundSql(parameterObject);
  CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
  return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
    throws SQLException {
  Cache cache = ms.getCache();
//这里先拿到的是二级缓存,默认为null,所以不会走if判断
//mybatis是先判断二级缓存,再判断一级缓存
  if (cache != null) {
    flushCacheIfRequired(ms);
    if (ms.isUseCache() && resultHandler == null) {
      ensureNoOutParams(ms, boundSql);
      @SuppressWarnings("unchecked")
      List<E> list = (List<E>) tcm.getObject(cache, key);
      if (list == null) {
        list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
        tcm.putObject(cache, key, list); // issue #578 and #116
      }
      return list;
    }
  }
  return delegate.query(ms, parameterObject, 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.");
  }
  //queryStack用于记录查询栈,防止递归查询重复处理缓存
  if (queryStack == 0 && ms.isFlushCacheRequired()) {
    //清除本地缓存(一级缓存)
    clearLocalCache();
  }
  List<E> list;
  try {
    queryStack++;
    //根据CacheKey 从本地缓存中拿
    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 {
    //执行Executor的doQuery方法,默认为SimpleExecutor
    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方法内部

@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);
  }
}
@Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
  String sql = boundSql.getSql();
  statement.execute(sql);
  return resultSetHandler.handleResultSets(statement);
}

点进handleResultSets方法里面,查看ResultSetWrapper的构造方法,ORM框架的核心就在这里。

ORM(Object Relational Mapping) 框架采用元数据来描述对象与关系映射的细节,元数据一般采用XML格式,并且存放在专门的对象一映射文件中。简单理解为一种框架的格式

private final List<String> columnNames = new ArrayList<>();
private final List<String> classNames = new ArrayList<>();
private final List<JdbcType> jdbcTypes = new ArrayList<>();

image.png

image.png

image.png

这三个list实现了数据库Mysql字段的数据类型和Java属性的数据类型的转换,ORM框架的精髓所在。

public ResultSetWrapper(ResultSet rs, Configuration configuration) throws SQLException {
  super();
  this.typeHandlerRegistry = configuration.getTypeHandlerRegistry();
  this.resultSet = rs;
  final ResultSetMetaData metaData = rs.getMetaData();
  final int columnCount = metaData.getColumnCount();
  for (int i = 1; i <= columnCount; i++) {
    columnNames.add(configuration.isUseColumnLabel() ? metaData.getColumnLabel(i) : metaData.getColumnName(i));
    jdbcTypes.add(JdbcType.forCode(metaData.getColumnType(i)));
    classNames.add(metaData.getColumnClassName(i));
  }
}