Mybatis源码分析之如何理解SQLSession初始化(mybatis,sqlsession,编程语言)

时间:2024-05-02 22:24:46 作者 : 石家庄SEO 分类 : 编程语言
  • TAG :

这次打算写一个 Mybatis 源码分析的系列,大致分为

  • Mybatis 启动流程分析

  • Mybatis 的SQL 执行流程分析

  • Mybatis 的拓展点以及与 Spring Boot 的整合

这篇文章先来分析 Mybati初始化流程,如何读取配置文件到,以及创建出 SqlSession 示例.主要内容包括

  • 读取、解析mybatis 全局配置文件

  • 映射 mapper.java 文件

  • 解析 mapper.xml 文件

  • 解析 mapper.xml 各个节点配置,包括 namespace、缓存、增删改查节点

  • Mybatis 缓存机制

  • 构建DefaultSqlSessionFactory

什么是 SQLSession

SQLSession对外提供了用户和数据库之间交互需要的所有方法,隐藏了底层的细节。默认实现类是DefaultSqlSession

SQLSession 创建示例

通过一个mybatis 官方提供的示例,看下如何手动创建 SQLSession

//Mybatis配置文件,通常包含:数据库连接信息,Mapper.class全限定名包路径,事务配置,插件配置等等Stringresource="org/mybatis/builder/mybatis-config.xml";//以输入流的方式读取配置InputStreaminputStream=Resources.getResourceAsStream(resource);//实例化出SQLSession的必要步骤SqlSessionFactoryBuilder-->SqlSessionFactory-->SqlSessionSqlSessionFactoryBuilderbuilder=newSqlSessionFactoryBuilder();SqlSessionFactoryfactory=builder.build(inputStream);SqlSessionsession=factory.openSession();

通过输入流读取mybatis-config.xml 配置文件

接下来就通过new SqlSessionFactoryBuilder() 开始我们的构建 SQLSession 源码分析

//SqlSessionFactory有4个构造方法,最终都会执行到全参的构造方法publicSqlSessionFactorybuild(Readerreader,Stringenvironment,Propertiesproperties){try{//首先会实例化一个XMLConfigBuilder,这里先有个基本的认知:XMLConfigBuilder就是用来解析XML文件配置的XMLConfigBuilderparser=newXMLConfigBuilder(reader,environment,properties);//经过parser.parse()之后,XML配置文件已经被解析成了Configuration,Configuration对象是包含着mybatis的所有属性.returnbuild(parser.parse());}catch(Exceptione){throwExceptionFactory.wrapException("ErrorbuildingSqlSession.",e);}finally{//...关闭流,抛异常}}
插句嘴,先看下XMLConfigBuilder 类图

Mybatis源码分析之如何理解SQLSession初始化

  • MLConfigBuilder : 解析全局配置文件即 mybatis-config.xml

  • XMLMapperBuilder : 解析 Mapper 文件,配置在mybatis-config.xml 文件中 mapper.java 的包路径

  • XMLStatementBuilder :解析 mapper 文件的节点中 ,SQL 语句标签:select,update,insert,delete

  • SQLSourceBuilder:动态解析 SQL 语句,根据 SqlNode 解析 Sql 语句中的标签,比如<trim>,<if>等标签

当然 BaseBuilder 的实现类不仅这 4 个,这里只介绍这 4 类,在后续一步步分析中都能看到这几个的身影 点进去看一下 parser.parse()

