mybatis源码解析(一)配置文件解析

下面是我看源码的几点经验供你们参考:一,官方文档不可忽视,官网文档是最准确,最核心的知识宝库,你可以从这里获取到开发团队的思想,设计概要。二,英文很重要,由于我们都使用国外的技术,所以英语是很重要的,我很反对有些培训机构说的英文无用论,除非你一辈子相当码农。三,demo先行,官网中的demo是我们入门写的hello world,几行代码凝聚着作者深藏在代码的核心,如果没有demo你就像一只无头苍蝇一样,始终也无法理解框架内核。四,多看博客,现在java的技术栈很成熟,很多人也在致力研究着源码,可以适当多看看别人博客,将其内化成自己的知识。

  • 对于java程序员,xml似乎是始终绕不开的。ssm三大框架似乎都是从xml开始获取配置选项,对于sevlet3注解没有普遍的时候,web.xml占据重要位置,它是和我们容器起到桥梁的位置。当然这不包括springboot,springboot似乎抛弃了web.xml,但是springboot使用了大量注解,起到了sevlet和拦截器作用。
  • xml在java的技术栈中占据很重要的位置,那它在mybatis中起到了什么作用呢,mybatis的配置从xml获取,sql语句也是从xml获取。
  • 本人功力过浅,如有错误,请大神指导。

demo


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.Reader;

/**
 * Created by wangbeibei on 2019-4-17.
 */
public class MyBatisTest {
    public static void main(String[] args) throws Exception {
        String resource = "configuration.xml";
        Reader reader = Resources.getResourceAsReader(resource);
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder()
                .build(reader);
        SqlSession sqlSession = sqlSessionFactory.openSession();
        User user = sqlSession.selectOne("userTest.selectUser", "40288ab85ce3c20a015ce3ca6df60000");
        System.out.println(user.getRealname());
        sqlSession.close();
    }
}

  • 我们的源码解析就从上面的demo开始,我们一步一步来debug,对于源码我们要把握几个核心类,对于mybatis的解析配置文件核心类就是XMLConfigBuilder和Configuration
  • SqlSessionFactoryBuider.build()方法中初始化XmlConfigBuilder对象,XmlConfigBuilder构造方法中有初始化XpathParser,在Xpathpareser构造方法触发creatDoucument,
    creatDoucument构造Document对象,触发了DocumentBuilder接口类的实现类apache的DocumentBuilderImpl的实现类生成Document。
  • 触发XMLConfigBuilder(XPathParser parser, String environment, Properties props)
    return build(parser.parse());中又触发XMLconfigBulder的parse()----->parseConfiguration用来解析mybatis的配置文件,查找/configuration节点。
  • XMLConfigBuilder是对mybatis的配置文件进行解析的类,会对myabtis解析后的信息存放在Configuration对象中,Configuration对象会贯穿整个mybatis的执行流程,为mybatis的执行过程提供各种需要的配置信息
  • 首先解析是mybatis的xml配置文件,然后通过XMLConfigBuilder装配道configuration,通过XmlConfigBuilder的核心办法ParseConfiguration的parseConfiguration来解析mapper文件,调动xmlMapperBuilder,通过xmlMapperBuilder.Parse的ConfigurationElement(parser.evalNode("/mapper"))来解析xml文件,然后到buildStatementFromContext调用xmlStatementBuilder的parseStatementNode来解析sql节点,填充到MappedStatement对象中 ,sql语句会解析成sqlSource对象
  • 配置文件解析主要用到XMLConfigBuilder(解析mybatis-config.xml) --> XMLMapperBuilder(解析mapper.xml) --> XMLStatementBuilder(解析mapper.xml中cache, resultMap等配置信息,包括处理sql语句中的include标签) -->XMLScriptBuilder(解析mapper.xml中insert update select delete等sql语句节点)
  • uml图
    mybatis源码解析(一)配置文件解析_第1张图片
XMLConfigBuilder源码
//以下参考官网的配置文件。官网是最好的文档。
http://www.mybatis.org/mybatis-3/zh/configuration.html#properties
configuration(配置)
* properties(属性)
* settings(设置)
* typeAliases(类型别名)
* typeHandlers(类型处理器)
* objectFactory(对象工厂)
* plugins(插件)
* environments(环境配置)
    * environment(环境变量)
        * transactionManager(事务管理器)
        * dataSource(数据源)
