功能
XMLConfigBuilder要完成的最主要的功能其实就是解析xml配置文件,其中分为了这几步
第一步,是构建一个叫做 XPathParser 的类,这个类主要是负责对xml的声明校验以及将xml解析成为document,mybatis解析xml用的是原生的dom解析。
第二步,得到document之后就是configuration的构造,首先会通过XPathParser 解析document的每个节点,调用的是XPath将每个节点解析成为XNode
第三步,得到XNode之后就是解析配置文件中的配置信息,主要是解析properties(属性),settings(设置),typeAliases(类型别名),plugins(插件),objectFactory(实例化对象工厂),objectWrapperFactory(给对象设置工厂),reflectorFactory(反射元数据构建工厂),environments(环境配置),databaseIdProvider(数据库厂商标识),typeHandlers(类型处理器),mappers(映射器)这些标签并且将其放入到上下文configuration中
UML
时序图
代码解析
首先是构建 XPathParser
在SqlSessionFactoryBuilder去build的时候其实调用的是这里,会调用一个默认的XMLConfigBuilder多参构造,将文件流传入进来
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
// 构造 XMLConfigBuilder,properties传入的键值配置
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
// (parser.parse() 解析xml
return build(parser.parse());
}
XMLConfigBuilder的构造方法,这里会去创建XPathParser,并且传入一个XMLMapperEntityResolver,XMLMapperEntityResolver这个类是mybatis用来验证xml声明的,想要了解xml的声明怎么验证可以查看我之前的博客
public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) {
this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props);
}
XPathParser的构造方法主要完成了两个步骤,首先是设置validation 为true也就是打开验证,然后就是entityResolver 赋值,这里最关键的是构建XPath,之后是解析document,用的就是java自带的dom解析方式,代码也比较简单
public XPathParser(InputStream inputStream, boolean validation, Properties variables, EntityResolver entityResolver) {
// 构建初始参数,主要是构建XPath
commonConstructor(validation, variables, entityResolver);
// 解析document,用的dom解析
this.document = createDocument(new InputSource(inputStream));
}
private void commonConstructor(boolean validation, Properties variables, EntityResolver entityResolver) {
this.validation = validation;
this.entityResolver = entityResolver;
this.variables = variables;
XPathFactory factory = XPathFactory.newInstance();
this.xpath = factory.newXPath();
}
private Document createDocument(InputSource inputSource) {
// important: this must only be called AFTER common constructor
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
factory.setValidating(validation);
factory.setNamespaceAware(false);
factory.setIgnoringComments(true);
factory.setIgnoringElementContentWhitespace(false);
factory.setCoalescing(false);
factory.setExpandEntityReferences(true);
DocumentBuilder builder = factory.newDocumentBuilder();
builder.setEntityResolver(entityResolver);
return builder.parse(inputSource);
}
构建好XMLConfigBuilder之后就是parse()解析了,在parse()中有个 parsed字段,这个字段是用来标识当前configuration是否已经解析过了,防止重复解析的,可以看到在整个configuration解析完成后将parsed设置为true,这里有个有趣的地方是写死的一个字符串 “/configuration”,它会调用XPathParser的evalNode()方法去获取到document的节点,这也是为什么我们的mybatis配置文件的根节点要写成 configuration 了
public Configuration parse() {
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
parseConfiguration()是一个重要的方法,在这个方法中会去解析在configuration配置的那些信息,比如properties等,从代码中可以看到各个解析动作是很清晰的
private void parseConfiguration(XNode root) {
try {
//解析各个节点的信息
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);
}
}
我们随便进入一个方法里边看一下,发现他正常就是xnode解析,然后根据配置的信息找到这个类最终塞到全局的配置文件configuration中,configuration是mybatis里很重要的一个东西,全局上下文就一个,几乎所有的元数据信息都存储在它中
private void objectFactoryElement(XNode context) throws Exception {
if (context != null) {
String type = context.getStringAttribute("type");
Properties properties = context.getChildrenAsProperties();
ObjectFactory factory = (ObjectFactory) resolveClass(type).getDeclaredConstructor().newInstance();
factory.setProperties(properties);
configuration.setObjectFactory(factory);
}
}
settings
在XMLConfigBuilder中不得不说的一个地方就是settings文件的配置,settings其实还是有一些比较有趣的东西的,适当去配置一下可以调整mybatis的一直策略从而提升性能。
比如说你想让自定义嵌套结果也自动字段映射的话,可以在配置autoMappingBehavior为FULL,这样mybatis会自动将嵌套结果映射不过性能可能会有些损耗,但是方便。
再比如可以配置延迟加载lazyLoadingEnabled,这样只有用到了才会加载,可以适当提升性能,或者是aggressiveLazyLoading按需加载。
当然也可以这是默认的执行器,执行器是mybatis执行sql查询的核心,默认是simple,你可以设置为REUSE ,重用prepareStatement,这样就不用每次都去解析prepareStatement了。
最有趣的还是logPrefix这个参数,他可以为mybatis的日志添加一个前缀,这样查看起来日志也比较方便。
其实这些都是可以适当调整修改的,如果你有需要,可以仔细看看这一块他们分别所起的作用。
private void settingsElement(Properties props) {
// 指定 MyBatis 应如何自动映射列到字段或属性。PARTIAL 只会自动映射没有定义嵌套结果映射的字段。
configuration.setAutoMappingBehavior(AutoMappingBehavior.valueOf(props.getProperty("autoMappingBehavior", "PARTIAL")));
// 指定发现自动映射目标未知列(或未知属性类型)的行为。NONE: 不做任何反应
configuration.setAutoMappingUnknownColumnBehavior(AutoMappingUnknownColumnBehavior.valueOf(props.getProperty("autoMappingUnknownColumnBehavior", "NONE")));
// 全局性地开启或关闭所有映射器配置文件中已配置的任何缓存。默认为开启
configuration.setCacheEnabled(booleanValueOf(props.getProperty("cacheEnabled"), true));
// 指定 Mybatis 创建可延迟加载对象所用到的代理工具。JAVASSIST (MyBatis 3.3 以上)
configuration.setProxyFactory((ProxyFactory) createInstance(props.getProperty("proxyFactory")));
//延迟加载的全局开关。默认为关闭
configuration.setLazyLoadingEnabled(booleanValueOf(props.getProperty("lazyLoadingEnabled"), false));
// 开启时,任一方法的调用都会加载该对象的所有延迟加载属性。 否则,每个延迟加载属性会按需加载
configuration.setAggressiveLazyLoading(booleanValueOf(props.getProperty("aggressiveLazyLoading"), false));
// 是否允许单个语句返回多结果集,默认我true
configuration.setMultipleResultSetsEnabled(booleanValueOf(props.getProperty("multipleResultSetsEnabled"), true));
// 使用列标签代替列名。
configuration.setUseColumnLabel(booleanValueOf(props.getProperty("useColumnLabel"), true));
// 允许 JDBC 支持自动生成主键,需要数据库驱动支持。
configuration.setUseGeneratedKeys(booleanValueOf(props.getProperty("useGeneratedKeys"), false));
// 配置默认的执行器。SIMPLE 就是普通的执行器;REUSE 执行器会重用预处理语句(PreparedStatement); BATCH 执行器不仅重用语句还会执行批量更新。
configuration.setDefaultExecutorType(ExecutorType.valueOf(props.getProperty("defaultExecutorType", "SIMPLE")));
// 设置超时时间,它决定数据库驱动等待数据库响应的秒数。
configuration.setDefaultStatementTimeout(integerValueOf(props.getProperty("defaultStatementTimeout"), null));
// 为驱动的结果集获取数量(fetchSize)设置一个建议值。此参数只可以在查询设置中被覆盖。
configuration.setDefaultFetchSize(integerValueOf(props.getProperty("defaultFetchSize"), null));
// 指定语句默认的滚动策略。
configuration.setDefaultResultSetType(resolveResultSetType(props.getProperty("defaultResultSetType")));
// 是否开启驼峰命名自动映射,即从经典数据库列名 A_COLUMN 映射到经典 Java 属性名 aColumn。
configuration.setMapUnderscoreToCamelCase(booleanValueOf(props.getProperty("mapUnderscoreToCamelCase"), false));
// 是否允许在嵌套语句中使用分页
configuration.setSafeRowBoundsEnabled(booleanValueOf(props.getProperty("safeRowBoundsEnabled"), false));
// MyBatis 利用本地缓存机制(Local Cache)防止循环引用和加速重复的嵌套查询。 默认值为 SESSION,会缓存一个会话中执行的所有查询。
configuration.setLocalCacheScope(LocalCacheScope.valueOf(props.getProperty("localCacheScope", "SESSION")));
// 当没有为参数指定特定的 JDBC 类型时,空值的默认 JDBC 类型。这里为OTHER,也可以设置为null
configuration.setJdbcTypeForNull(JdbcType.valueOf(props.getProperty("jdbcTypeForNull", "OTHER")));
// 指定对象的哪些方法触发一次延迟加载。
configuration.setLazyLoadTriggerMethods(stringSetValueOf(props.getProperty("lazyLoadTriggerMethods"), "equals,clone,hashCode,toString"));
// 是否允许在嵌套语句中使用结果处理器(ResultHandler)。如果允许使用则设置为 false。
configuration.setSafeResultHandlerEnabled(booleanValueOf(props.getProperty("safeResultHandlerEnabled"), true));
// 指定动态 SQL 生成使用的默认脚本语言。默认为 XMLLanguageDriver
configuration.setDefaultScriptingLanguage(resolveClass(props.getProperty("defaultScriptingLanguage")));
// 指定 Enum 使用的默认 TypeHandler 。
configuration.setDefaultEnumTypeHandler(resolveClass(props.getProperty("defaultEnumTypeHandler")));
// 指定当结果集中值为 null 的时候是否调用映射对象的 setter(map 对象时为 put)方法。
configuration.setCallSettersOnNulls(booleanValueOf(props.getProperty("callSettersOnNulls"), false));
configuration.setUseActualParamName(booleanValueOf(props.getProperty("useActualParamName"), true));
// 当返回行的所有列都是空时,MyBatis默认返回 null。 当开启这个设置时,MyBatis会返回一个空实例。
configuration.setReturnInstanceForEmptyRow(booleanValueOf(props.getProperty("returnInstanceForEmptyRow"), false));
// 指定 MyBatis 增加到日志名称的前缀
configuration.setLogPrefix(props.getProperty("logPrefix"));
configuration.setConfigurationFactory(resolveClass(props.getProperty("configurationFactory")));
}
XMLConfigBuilder做的事情还是比较中规中矩的,复杂的地方在于mapper映射器的解析,也就是在mapperElement中,里边的内容下一章会介绍。