publicConfigurationparse(){//若已经解析过了就抛出异常if(parsed){thrownewBuilderException("EachXMLConfigBuildercanonlybeusedonce.");}//设置解析标志位parsed=true;//解析mybatis-config.xml的节点,读取配置文件,加载到Configuration中parseConfiguration(parser.evalNode("/configuration"));returnconfiguration;}

通过XPathParser 对象来解析 xml 文件成XNode 对象

解析成 Configuration 成之前会先将 xml 配置文件解析成 XNode 对象

publicXNodeevalNode(Objectroot,Stringexpression){//mybatis自已定义了一个XPathParser对象来解析xml,其实对Document做了封装Nodenode=(Node)evaluate(expression,root,XPathConstants.NODE);if(node==null){returnnull;}returnnewXNode(this,node,variables);}

解析config.xml 中的各个节点

接下来就看看下mybatis 是如何一步步读取配置文件的

/***解析mybatis-config.xml的configuration节点*解析XML中的各个节点*/privatevoidparseConfiguration(XNoderoot){try{/***解析properties节点*<propertiesresource="mybatis/db.properties"/>*解析到org.apache.ibatis.parsing.XPathParser#variables*org.apache.ibatis.session.Configuration#variables*/propertiesElement(root.evalNode("properties"));/***解析我们的mybatis-config.xml中的settings节点*具体可以配置哪些属性:http://www.mybatis.org/mybatis-3/zh/configuration.html#settings*<settings><settingname="cacheEnabled"value="true"/><settingname="lazyLoadingEnabled"value="true"/><settingname="mapUnderscoreToCamelCase"value="false"/><settingname="localCacheScope"value="SESSION"/><settingname="jdbcTypeForNull"value="OTHER"/>..............</settings>**/Propertiessettings=settingsAsProperties(root.evalNode("settings"));/***基本没有用过该属性*VFS含义是虚拟文件系统;主要是通过程序能够方便读取本地文件系统、FTP文件系统等系统中的文件资源。Mybatis中提供了VFS这个配置,主要是通过该配置可以加载自定义的虚拟文件系统应用程序解析到:org.apache.ibatis.session.Configuration#vfsImpl*/loadCustomVfs(settings);/***指定MyBatis所用日志的具体实现,未指定时将自动查找。*SLF4J|LOG4J|LOG4J2|JDK_LOGGING|COMMONS_LOGGING|STDOUT_LOGGING|NO_LOGGING*解析到org.apache.ibatis.session.Configuration#logImpl*/loadCustomLogImpl(settings);/***解析我们的别名*<typeAliases><typeAliasalias="User"type="com.xxx.entity.User"/></typeAliases><typeAliases><packagename="com.xxx.use"/></typeAliases>解析到oorg.apache.ibatis.session.Configuration#typeAliasRegistry.typeAliases*/typeAliasesElement(root.evalNode("typeAliases"));/***解析我们的插件(比如分页插件)*mybatis自带的*Executor(update,query,flushStatements,commit,rollback,getTransaction,close,isClosed)ParameterHandler(getParameterObject,setParameters)ResultSetHandler(handleResultSets,handleOutputParameters)StatementHandler(prepare,parameterize,batch,update,query)解析到:org.apache.ibatis.session.Configuration#interceptorChain.interceptors*/pluginElement(root.evalNode("plugins"));objectFactoryElement(root.evalNode("objectFactory"));objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));reflectorFactoryElement(root.evalNode("reflectorFactory"));//设置settings和默认值settingsElement(settings);//readitafterobjectFactoryandobjectWrapperFactoryissue#631/***解析我们的mybatis环境,解析DataSource<environmentsdefault="dev"><environmentid="dev"><transactionManagertype="JDBC"/><dataSourcetype="POOLED"><propertyname="driver"value="${jdbc.driver}"/><propertyname="url"value="${jdbc.url}"/><propertyname="username"value="root"/><propertyname="password"value="Zw726515"/></dataSource></environment><environmentid="test"><transactionManagertype="JDBC"/><dataSourcetype="POOLED"><propertyname="driver"value="${jdbc.driver}"/><propertyname="url"value="${jdbc.url}"/><propertyname="username"value="root"/><propertyname="password"value="123456"/></dataSource></environment></environments>*解析到:org.apache.ibatis.session.Configuration#environment*在集成spring情况下由spring-mybatis提供数据源和事务工厂*/environmentsElement(root.evalNode("environments"));/***解析数据库厂商*<databaseIdProvidertype="DB_VENDOR"><propertyname="SQLServer"value="sqlserver"/><propertyname="DB2"value="db2"/><propertyname="Oracle"value="oracle"/><propertyname="MySql"value="mysql"/></databaseIdProvider>*解析到:org.apache.ibatis.session.Configuration#databaseId*/databaseIdProviderElement(root.evalNode("databaseIdProvider"));/***解析我们的类型处理器节点*<typeHandlers><typeHandlerhandler="org.mybatis.example.ExampleTypeHandler"/></typeHandlers>解析到:org.apache.ibatis.session.Configuration#typeHandlerRegistry.typeHandlerMap*/typeHandlerElement(root.evalNode("typeHandlers"));/***最最重要的就是解析我们的mapper*resource:来注册我们的class类路径下的url:来指定我们磁盘下的或者网络资源的class:若注册Mapper不带xml文件的,这里可以直接注册若注册的Mapper带xml文件的,需要把xml文件和mapper文件同名同路径--><mappers><mapperresource="mybatis/mapper/EmployeeMapper.xml"/><mapperclass="com.tuling.mapper.DeptMapper"></mapper><packagename="com.tuling.mapper"></package>--></mappers>*解析mapper:*1.解析mapper.java接口解析到:org.apache.ibatis.session.Configuration#mapperRegistry.knownMappers*2.解析mapper.xml配置*/mapperElement(root.evalNode("mappers"));}catch(Exceptione){thrownewBuilderException("ErrorparsingSQLMapperConfiguration.Cause:"+e,e);}}

解析 mapper 文件

解析 mapper.java 接口到knowMappers 中

privatevoidmapperElement(XNodeparent)throwsException{if(parent!=null){//获取我们mappers节点下的一个一个的mapper节点for(XNodechild:parent.getChildren()){/***指定mapper的4中方式:*1.指定的mapper所在的包路径,批量注册*2.通过resource目录指定*3.通过url指定,从网络资源或者本地磁盘*4.通过class路径注册*///判断我们mapper是不是通过批量注册的if("package".equals(child.getName())){StringmapperPackage=child.getStringAttribute("name");configuration.addMappers(mapperPackage);}else{//判断从classpath下读取我们的mapperStringresource=child.getStringAttribute("resource");//判断是不是从我们的网络资源读取(或者本地磁盘得)Stringurl=child.getStringAttribute("url");//解析这种类型(要求接口和xml在同一个包下)StringmapperClass=child.getStringAttribute("class");//解析mapper文件if(resource!=null&&url==null&&mapperClass==null){ErrorContext.instance().resource(resource);//把mapper文件读取出一个流,是不是似曾相识,最开始的时候读取mybatis-config.xml配置文件也是通过输入流的方式读取的InputStreaminputStream=Resources.getResourceAsStream(resource);//创建读取XmlMapper构建器对象,用于来解析我们的mapper.xml文件,上面提到过的XMLMapperBuilder对象/***读取的mapper文件会被放入到MapperRegistry中的knownMappers中*Map<Class<?>,MapperProxyFactory<?>>knownMappers=newHashMap<>();*为后续创建Mapper代理对象做准备*/XMLMapperBuildermapperParser=newXMLMapperBuilder(inputStream,configuration,resource,configuration.getSqlFragments());//真正的解析我们的mapper.xml配置文件,这里就会来解析我们的sqlmapperParser.parse();}elseif(resource==null&&url!=null&&mapperClass==null){ErrorContext.instance().resource(url);InputStreaminputStream=Resources.getUrlAsStream(url);XMLMapperBuildermapperParser=newXMLMapperBuilder(inputStream,configuration,url,configuration.getSqlFragments());mapperParser.parse();}elseif(resource==null&&url==null&&mapperClass!=null){Class<?>mapperInterface=Resources.classForName(mapperClass);configuration.addMapper(mapperInterface);}else{thrownewBuilderException("Amapperelementmayonlyspecifyaurl,resourceorclass,butnotmorethanone.");}}}}}

解析 mapper.xml 文件

解析Mapper.xml 中的 SQL 标签

//解析的SQL语句节点会放在Configuration.MappedStatement.SqlSource中,SqlSource中包含了一个个的SQLNode,一个标签对应一个SQLNodepublicvoidparse(){//判断当前的Mapper是否被加载过if(!configuration.isResourceLoaded(resource)){//真正的解析我们的mapperconfigurationElement(parser.evalNode("/mapper"));//把资源保存到我们Configuration中configuration.addLoadedResource(resource);bindMapperForNamespace();}parsePendingResultMaps();parsePendingCacheRefs();parsePendingStatements();}

解析 mapper.xml 中的各个节点

//解析我们的<mapper></mapper>节点privatevoidconfigurationElement(XNodecontext){try{/***解析我们的namespace属性*<mappernamespace="com.xx.mapper.xxxMapper">*/Stringnamespace=context.getStringAttribute("namespace");if(namespace==null||namespace.equals("")){thrownewBuilderException("Mapper'snamespacecannotbeempty");}//保存我们当前的namespace并且判断接口完全类名==namespacebuilderAssistant.setCurrentNamespace(namespace);/***解析我们的缓存引用*说明我当前的缓存引用和DeptMapper的缓存引用一致*<cache-refnamespace="com.xx.mapper.xxxMapper"></cache-ref>解析到org.apache.ibatis.session.Configuration#cacheRefMap<当前namespace,ref-namespace>异常下(引用缓存未使用缓存):org.apache.ibatis.session.Configuration#incompleteCacheRefs*/cacheRefElement(context.evalNode("cache-ref"));/***解析我们的cache节点*<cachetype="org.mybatis.caches.ehcache.EhcacheCache"></cache>解析到:org.apache.ibatis.session.Configuration#cachesorg.apache.ibatis.builder.MapperBuilderAssistant#currentCache*/cacheElement(context.evalNode("cache"));/***解析paramterMap节点*/parameterMapElement(context.evalNodes("/mapper/parameterMap"));/***解析我们的resultMap节点*解析到:org.apache.ibatis.session.Configuration#resultMaps*异常org.apache.ibatis.session.Configuration#incompleteResultMaps**/resultMapElements(context.evalNodes("/mapper/resultMap"));/***解析我们通过sql节点*解析到org.apache.ibatis.builder.xml.XMLMapperBuilder#sqlFragments*其实等于org.apache.ibatis.session.Configuration#sqlFragments*因为他们是同一引用,在构建XMLMapperBuilder时把Configuration.getSqlFragments传进去了*/sqlElement(context.evalNodes("/mapper/sql"));/***解析我们的select|insert|update|delete节点*解析到org.apache.ibatis.session.Configuration#mappedStatements*最终SQL节点会被解析成MappedStatement,一个节点就是对应一个MappedStatement*准确的说sql节点被解析成SQLNode封装在MappedStatement.SqlSource中*SQLNode对应的就是sql节点中的子标签,比如<trim>,<if>,<where>等*/buildStatementFromContext(context.evalNodes("select|insert|update|delete"));}catch(Exceptione){thrownewBuilderException("ErrorparsingMapperXML.TheXMLlocationis'"+resource+"'.Cause:"+e,e);}}

着重分析几个解析过程

解析缓存
privatevoidcacheElement(XNodecontext){if(context!=null){/***cache元素可指定如下属性,每种属性的指定都是针对都是针对底层Cache的一种装饰,采用的是装饰器的模式*缓存属性:*1.eviction:缓存过期策略:默认是LRU*LRU–最近最少使用的:移除最长时间不被使用的对象。-->LruCache*FIFO–先进先出:按对象进入缓存的顺序来移除它们。-->FifoCache*SOFT–软引用:移除基于垃圾回收器状态和软引用规则的对象。-->SoftCache*WEAK–弱引用:更积极地移除基于垃圾收集器状态和弱引用规则的对象。-->WeakCache*2.flushInterval:清空缓存的时间间隔,单位毫秒,默认不清空,指定了之后将会用ScheduleCache封装*3.size:缓存对象的大小,默认是1024,其是针对LruCache而言的,LruCache默认只存储最多1024个Key*4.readOnly:默认是false,底层SerializedCache包装,会在写缓存的时候将缓存对象进行序列化,然后在读缓存的时候进行反序列化,这样每次读到的都将是一个新的对象,即使你更改了读取到的结果,也不会影响原来缓存的对象;true-给所有调用者返回缓存对象的相同实例*5.blocking:默认为false,当指定为true时将采用BlockingCache进行封装,在进行增删改之后的并发查询,只会有一条去数据库查询,而不会并发访问*6.type:type属性用来指定当前底层缓存实现类,默认是PerpetualCache,如果我们想使用自定义的Cache,则可以通过该属性来指定,对应的值是我们自定义的Cache的全路径名称*///解析cache节点的type属性Stringtype=context.getStringAttribute("type","PERPETUAL");//根据type的String获取class类型Class<?extendsCache>typeClass=typeAliasRegistry.resolveAlias(type);//获取缓存过期策略:默认是LRUStringeviction=context.getStringAttribute("eviction","LRU");Class<?extendsCache>evictionClass=typeAliasRegistry.resolveAlias(eviction);//flushInterval(刷新间隔)属性可以被设置为任意的正整数,设置的值应该是一个以毫秒为单位的合理时间量。默认情况是不设置,也就是没有刷新间隔,缓存仅仅会在调用语句时刷新。LongflushInterval=context.getLongAttribute("flushInterval");//size(引用数目)属性可以被设置为任意正整数,要注意欲缓存对象的大小和运行环境中可用的内存资源。默认值是1024。Integersize=context.getIntAttribute("size");//只读)属性可以被设置为true或false。只读的缓存会给所有调用者返回缓存对象的相同实例。因此这些对象不能被修改。这就提供了可观的性能提升。而可读写的缓存会(通过序列化)返回缓存对象的拷贝。速度上会慢一些,但是更安全,因此默认值是falsebooleanreadWrite=!context.getBooleanAttribute("readOnly",false);booleanblocking=context.getBooleanAttribute("blocking",false);Propertiesprops=context.getChildrenAsProperties();//把缓存节点加入到Configuration中//这里的builder()方法利用责任链方式循环实例化Cache对象builderAssistant.useNewCache(typeClass,evictionClass,flushInterval,size,readWrite,blocking,props);}}
Mybatis 缓存机制
MyBatis自带的缓存有一级缓存和二级缓存

一级缓存

Mybatis一级缓存是指Session缓存。作用域默认是一个SqlSession。默认开启一级缓存,范围有SESSION和STATEMENT两种,默认是SESSION,如果需要更改一级缓存的范围,可以在Mybatis的配置文件中,通过localCacheScope指定

<settingname="localCacheScope"value="STATEMENT"/>

二级缓存

Mybatis的二级缓存是指mapper映射文件。二级缓存的作用域是同一个namespace下的mapper映射文件内容,多个SqlSession共享。二级缓存是默认启用的,但是需要手动在 mapper 文件中设置启动二级缓存

//在mapper.xml文件加上此配置,该mapper文件对应的SQL就开启了缓存<cache/>

或者直接关闭缓存

//在全局配置文件中关闭缓存<settings><settingname="cacheEnabled"value="false"/></settings>

注意:如果开启了二级缓存,查询结果的映射对象一定要实现Serializable ,因为mybatis 缓存对象的时候默认是会对映射对象进行序列号操作的

解析select | insert |update |delete节点
privatevoidbuildStatementFromContext(List<XNode>list,StringrequiredDatabaseId){//循环我们的select|delte|insert|update节点for(XNodecontext:list){//创建一个xmlStatement的构建器对象finalXMLStatementBuilderstatementParser=newXMLStatementBuilder(configuration,builderAssistant,context,requiredDatabaseId);try{//通过该步骤解析之后mapper.xml的sql节点就也被解析了statementParser.parseStatementNode();}catch(IncompleteElementExceptione){configuration.addIncompleteStatement(statementParser);}}}

至此配置mybatis 的配置文件已经解析完成,配置文件已经解析成了Configuration,会到最初,我们的目标是获取 SqlSession 对象,通过new SqlSessionFactoryBuilder().build(reader) 已经构建出了一个SqlSessionFactory 工厂对象,还差一步 SqlSession session = sqlMapper.openSession();

根据 Configuration build 出 DefaultSqlSessionFactory

通过分析DefaultSqlSession 的 openSession() 来实例化 SQLSession 对象

//从session中开启一个数据源privateSqlSessionopenSessionFromDataSource(ExecutorTypeexecType,TransactionIsolationLevellevel,booleanautoCommit){Transactiontx=null;try{//获取环境变量finalEnvironmentenvironment=configuration.getEnvironment();//获取事务工厂finalTransactionFactorytransactionFactory=getTransactionFactoryFromEnvironment(environment);tx=transactionFactory.newTransaction(environment.getDataSource(),level,autoCommit);/***创建一个sql执行器对象*一般情况下若我们的mybaits的全局配置文件的cacheEnabled默认为ture就返回*一个cacheExecutor,若关闭的话返回的就是一个SimpleExecutor*/finalExecutorexecutor=configuration.newExecutor(tx,execType);//创建返回一个DeaultSqlSessoin对象返回returnnewDefaultSqlSession(configuration,executor,autoCommit);}catch(Exceptione){closeTransaction(tx);//mayhavefetchedaconnectionsoletscallclose()throwExceptionFactory.wrapException("Erroropeningsession.Cause:"+e,e);}finally{ErrorContext.instance().reset();}}

仔细一点看你会发现configuration 就是刚才千辛万苦创建出来的 Configuration 对象,包含所有 mybatis 配置信息.至此SQLSession 的创建已分析完毕.

总结

总结一下上述流程:

第一步:通过输入流读取mybatis-config.xml 配置文件

1:通过XPathParser 读取xml 配置文件成 XNode 属性 2:通过 XMLConfigBuilder 解析 mybatis-config.xml 中的各个节点配置,包括

  • 解析properties 节点

  • 解析settings 节点

  • 加载日志框架

  • 解析 typeAliases

  • 解析拓展插架 plugins

  • 解析数据源 DataSource

  • 解析类型处理器 typeHandle

  • 解析 mapper文件

第二步:读取 mapper.java 类

读取方式有 package,resource,url,class ,最终都会放入到 Map<Class<?>, MapperProxyFactory<?>> knownMappers 中

第三步: 读取 mapper.xml 节点

1.同样以输入流的方式读取 mapper.xml 文件 2.通过 XMLMapperBuilder 实例解析 mapper.xml 文件中各个接点属性

  • 解析 namespace 属性

  • 解析缓存引用 cache-ref

  • 解析 cache 节点

  • 解析 resultMap 节点

  • 解析 sql 节点

  • 解析 select | insert |update |delete节点 3.通过 XMLStatementBuilder 解析SQL 标签

第四步:将所有配置属性都封装到 Configuration 对象中,构建出SqlSessionFactory 工厂实例
第五步:从session中开启一个数据源 SqlSessionFactory#openSession(),默认是 DefaultSqlSession
 </div> <div class="zixun-tj-product adv-bottom"></div> </div> </div> <div class="prve-next-news">
本文:Mybatis源码分析之如何理解SQLSession初始化的详细内容,希望对您有所帮助,信息来源于网络。
上一篇:ABAP Netweaver里的寄生式编程语言有哪些下一篇:

3 人围观 / 0 条评论 ↓快速评论↓

(必须)

(必须,保密)

阿狸1 阿狸2 阿狸3 阿狸4 阿狸5 阿狸6 阿狸7 阿狸8 阿狸9 阿狸10 阿狸11 阿狸12 阿狸13 阿狸14 阿狸15 阿狸16 阿狸17 阿狸18