* databaseIdProvider(数据库厂商标识)
* mappers(映射器)

private void parseConfiguration(XNode root) {
  try {
    //issue #117 read properties first
    //获取配置文件的路径,放入Properties对象中。
    propertiesElement(root.evalNode("properties"));
//获取settings
    Properties settings = settingsAsProperties(root.evalNode("settings"));
    //指定 VFS 的实现(关于vfs请看:https://blog.csdn.net/qq_17231297/article/details/84567599和https://blog.csdn.net/weixin_36145588/article/details/73607463可以理解为读取任何一个系统文件格式的资源文件)
    loadCustomVfs(settings);
    //制定日志的实现(log4j等)
    loadCustomLogImpl(settings);
    //类型别名(TypeAliasRegistry)放入TYPE_ALIASES的hashmap中,这个类注册的别名包括的java的基础数据类型,和数组还有带_的基础数据类型,关于包的注册,查找了Alias注解的类
    typeAliasesElement(root.evalNode("typeAliases"));
  //要实现插件功能要实现Interceptor类,可以参考https://blog.csdn.net/fageweiketang/article/details/80808316博文,同时可以看看mybatis-plus怎么实现的。
    pluginElement(root.evalNode("plugins"));
//设置objectFactory
    objectFactoryElement(root.evalNode("objectFactory"));
    objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
    reflectorFactoryElement(root.evalNode("reflectorFactory"));
    //设置setting
    settingsElement(settings);
    // read it after objectFactory and objectWrapperFactory issue #631
   //读取环境节点,加载数据库配置
    environmentsElement(root.evalNode("environments"));
//
    databaseIdProviderElement(root.evalNode("databaseIdProvider"));
    typeHandlerElement(root.evalNode("typeHandlers"));
//加载mappers节点,解析mapperxml文件(最重要的)&&&&&
    mapperElement(root.evalNode("mappers"));
  } catch (Exception e) {
    throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
  }
}

//通过下列方法来查找节点。
root.evalNode

public XNode evalNode(Object root, String expression) {
  Node node = (Node) evaluate(expression, root, XPathConstants.NODE);
  if (node == null) {
    return null;
  }
//初始化一个Xnode对象
  return new XNode(this, node, variables);
}

private Object evaluate(String expression, Object root, QName returnType) {
  try {
    return xpath.evaluate(expression, root, returnType);
  } catch (Exception e) {
    throw new BuilderException("Error evaluating XPath.  Cause: " + e, e);
  }
}
XNODE
public XNode evalNode(String expression) {
  return xpathParser.evalNode(node, expression);
}







private void propertiesElement(XNode context) throws Exception {
  if (context != null) {
//通过Xnode的getChildrenAsProperties来获取name和value的值
    Properties defaults = context.getChildrenAsProperties();
//获取url和resource节点,从这些节点来获得配置文件,获取值。
    String resource = context.getStringAttribute("resource");
    String url = context.getStringAttribute("url");
    if (resource != null && url != null) {
      throw new BuilderException("The properties element cannot specify both a URL and a resource based property file reference.  Please specify one or the other.");
    }
    if (resource != null) {
      defaults.putAll(Resources.getResourceAsProperties(resource));
    } else if (url != null) {
      defaults.putAll(Resources.getUrlAsProperties(url));
    }
    Properties vars = configuration.getVariables();
    if (vars != null) {
      defaults.putAll(vars);
    }
    parser.setVariables(defaults);
//放入configuration对象中
    configuration.setVariables(defaults);
  }
}

//获取setting对象。关于setting属性请参考setting。也放入properties对象
private Properties settingsAsProperties(XNode context) {
  if (context == null) {
    return new Properties();
  }
  Properties props = context.getChildrenAsProperties();
  // Check that all settings are known to the configuration class
    //反射configuration对象,来进行验证是否存在。
  MetaClass metaConfig = MetaClass.forClass(Configuration.class, localReflectorFactory);
  for (Object key : props.keySet()) {
    if (!metaConfig.hasSetter(String.valueOf(key))) {
      throw new BuilderException("The setting " + key + " is not known.  Make sure you spelled it correctly (case sensitive).");
    }
  }
  return props;
}

