第02篇Mybatis配置文件解析
本篇主要内容如下,由于头条页面对Markdown文档的展示问题,所以排版可能有问题。
一、配置文件分析文件分析
在上一篇的代码中,我们看到了一个非常重要文件,这里我们先来人肉分析看,然后看下代码是如何解析的,毕竟代码也是人写的。思路决定出路,我们如果有思路,然后在看源码会更加的具有分析的能动性。Testpublicvoidmapper(){读取配置信息(为什么路径前不用加,因为是相对路径。maven编译后的资源文件和class文件都是在一个包下,所以不用加就是当前包目录)InputStreammapperInputStreamThread。currentThread()。getContextClassLoader()。getResourceAsStream(mybatisConfig。xml);生成SqlSession工厂,SqlSession从名字上看就是,跟数据库交互的会话信息,负责将sql提交到数据库进行执行SqlSessionFactorysqlSessionFactorynewSqlSessionFactoryBuilder()。build(mapperInputStream,development);获取Mybatis配置信息ConfigurationconfigurationsqlSessionFactory。getConfiguration();参数:autoCommit,从名字上看就是是否自动提交事务SqlSessionsqlSessionsqlSessionFactory。openSession(false);获取MapperTUserMappermapperconfiguration。getMapperRegistry()。getMapper(TUserMapper。class,sqlSession);TUsertUsernewTUser();tUser。setName(testUser1);tUser。setTokenId(testTokenId1);mapper。insert(tUser);获取插入的数据System。out。println(mapper。selectAll());数据插入后,执行查询,然后回滚数据sqlSession。rollback();}1。1mybatisConfig。xml
tip注意看高亮行line(4)dtd文件是xml的约束文件,用于约束xml标签中属性line(8)properties标签,指定了配置信息文件是application。propertiesline(1113)mybatis的配置信息line(1527)mybatis支持多环境配置line(3032)映射文件:::
基于上面的行,我们来讲解。lt;?xmlversion1。0encodingUTF8?!DOCTYPEconfigurationPUBLICmybatis。orgDTDConfig3。0ENhttp:mybatis。orgdtdmybatis3config。dtdconfiguration!指定properties配置文件,我这里面配置的是数据库相关propertiesresourceapplication。propertiesproperties!指定Mybatis使用log4jsettingssettingnamelogImplvalueLOG4JsettingsenvironmentsdefaultdevelopmentenvironmentiddevelopmenttransactionManagertypeJDBCdataSourcetypePOOLED!上面指定了数据库配置文件,配置文件里面也是对应的这四个属性propertynamedrivervalue{datasource。driverclassname}propertynameurlvalue{datasource。url}propertynameusernamevalue{datasource。username}propertynamepasswordvalue{datasource。password}dataSourceenvironmentenvironments!映射文件,mybatis精髓,后面才会细讲mappersmapperresourcemapperTUserMapper。xmlmappersconfiguration二、知识点讲解2。1xml约束文件dtd
为什么要学习dtd约束文件呢?当你学会dtd约束文件后,你就知道这个标签有那些属性,知道标签及子标签信息。当有一天你要写开源框架的时候,你也可以来定义你自己的配置文件规则。这部分知识了解就行。不需要死记硬背。因为记住也基本没啥用,只要做到看到了认识,需要用了知道去哪里抄代码学习就够了。2。1。1元素属性属性值
dtd文件
域示例语法例子元素声明根元素标签,元素students有一个student元素空元素
元素元素只出现一次,元素students至少有一个student元素元素最少出现一次,元素students最少有一个student元素声明出现零次或多次的元素,元素students可以有多个student,也可以一个没有元素声明非。。。既。。。类型的内容元素声明混合型的内容属性属性声明,payment有一个属性type,类型为字符类型,默认值checkspan
值类型
类型描述CDATA值为字符数据(characterdata)(en1en2ID值为唯一的idIDREF值为另外一个元素的idIDREFS值为其他id的列表NMTOKEN值为合法的XML名称NMTOKENS值是一个实体ENTITIES值是一个实体列表NOTATION此值是符号的名称xml:值是一个预定义的XML值
默认值参数可使用下列值
类型描述值属性的默认值REQUIRED属性值是必需的IMPLIED属性不是必需的FIXEDvalue属性值是固定的2。2configuration标签分析
前面我们知道了dtd约束文件,我们就可以看下,configuration标签一共有那些子标签及属性信息了。
mybatis3config。dtd
通过分析dtd文件,我们知道有那些子标签及属性信息。内容比较长。但是不是很重要。这里只要知道就行。
后面我们看如何使用代码来解析这些标签。
2。3Mybatis配置解析核心逻辑
:::tip思路决定出路line(6)sqlSessionFactory。getConfiguration()
由此来看所有的解析都是在SqlSessionFactoryBuilder进行完成的。具体的解析xml代码我们不研究,这里我们只要搞清楚它的调用关系,及实现的代码在哪里即可。如果这里看懂,其实都会得到一个结论。就是mybaits的源码是比较简单的,因为他的配置是比较集中的,无论是xml方式或者是注解方式。最终所有的配置信息都在Configuration类中。:::Testpublicvoidconfiguration(){读取配置信息(为什么路径前不用加,因为是相对路径。maven编译后的资源文件和class文件都是在一个包下,所以不用加就是当前包目录)InputStreammapperInputStreamThread。currentThread()。getContextClassLoader()。getResourceAsStream(mybatisConfig。xml);生成SqlSession工厂,SqlSession从名字上看就是,跟数据库交互的会话信息,负责将sql提交到数据库进行执行SqlSessionFactorysqlSessionFactorynewSqlSessionFactoryBuilder()。build(mapperInputStream,development);获取Mybatis配置信息,由此来看所有的解析都是在SqlSessionFactoryBuilder进行完成的。ConfigurationconfigurationsqlSessionFactory。getConfiguration();}2。3。1newSqlSessionFactoryBuilder()。build
这里可以看到就是核心类就是使用XMLConfigBuilder进行解析。下面我们就主要分析XMLConfigBuilderpublicSqlSessionFactorybuild(InputStreaminputStream,Stringenvironment,Propertiesproperties){try{XMLConfigBuilderparsernewXMLConfigBuilder(inputStream,environment,properties);returnbuild(parser。parse());}catch(Exceptione){throwExceptionFactory。wrapException(ErrorbuildingSqlSession。,e);}finally{ErrorContext。instance()。reset();try{inputStream。close();}catch(IOExceptione){Intentionallyignore。Preferpreviouserror。}}}2。3。2核心配置类解析(XMLConfigBuilder)
:::note重点关注line(8),我们看到核心解析类是XPathParserparsernewXPathParser()line(17),标签的解析都在parseConfigurationline(17),思考下为什么先解析propertiesElement(root。evalNode(properties)):::publicclassXMLConfigBuilderextendsBaseBuilder{privatebooleanparsed;privatefinalXPathParserparser;privateStringenvironment;privatefinalReflectorFactorylocalReflectorFactorynewDefaultReflectorFactory();publicConfigurationparse(){if(parsed){thrownewBuilderException(EachXMLConfigBuildercanonlybeusedonce。);}parsedtrue;parseConfiguration(parser。evalNode(configuration));returnconfiguration;}privatevoidparseConfiguration(XNoderoot){try{issue117readpropertiesfirstpropertiesElement(root。evalNode(properties));PropertiessettingssettingsAsProperties(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);readitafterobjectFactoryandobjectWrapperFactoryissue631environmentsElement(root。evalNode(environments));databaseIdProviderElement(root。evalNode(databaseIdProvider));typeHandlerElement(root。evalNode(typeHandlers));mapperElement(root。evalNode(mappers));}catch(Exceptione){thrownewBuilderException(ErrorparsingSQLMapperConfiguration。Cause:e,e);}}}
看到上面代码是不是就恍然大悟了,原来配置文件的标签都是在这里解析呀。这里的主要思路就是将xml解析成Java对象然后放到Configuration中。具体任何实现呢?感兴趣可以自己研究下。2。3。3Configuration属性介绍
那么这些数据最终哪里会使用呢,我们专门留一片文章,详细分析。这里先看看Configuration内部都有那些关键的配置类把。
属性解释TypeAliasRegistrykey是一个别名,value是一个class对象Propertiesvariables配置文件中占位符的变量配置InterceptorChaininterceptorChain拦截链,用于拦截方法,实现插件ObjectFactoryobjectFactory对象实例化统一的工厂方法,我们不用都反射来实例化了ObjectWrapperFactoryobjectWrapperFactory包装对象后为其提供统一的属性操作方法。我们不用通过反射来修改对象属性值了ReflectorFactoryreflectorFactory反射工厂,用于生成一个反射信息对象,具有缓存的作用Environmentenvironment环境信息包含(事务管理器和数据源)TypeHandlerRegistrytypeHandlerRegistry主要处理jdbc的返回数据,转换成Java对象MapperRegistrymapperRegistryMapper生成的处理类,包含代理的逻辑2。3。4Mapper。xml解析
XMLMapperBuilder
解析Mapper对应的xml配置文件,这里面包含了sql的信息。
mapper的dtd约束文件更多,可以参考:https:mybatis。orgmybatis3zhsqlmapxml。html!映射文件,mybatis精髓,后面才会细讲mappersmapperresourcemapperTUserMapper。xmlmappers
这里就要介绍一个重要的类的,MapperBuilderAssistantMapper构建辅助工具类。
属性解释MapperBuilderAssistantMapper构建辅助工具类(缓存配置)CacheRefResolver决定如何使用缓存ParameterMapping当sql中使用到了{}占位符时候,负责填充sql参数ResultMapResolver返回值映射MapString,XNodesqlFragmentssql片段MappedStatementMapper方法的所有信息(出参,入参,及sql信息等)2。4Mybatis可以借鉴的知识点2。4。1占位符解析逻辑
在第一篇的时候我们说过,从配置文件解析中我们能学会,如果解析占位符。并将占位符填充真实数据。这里我们就具体说下是如何解析。还记得前面让思考下为什么先解析propertiesElement(root。evalNode(properties))。
答案就是为了先读取变量信息,方便后面给依赖的信息,给填充值。
我们直接说答案:具体谁来做了这个事情,从职责划分上来看,这个其实还是属于xml文件解析。所以是XPathParserparserXPathParser中填充上变量信息,这样XPathParser在解析的时候会自动将{}填充上真实的数据。执行后,会解析properties标签,并且将属性赋值给XPathParserpropertiesElement(root。evalNode(properties));parser。setVariables(defaults);configuration。setVariables(defaults);XPathParser生成节点时候,属性信息会提前处理。publicXNode(XPathParserxpathParser,Nodenode,Propertiesvariables){this。xpathParserxpathParser;this。nodenode;this。namenode。getNodeName();this。variablesvariables;this。attributesparseAttributes(node);this。bodyparseBody(node);}发现是占位符,就从变量中读取。{datasource。driverclassname}替换成变量值里面的数据。publicstaticStringparse(Stringstring,Propertiesvariables){VariableTokenHandlerhandlernewVariableTokenHandler(variables);GenericTokenParserparsernewGenericTokenParser({,},handler);returnparser。parse(string);}2。4。2MybatisResources工具
可以从配置文件中或者网络中解析配置,生成Resources对象Stringresourcecontext。getStringAttribute(resource);if(resource!null){defaults。putAll(Resources。getResourceAsProperties(resource));}elseif(url!null){defaults。putAll(Resources。getUrlAsProperties(url));}parser。setVariables(defaults);configuration。setVariables(defaults);从资源中获取流InputStreaminputStreamResources。getResourceAsStream(resource)从url中获取流InputStreaminputStreamResources。getUrlAsStream(url)2。4。3MybatisPropertyParser占位符解析TestpublicvoidpropertyParser(){PropertiesvariablesnewProperties();variables。put(datasource。driverclassname,com。mysql。cj。jdbc。Driver);变量中有就从变量中获取参数信息:com。mysql。cj。jdbc。DriverSystem。out。println(PropertyParser。parse(参数信息:{datasource。driverclassname},variables));变量中没有就直接返回keydatasource。urlSystem。out。println(PropertyParser。parse(datasource。url,variables));}2。4。4反射工厂ReflectorFactory
在Mybatis中使用到的反射地方蛮多的,那么都知道反射是相对比较耗时间,那么我们来看Mybatis是如何利用反射工厂来提高反射的性能的?
缓存,对要使用的Class类,做反射并保存起来,生成的对象是Reflector。
ReflectorFactoryreflectorFactorynewDefaultReflectorFactory();publicinterfaceReflectorFactory{booleanisClassCacheEnabled();voidsetClassCacheEnabled(booleanclassCacheEnabled);ReflectorfindForClass(Classlt;?type);}publicclassReflector{privatefinalClasslt;?type;privatefinalString〔〕readablePropertyNames;privatefinalString〔〕writablePropertyNames;privatefinalMapString,InvokersetMethodsnewHashMap();privatefinalMapString,InvokergetMethodsnewHashMap();privatefinalMapString,Classlt;?setTypesnewHashMap();privatefinalMapString,Classlt;?getTypesnewHashMap();privateConstructorlt;?defaultConstructor;privateMapString,StringcaseInsensitivePropertyMapnewHashMap();}
Testpublicvoidreflector()throwsException{ReflectorFactoryreflectorFactorynewDefaultReflectorFactory();ReflectorforClassreflectorFactory。findForClass(TUser。class);TUseruser(TUser)forClass。getDefaultConstructor()。newInstance();forClass。getSetInvoker(uid)。invoke(user,newObject〔〕{1});forClass。getSetInvoker(name)。invoke(user,newObject〔〕{孙悟空});forClass。getSetInvoker(tokenId)。invoke(user,newObject〔〕{tokenId});1System。out。println(forClass。getGetInvoker(uid)。invoke(user,newObject〔〕{}));孙悟空System。out。println(forClass。getGetInvoker(name)。invoke(user,newObject〔〕{}));}2。4。5异常上下文设计ErrorContext在代码执行的过程中,将关键信息通过ErrorContext。instance()。message()保存进去。利用到了线程隔离的知识。ErrorContext。instance()是利用ThreadLocal进行线程隔离。异常打印后,进行reset重置。publicintupdate(Stringstatement,Objectparameter){try{dirtytrue;MappedStatementmsconfiguration。getMappedStatement(statement);returnexecutor。update(ms,wrapCollection(parameter));}catch(Exceptione){throwwrapException(Errorupdatingdatabase。Cause:e,e);}finally{完成之后异常上下文进行重置ErrorContext。instance()。reset();}}将异常上线文中报错的错误都打印出来。publicstaticRuntimeExceptionwrapException(Stringmessage,Exceptione){returnnewPersistenceException(ErrorContext。instance()。message(message)。cause(e)。toString(),e);}