private void loadCustomVfs(Properties props) throws ClassNotFoundException {
  String value = props.getProperty("vfsImpl");
  if (value != null) {
    String[] clazzes = value.split(",");
    for (String clazz : clazzes) {
      if (!clazz.isEmpty()) {
        @SuppressWarnings("unchecked")
        Class vfsImpl = (Class)Resources.classForName(clazz);
        configuration.setVfsImpl(vfsImpl);
      }
    }
  }
}




private void loadCustomLogImpl(Properties props) {
  Class logImpl = resolveClass(props.getProperty("logImpl"));
  configuration.setLogImpl(logImpl);
}

private void typeAliasesElement(XNode parent) {
  if (parent != null) {
    for (XNode child : parent.getChildren()) {
      if ("package".equals(child.getName())) {
        String typeAliasPackage = child.getStringAttribute("name");
        configuration.getTypeAliasRegistry().registerAliases(typeAliasPackage);
      } else {
        String alias = child.getStringAttribute("alias");
        String type = child.getStringAttribute("type");
        try {
          Class clazz = Resources.classForName(type);
          if (alias == null) {
            typeAliasRegistry.registerAlias(clazz);
          } else {
            typeAliasRegistry.registerAlias(alias, clazz);
          }
        } catch (ClassNotFoundException e) {
          throw new BuilderException("Error registering typeAlias for '" + alias + "'. Cause: " + e, e);
        }
      }
    }
  }
}


private void pluginElement(XNode parent) throws Exception {
  if (parent != null) {
    for (XNode child : parent.getChildren()) {
      String interceptor = child.getStringAttribute("interceptor");
      Properties properties = child.getChildrenAsProperties();
      Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).newInstance();
      interceptorInstance.setProperties(properties);
      configuration.addInterceptor(interceptorInstance);
    }
  }
}

private void objectFactoryElement(XNode context) throws Exception {
  if (context != null) {
    String type = context.getStringAttribute("type");
    Properties properties = context.getChildrenAsProperties();
    ObjectFactory factory = (ObjectFactory) resolveClass(type).newInstance();
    factory.setProperties(properties);
    configuration.setObjectFactory(factory);
  }
}

private void objectWrapperFactoryElement(XNode context) throws Exception {
  if (context != null) {
    String type = context.getStringAttribute("type");
    ObjectWrapperFactory factory = (ObjectWrapperFactory) resolveClass(type).newInstance();
    configuration.setObjectWrapperFactory(factory);
  }
}

private void reflectorFactoryElement(XNode context) throws Exception {
  if (context != null) {
     String type = context.getStringAttribute("type");
     ReflectorFactory factory = (ReflectorFactory) resolveClass(type).newInstance();
     configuration.setReflectorFactory(factory);
  }
}


private void settingsElement(Properties props) {
    //指定 MyBatis 应如何自动映射列到字段或属性。 NONE 表示取消自动映射;PARTIAL 只会自动映射没有定义嵌套结果集映射的结果集。 FULL 会自动映射任意复杂的结果集(无论是否嵌套)
  configuration.setAutoMappingBehavior(AutoMappingBehavior.valueOf(props.getProperty("autoMappingBehavior", "PARTIAL")));

//autoMappingUnknownColumnBehavior
指定发现自动映射目标未知列(或者未知属性类型)的行为。
* NONE: 不做任何反应
* WARNING: 输出提醒日志 ('org.apache.ibatis.session.AutoMappingUnknownColumnBehavior'的日志等级必须设置为 WARN)
* FAILING: 映射失败 (抛出 SqlSessionException)
  configuration.setAutoMappingUnknownColumnBehavior(AutoMappingUnknownColumnBehavior.valueOf(props.getProperty("autoMappingUnknownColumnBehavior", "NONE")));
    //全局地开启或关闭配置文件中的所有映射器已经配置的任何缓存
  configuration.setCacheEnabled(booleanValueOf(props.getProperty("cacheEnabled"), true));
//
指定 Mybatis 创建具有延迟加载能力的对象所用到的代理工具。CGLIB | JAVASSIST|JAVASSIST (MyBatis 3.3 以上)

  configuration.setProxyFactory((ProxyFactory) createInstance(props.getProperty("proxyFactory")));
    //延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。 特定关联关系中可通过设置 fetchType 属性来覆盖该项的开关状态。
  configuration.setLazyLoadingEnabled(booleanValueOf(props.getProperty("lazyLoadingEnabled"), false));
//当开启时,任何方法的调用都会加载该对象的所有属性。 否则,每个属性会按需加载(参考 lazyLoadTriggerMethods)。
  configuration.setAggressiveLazyLoading(booleanValueOf(props.getProperty("aggressiveLazyLoading"), false));
//是否允许单一语句返回多结果集(需要驱动支持)。
  configuration.setMultipleResultSetsEnabled(booleanValueOf(props.getProperty("multipleResultSetsEnabled"), true));
//使用列标签代替列名。不同的驱动在这方面会有不同的表现,具体可参考相关驱动文档或通过测试这两种不同的模式来观察所用驱动的结果。
  configuration.setUseColumnLabel(booleanValueOf(props.getProperty("useColumnLabel"), true));
//允许 JDBC 支持自动生成主键,需要驱动支持。 如果设置为 true 则这个设置强制使用自动生成主键,尽管一些驱动不能支持但仍可正常工作(比如 Derby)。
  configuration.setUseGeneratedKeys(booleanValueOf(props.getProperty("useGeneratedKeys"), false));
//配置默认的执行器。SIMPLE 就是普通的执行器;REUSE 执行器会重用预处理语句(prepared statements); BATCH 执行器将重用语句并执行批量更新。
  configuration.setDefaultExecutorType(ExecutorType.valueOf(props.getProperty("defaultExecutorType", "SIMPLE")));
//设置超时时间,它决定驱动等待数据库响应的秒数。
  configuration.setDefaultStatementTimeout(integerValueOf(props.getProperty("defaultStatementTimeout"), null));
//为驱动的结果集获取数量(fetchSize)设置一个提示值。此参数只可以在查询设置中被覆盖。
  configuration.setDefaultFetchSize(integerValueOf(props.getProperty("defaultFetchSize"), null));
//是否开启自动驼峰命名规则(camel case)映射,即从经典数据库列名 A_COLUMN 到经典 Java 属性名 aColumn 的类似映射。
  configuration.setMapUnderscoreToCamelCase(booleanValueOf(props.getProperty("mapUnderscoreToCamelCase"), false));
//允许在嵌套语句中使用分页(RowBounds)。如果允许使用则设置为 false
  configuration.setSafeRowBoundsEnabled(booleanValueOf(props.getProperty("safeRowBoundsEnabled"), false));
//MyBatis 利用本地缓存机制(Local Cache)防止循环引用(circular references)和加速重复嵌套查询。 默认值为 SESSION,这种情况下会缓存一个会话中执行的所有查询。 若设置值为 STATEMENT,本地会话仅用在语句执行上,对相同 SqlSession 的不同调用将不会共享数据。
  configuration.setLocalCacheScope(LocalCacheScope.valueOf(props.getProperty("localCacheScope", "SESSION")));
//当没有为参数提供特定的 JDBC 类型时,为空值指定 JDBC 类型。 某些驱动需要指定列的 JDBC 类型,多数情况直接用一般类型即可,比如 NULL、VARCHAR 或 OTHER。
  configuration.setJdbcTypeForNull(JdbcType.valueOf(props.getProperty("jdbcTypeForNull", "OTHER")));
//指定哪个对象的方法触发一次延迟加载。用逗号分隔的方法列表。equals,clone,hashCode,toString
  configuration.setLazyLoadTriggerMethods(stringSetValueOf(props.getProperty("lazyLoadTriggerMethods"), "equals,clone,hashCode,toString"));
   //允许在嵌套语句中使用分页(ResultHandler)。如果允许使用则设置为 false。
  configuration.setSafeResultHandlerEnabled(booleanValueOf(props.getProperty("safeResultHandlerEnabled"), true));
    //指定动态 SQL 生成的默认语言。
  configuration.setDefaultScriptingLanguage(resolveClass(props.getProperty("defaultScriptingLanguage")));
//指定 Enum 使用的默认 TypeHandler
  configuration.setDefaultEnumTypeHandler(resolveClass(props.getProperty("defaultEnumTypeHandler")));
//指定当结果集中值为 null 的时候是否调用映射对象的 setter(map 对象时为 put)方法,这在依赖于 Map.keySet() 或 null 值初始化的时候比较有用。注意基本类型(int、boolean 等)是不能设置成 null 的。
  configuration.setCallSettersOnNulls(booleanValueOf(props.getProperty("callSettersOnNulls"), false));
//允许使用方法签名中的名称作为语句参数名称。 为了使用该特性,你的项目必须采用 Java 8 编译,并且加上 -parameters 选项。(新增于 3.4.1
  configuration.setUseActualParamName(booleanValueOf(props.getProperty("useActualParamName"), true));
//当返回行的所有列都是空时,MyBatis默认返回 null。 当开启这个设置时,MyBatis会返回一个空实例。 请注意,它也适用于嵌套的结果集 (如集合或关联)。(新增于 3.4.2
  configuration.setReturnInstanceForEmptyRow(booleanValueOf(props.getProperty("returnInstanceForEmptyRow"), false));
//指定 MyBatis 增加到日志名称的前缀。
  configuration.setLogPrefix(props.getProperty("logPrefix"));
//指定一个提供 Configuration 实例的类。 这个被返回的 Configuration 实例用来加载被反序列化对象的延迟加载属性值。 这个类必须包含一个签名为static Configuration getConfiguration() 的方法。(新增于 3.2.3)
  configuration.setConfigurationFactory(resolveClass(props.getProperty("configurationFactory")));
}


//获取datasource实例化DataSourceFactory ,从DataSourceFactory获取datasource,获取transactionManager节点实例话TransactionFactory,
从设置configuration设置Environment
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)) {
        TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));
        DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));
        DataSource dataSource = dsFactory.getDataSource();
        Environment.Builder environmentBuilder = new Environment.Builder(id)
            .transactionFactory(txFactory)
            .dataSource(dataSource);
        configuration.setEnvironment(environmentBuilder.build());
      }
    }
  }
}


private void databaseIdProviderElement(XNode context) throws Exception {
  DatabaseIdProvider databaseIdProvider = null;
  if (context != null) {
    String type = context.getStringAttribute("type");
    // awful patch to keep backward compatibility
    if ("VENDOR".equals(type)) {
        type = "DB_VENDOR";
    }
    Properties properties = context.getChildrenAsProperties();
    databaseIdProvider = (DatabaseIdProvider) resolveClass(type).newInstance();
    databaseIdProvider.setProperties(properties);
  }
  Environment environment = configuration.getEnvironment();
  if (environment != null && databaseIdProvider != null) {
    String databaseId = databaseIdProvider.getDatabaseId(environment.getDataSource());
    configuration.setDatabaseId(databaseId);
  }
}
//注册typeHandler,找出javatype和jdbcType,放入typeHandlerRegistry.register放入TypeHandlerRegistry中的ALL_TYPE_HANDLERS_MAP的hashmap中
private void typeHandlerElement(XNode parent) {
  if (parent != null) {
    for (XNode child : parent.getChildren()) {
      if ("package".equals(child.getName())) {
        String typeHandlerPackage = child.getStringAttribute("name");
        typeHandlerRegistry.register(typeHandlerPackage);
      } else {
        String javaTypeName = child.getStringAttribute("javaType");
        String jdbcTypeName = child.getStringAttribute("jdbcType");
        String handlerTypeName = child.getStringAttribute("handler");
        Class javaTypeClass = resolveClass(javaTypeName);
        JdbcType jdbcType = resolveJdbcType(jdbcTypeName);
        Class typeHandlerClass = resolveClass(handlerTypeName);
        if (javaTypeClass != null) {
          if (jdbcType == null) {
            typeHandlerRegistry.register(javaTypeClass, typeHandlerClass);
          } else {
            typeHandlerRegistry.register(javaTypeClass, jdbcType, typeHandlerClass);
          }
        } else {
          typeHandlerRegistry.register(typeHandlerClass);
        }
      }
    }
  }
}
//加载mapper文件,个人认为是最重要的,主要从四个方面来进行加载mapper文件中的,一个是包,一个resource,一个是url,一个class。
private void mapperElement(XNode parent) throws Exception {
  if (parent != null) {
    for (XNode child : parent.getChildren()) {
      if ("package".equals(child.getName())) {
        String mapperPackage = child.getStringAttribute("name");
//从包中获取mapper时候,configuation中设置mappers
        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.");
        }
      }
    }
  }
}

你可能感兴趣的:(mybatis)