1.mybatisԴ?码分?????鼮????
2.数据库时间慢了14个小时,Mybatis说,析书这个锅我不背!籍作
3.阿里技术官架构使用总结:Spring源码+MyBatis源码+Tomcat架构解析等
4.mybatis插件机制源码解析
5.一文带你理解透MyBatis源码
mybatisԴ?码分?????鼮????
在实际开发过程中,我遇到了mybatis的析书一个问题,觉得很有必要记录下来并分享给大家。籍作asp源码上传主机后安装
这个坑的码分具体情况是这样的:在mybatis中,OgnlOps.equal(0,析书"")返回的是true,这违背了我们的籍作常识,并且会带来一些问题。码分
接下来,析书我将按照遇到问题 -> 分析问题 -> 解决问题的籍作思路,用追踪源码的码分方法,对这个问题进行剖析。析书
同时,籍作我会分享一下我是如何通过逆向排查的方法,通过Debug模式找到关键源码,并解决这个问题的。
本文源码:mybatis 3.5.3版本。
背景介绍和需求分析
为了简化问题,我们假设有一个订单表,表结构如下:
为了方便说明,我们假设表里面只有两条数据:订单号为的订单状态为0(关闭),订单号为的订单状态为1(开启)。
已经开发好的功能是模糊查询订单名称,接口如下:
现在需要在已有功能上添加一个根据状态过滤订单的功能。
假设某个页面有这样的一个下拉框,可以根据订单状态过滤订单数据。
准备开发
现在明确了需求,根据订单状态进行过滤。
很简单,最主要的修改地方就是对mapper.xml的修改。
开始自测,遇到问题
为了确保功能的正确性,我进行了单元测试,分别传入状态0和1,预期的结果是各自查询出一条数据。
然而,执行结果却与预期不符,status=0时查询出2条数据,status=1时查询出1条数据。
当时我意识到这个问题可能并不简单,于是决定分析原因。
分析问题
为了找到问题的根源,我首先将sql打印出来,查看最终执行的sql。
通过分析sql,我发现当status为0时,mybatis并没有给我们拼接where关键字。
逆向排查法
为了定位问题,学点云源码我通过日志找到了关键源码,并使用逆向排查的方法进行追踪。
最终,我发现问题的根源在于mybatis中的OgnlOps.equal(0,"")返回了true。
关键源码
通过分析源码,我找到了导致这个问题的关键代码,并解决了这个问题。
解决问题
为了解决这个问题,我修改了mapper.xml文件中的if标签,最终实现了预期效果。
总结
这次的经历让我深刻认识到,在开发过程中遇到问题时,要善于分析、思考和总结,才能不断提升自己的技能。
数据库时间慢了个小时,Mybatis说,这个锅我不背!
同事反馈一个问题:Mybatis插入数据库的时间是昨天的,是不是因为生成Mybatis逆向工程生成的代码有问题?大家都知道,对于这类Bug本人是很感兴趣的。直觉告诉我,应该不是Mybatis的Bug,很可能是时区的问题。
很好,今天又可以带大家一起来排查Bug了,看看从这次的Bug排查中你能Get什么技能。
这次研究的问题有点深奥,但结论很重要。Let's go!
问题猜想同事反馈问题的时候,带了自己的猜想:是不是数据库字段设置为datetime导致?是不是Mybatis逆向工程生成的代码中类型不一致导致的?
同事还要把datetime改为varchar……马上被我制止了,说:先排查问题,再说解决方案,下午我也抽时间看看。
问题核查第一步,检查数据库字段类型,是datetime的,没问题。
第二步,检查实体类中类型,是java.util.Date类型,没问题。
第三步,Bug复现。
在Bug复现这一步,用到了单元测试。话说之前还跟朋友讨论过单元测试的魅力,现在本人是越来越喜欢单元测试了。
项目基于Spring Boot的clang 源码转换,单元测试如下(代码已脱敏):
@SpringBootTestclassDateTimeTests{ @ResourceprivateUserMapperuserMapper;@TestpublicvoidtestDate(){ Useruser=newUser();//省略其他字段user.setCreateDate(newDate());userMapper.insertSelective(user);}}执行单元测试,查看数据库中插入的数据。Bug复现,时间的确是前一天的,与当前时间相差个小时。
经过上面三步的排查,核实了数据库字段和代码中类型没问题。单元测试也复现了问题,同事没有欺骗我,总要眼见为实,哈哈~
于是基本确定是时区问题。
时区排查检查服务器时间登录测试服务器,执行date命令,检查服务器时间和时区:
[root@xxx~]#date年月日星期四::CST[root@xxx~]#date-RThu,Nov::+显示时间是当前时间,采用CST时间,最后的+,即东8区,没问题。
检查数据库时区连接数据库,执行show命令:
showvariableslike'%time_zone%';+----------------------------+|Variable|Value|+----------------------------+|system_time_zone|CST||time_zone|SYSTEM|system_time_zone:全局参数,系统时区,在MySQL启动时会检查当前系统的时区并根据系统时区设置全局参数system_time_zone的值。值为CST,与系统时间的时区一致。
time_zone:全局参数,设置每个连接会话的时区,默认为SYSTEM,使用全局参数system_time_zone的值。
检查代码中时区在单元测试的方法内再添加打印时区的代码:
@TestpublicvoidtestDate(){ System.out.println(System.getProperty("user.timezone"));Useruser=newUser();//省略其他字段user.setCreateDate(newDate());userMapper.insertSelective(user);}打印的时区为:
Asia/Shanghai也就是说Java中使用的是UTC时区进行业务逻辑处理的,也是东八区的时间。
那么问题到底出在哪里呢?
问题基本呈现经过上述排查,基本上确定是时区的问题。这里,再补充一下上述相关的时区知识点。
UTC时间UTC时间:世界协调时间(UTC)是世界上不同国家用来调节时钟和时间的主要时间标准,也就是零时区的时间。
UTC, Coordinated Universal Time是一个标准,而不是一个时区。UTC 是一个全球通用的时间标准。全球各地都同意将各自的时间进行同步协调 (coordinated),这也是UTC名字的来源:Universal Coordinated Time。
CST时间CST时间:中央标准时间。
CST可以代表如下4个不同的时区:
Central Standard Time (USA) UT-6:,美国
Central Standard Time (Australia) UT+9:,澳大利亚
China Standard Time UT+8:,中国
Cuba Standard Time UT-4:,古巴
再次分析很显然,这里与UTC时间无关,它只是梦幻都市源码时间标准。目前Mysql中的system_time_zone是CST,而CST可以代表4个不同的时区,那么,Mysql把它当做哪个时区进行处理了呢?
简单推算一下,中国时间是UT+8:,美国是 UT-6:,当传入中国时间,直接转换为美国时间(未考虑时区问题),时间便慢了个小时。
既然知道了问题,那么解决方案也就有了。
解决方案针对上述问题可通过数据库层面和代码层面进行解决。
方案一:修改数据库时区既然是Mysql理解错了CST指定的时区,那么就将其设置为正确的。
连接Mysql数据库,设置正确的时区:
[root@xxxxx~]#mysql-uroot-pmysql>setglobaltime_zone='+8:';mysql>settime_zone='+8:'mysql>flushprivileges;再次执行show命令:
showvariableslike'%time_zone%';+----------------------------+|Variable|Value|+----------------------------+|system_time_zone|CST||time_zone|+:|可以看到时区已经成为东八区的时间了。再次执行单元测试,问题得到解决。
此种方案也可以直接修改MySQL的my.cnf文件进行指定时区。
方案二:修改数据库连接参数在代码连接数据库时,通过参数指定所使用的时区。
在配置数据库连接的URL后面添加上指定的时区serverTimezone=Asia/Shanghai:
url:jdbc:MySQL://xx.xx.xx.xx:/db_name?useUnicode=true&characterEncoding=utf8&autoReconnect=true&serverTimezone=Asia/Shanghai再次执行单元测试,问题同样可以得到解决。
问题完了?经过上述分析与操作,时区的问题已经解决了。问题就这么完事了吗?为什么是这样呢?
为了验证时区问题,在时区错误的数据库中,创建了一个字段,该字段类型为datetime,默认值为CURRENT_TIMESTAMP。
那么,此时插入一条记录,让Mysql自动生成该字段的时间,你猜该字段的时间是什么?中国时间。
神奇不?为什么同样是CST时区,系统自动生成的时间是正确的,而代码插入的时间就有时差问题呢?
到底是Mysql将CST时区理解为美国时间了,还是Mybatis、连接池或驱动程序将其理解为美国时间了?
重头戏开始为了追查到底是代码中哪里出了问题,先开启Mybatis的debug日志,看看insert时是什么值:
--::.[||]DEBUG---[scheduling-1]c.h.s.m.H.listByCondition:==>Parameters:--::(String),0(Integer),1(Integer),2(Integer),3(Integer),4(Integer)上面是insert时的参数,也就是说在Mybatis层面时间是没问题的。排除一个。
那是不是连接池或驱动程序的问题?连接池本身来讲跟数据库连接的具体操作关系不大,就直接来排查驱动程序。
Mybatis是xml中定义日期字段类型为TIMESTAMP,扒了一下mysql-connector-Java-8.0.x的源码,发现SqlTimestampValueFactory是用来处理TIMESTAMP类型的。
在SqlTimestampValueFactory的koa源码路由构造方法上打上断点,执行单元测试:
可以明确的看到,Calendar将时区设置为Locale.US,也就是美国时间,时区为CST,offset为-。-单位为毫秒,转化为小时,恰好是“-6:”,这与北京时间“GMT+:”恰好相差个小时。
于是一路往上最终追溯调用链路,该TimeZone来自NativeServerSession的serverTimeZone,而serverTimeZone的值是由NativeProtocol类的configureTimezone方法设置的。
publicvoidconfigureTimezone(){ StringconfiguredTimeZoneOnServer=this.serverSession.getServerVariable("time_zone");if("SYSTEM".equalsIgnoreCase(configuredTimeZoneOnServer)){ configuredTimeZoneOnServer=this.serverSession.getServerVariable("system_time_zone");}StringcanonicalTimezone=getPropertySet().getStringProperty(PropertyKey.serverTimezone).getValue();if(configuredTimeZoneOnServer!=null){ //usercanoverridethiswithdriverproperties,sodon'tdetectifthat'sthecaseif(canonicalTimezone==null||StringUtils.isEmptyOrWhitespaceOnly(canonicalTimezone)){ try{ canonicalTimezone=TimeUtil.getCanonicalTimezone(configuredTimeZoneOnServer,getExceptionInterceptor());}catch(IllegalArgumentExceptioniae){ throwExceptionFactory.createException(WrongArgumentException.class,iae.getMessage(),getExceptionInterceptor());}}}if(canonicalTimezone!=null&&canonicalTimezone.length()>0){ //此处设置TimeZonethis.serverSession.setServerTimeZone(TimeZone.getTimeZone(canonicalTimezone));if(!canonicalTimezone.equalsIgnoreCase("GMT")&&this.serverSession.getServerTimeZone().getID().equals("GMT")){ throwExceptionFactory.createException(WrongArgumentException.class,Messages.getString("Connection.9",newObject[]{ canonicalTimezone}),getExceptionInterceptor());}}}debug跟踪一下上述代码,显示信息如下:
至此,通过canonicalTimezone值的获取,可以看出URL后面配置serverTimezone=Asia/Shanghai的作用了。其中,上面第一个代码块获取time_zone的值,第二个代码块中获取system_time_zone的值。这与查询数据库获得的值一致。
因为出问题时并未在url中添加参数serverTimezone=Asia/Shanghai,所以走canonicalTimezone为null的情况。随后逻辑中调用了TimeUtil.getCanonicalTimezone方法:
[root@xxx~]#date年月日星期四::CST[root@xxx~]#date-RThu,Nov::+上述代码中最终走到了loadTimeZoneMappings(exceptionInterceptor);方法:
[root@xxx~]#date年月日星期四::CST[root@xxx~]#date-RThu,Nov::+该方法加载了配置文件"/com/mysql/cj/util/TimeZoneMapping.properties"里面的值,经过转换,timeZoneMappings中,对应CST的为"CST"。
最终得到canonicalTimezone为“CST”,而TimeZone获得是通过TimeZone.getTimeZone(canonicalTimezone)方法获得的。
也就是说TimeZone.getTimeZone("CST")的值为美国时间。写个单元测试验证一下:
[root@xxx~]#date年月日星期四::CST[root@xxx~]#date-RThu,Nov::+打印结果:
[root@xxx~]#date年月日星期四::CST[root@xxx~]#date-RThu,Nov::+很显然,该方法传入CST之后,默认是美国时间。
至此,问题原因基本明朗:
Mysql中设置的server_time_zone为CST,time_zone为SYSTEM。
Mysql驱动查询到time_zone为SYSTEM,于是使用server_time_zone的值,为”CST“ 。
JDK中TimeZone.getTimeZone("CST")获得的值为美国时区;
以美国时区构造的Calendar类;
SqlTimestampValueFactory使用上述Calendar来格式化系统获取的中国时间,时差问题便出现了;
最终反映在数据库数据上就是错误的时间。
serverVariables变量再延伸一下,其中server_time_zone和time_zone都来自于NativeServerSession的serverVariables变量,该变量在NativeSession的loadServerVariables方法中进行初始化,关键代码:
[root@xxx~]#date年月日星期四::CST[root@xxx~]#date-RThu,Nov::+在上述StringBuilder的append操作中,有"@@time_zone AS time_zone"和"@@system_time_zone AS system_time_zone"两个值,然后查询数据库,从数据库获得值之后,put到serverVariables中。
再来debug一下:
可以看出system_time_zone的值为CST。
同样time_zone的值为“SYSTEM”。
根据代码中的提示,拼接与代码一样的SQL查询一下数据库:
[root@xxx~]#date年月日星期四::CST[root@xxx~]#date-RThu,Nov::+值的确是“SYSTEM”。此时,我们又得出另外一个查询Mysql当前时区的方法。
至此,该问题的排查完美收官。大出一口气~~~
小结在上述问题排查的过程中,多次用到单元测试,这也是单元测试的魅力所在,用最简单的代码,最轻量的逻辑,最节省时间的方式来验证和追踪错误。
再回顾一下上述Bug排查中用到和学到的知识点:
Linux日期查看,时区查看及衍生如何配置时区;
Mysql时区查看;
Spring Boot单元测试;
Java时区获取;
UTC时间和CST时间;
两种解决时区问题的方案;
阅读、debug Mysql驱动源代码;
TimeZone.getTimeZone("CST")默认时区为美国时区;
Mysql驱动中处理时区问题基本流程逻辑;
Mybatis debug日志相关打印;
其他相关知识。
通过本篇Bug查找的文章,你学到了什么?如果有那么一点启发,不要吝啬,给点个赞吧!
博主简介:《SpringBoot技术内幕》技术图书作者,酷爱钻研技术,写技术干货文章。
公众号:「程序新视界」,博主的公众号,欢迎关注~
技术交流:请联系博主微信号:zhuan2quan
阿里技术官架构使用总结:Spring源码+MyBatis源码+Tomcat架构解析等
分享Java技术文以及学习经验也有一段时间了,实际作为程序员,我们都清楚学习的重要性,毕竟时代在发展,互联网之下,稍有落后可能就会被淘汰掉,因此我们需要不断审视自己,通过学习来提升自己。
对于大多数程序员而言,阿里一直是目标,但进入大厂工作并非易事。今日,由阿里一线P8架构师揭秘,对其使用的技术进行总结,此PDF总结主要涉及Spring源码、MyBatis源码以及Tomcat架构解析等,以期帮助大家提升。
如果你需要PDF版本,可直接点击下方链接免费获取。
第一部分:Spring源码深度解析
一、核心实现
二、企业应用
第二部分:MyBatis源码解析
一、MyBatis入门
二、配置文件解析过程
三、映射文件解析过程
四、SQL执行流程
五、内置数据源
六、缓存机制
七、插件机制
第三部分:Tomcat架构解析
一、Tomcat介绍
二、Tomcat总体架构
三、Catalina
四、Coyote
五、Jasper
六、Tomcat配置管理
七、Web服务器集成
八、Tomcat集群
九、Tomcat安全
十、Tomcat性能调优
十一、Tomcat附加功能
总结:
作为Java程序员,务必不断充实自己的知识储备,关于阿里等一线大厂所使用的技术,应心中有数。
最后,提醒一句,所学知识均为己有,如果你需要这些架构技术使用总结,我愿意免费分享,有兴趣的老铁请点击下方链接免费领取。若支持我这篇文章,不妨点赞+喜欢+收藏一键三连,谢谢!
mybatis插件机制源码解析
引言
本篇源码解析基于MyBatis3.5.8版本。
首先需要说明的是,本篇文章不是mybatis插件开发的教程,而是从源码层面分析mybatis是如何支持用户自定义插件开发的。
mybatis的插件机制,让其扩展能力大大增加。比如我们项目中经常用到的PageHelper,这就是一款基于mybatis插件能力开发的产品,它的功能是让基于mybatis的数据库分页查询更容易使用。
当然基于插件我们还可以开发其它功能,比如在执行sql前打印日志、做权限控制等。
正文mybatis插件也叫mybatis拦截器,它支持从方法级别对mybatis进行拦截。整体架构图如下:
解释下几个相关概念:
Interceptor拦截器接口,用户自定义的拦截器就是实现该接口。
InterceptorChain拦截器链,其内部维护一个interceptorslist,表示拦截器链中所有的拦截器,并提供增加或获取拦截器链的方法。比如有个核心的方法是pluginAll。该方法用来生成代理对象。
Invocation拦截器执行时的上下文环境,其实就是目标方法的调用信息,包含目标对象、调用的方法信息、参数信息。核心方法是proceed。该方法的主要目的就是进行处理链的传播,执行完拦截器的方法后,最终需要调用目标方法的invoke方法。
mybatis支持在哪些地方进行拦截呢?你只需要在代码里搜索interceptorChain.pluginAll的使用位置就可以获取答案,一共有四处:
parameterHandler=(ParameterHandler)interceptorChain.pluginAll(parameterHandler);resultSetHandler=(ResultSetHandler)interceptorChain.pluginAll(resultSetHandler);statementHandler=(StatementHandler)interceptorChain.pluginAll(statementHandler);executor=(Executor)interceptorChain.pluginAll(executor);这四处实现的原理都是一样的,我们只需要选择一个进行分析就可以了。
我们先来看下自定义的插件是如何加载进来的,比如我们使用PageHelper插件,通常会在mybatis-config.xml中加入如下的配置:
<plugins><plugininterceptor="com.github.pagehelper.PageInterceptor"><!--configparamsasthefollowing--><propertyname="param1"value="value1"/></plugin></plugins>mybatis在创建SqlSessionFactory的时候会加载配置文件,
publicConfigurationparse(){ if(parsed){ thrownewBuilderException("EachXMLConfigBuildercanonlybeusedonce.");}parsed=true;parseConfiguration(parser.evalNode("/configuration"));returnconfiguration;}parseConfiguration方法会加载包括plugins在内的很多配置,
privatevoidparseConfiguration(XNoderoot){ try{ ...pluginElement(root.evalNode("plugins"));...}catch(Exceptione){ thrownewBuilderException("ErrorparsingSQLMapperConfiguration.Cause:"+e,e);}}privatevoidpluginElement(XNodeparent)throwsException{ if(parent!=null){ for(XNodechild:parent.getChildren()){ Stringinterceptor=child.getStringAttribute("interceptor");Propertiesproperties=child.getChildrenAsProperties();InterceptorinterceptorInstance=(Interceptor)resolveClass(interceptor).getDeclaredConstructor().newInstance();interceptorInstance.setProperties(properties);configuration.addInterceptor(interceptorInstance);}}}pluginElement干了几件事情:
创建Interceptor实例
设置实例的属性变量
添加到Configuration的interceptorChain拦截器链中
mybatis的插件是通过动态代理实现的,那肯定要生成代理对象,生成的逻辑就是前面提到的pluginAll方法,比如对于Executor生成代理对象就是,
executor=(Executor)interceptorChain.pluginAll(executor);接着看pluginAll方法,
/***该方法会遍历用户定义的插件实现类(Interceptor),并调用Interceptor的plugin方法,对target进行插件化处理,*即我们在实现自定义的Interceptor方法时,在plugin中需要根据自己的逻辑,对目标对象进行包装(代理),创建代理对象,*那我们就可以在该方法中使用Plugin#wrap来创建代理类。*/publicObjectpluginAll(Objecttarget){ for(Interceptorinterceptor:interceptors){ target=interceptor.plugin(target);}returntarget;}这里遍历所有我们定义的拦截器,调用拦截器的plugin方法生成代理对象。有人可能有疑问:如果有多个拦截器,target不是被覆盖了吗?
其实不会,所以如果有多个拦截器的话,生成的代理对象会被另一个代理对象代理,从而形成一个代理链条,执行的时候,依次执行所有拦截器的拦截逻辑代码。
plugin方法是接口Interceptor的默认实现类,
defaultObjectplugin(Objecttarget){ returnPlugin.wrap(target,this);}然后进入org.apache.ibatis.plugin.Plugin#wrap,
publicstaticObjectwrap(Objecttarget,Interceptorinterceptor){ Map<Class<?>,Set<Method>>signatureMap=getSignatureMap(interceptor);Class<?>type=target.getClass();Class<?>[]interfaces=getAllInterfaces(type,signatureMap);if(interfaces.length>0){ returnProxy.newProxyInstance(type.getClassLoader(),interfaces,newPlugin(target,interceptor,signatureMap));}returntarget;}首先是获取我们自己实现的Interceptor的方法签名映射表。然后获取需要代理的对象的Class上声明的所有接口。比如如果我们wrap的是Executor,就是Executor的所有接口。然后就是最关键的一步,用Proxy类创建一个代理对象(newProxyInstance)。
注意,newProxyInstance方法的第三个参数,接收的是一个InvocationHandler对象,表示的是当动态代理对象调用方法的时候会关联到哪一个InvocationHandler对象上,并最终由其调用。
我们这里传入的是Plugin类,故在动态运行过程中会执行Plugin的invoker方法。
如果对这一段不是很理解,建议先了解下java动态代理的原理。java动态代理机制中有两个重要的角色:InvocationHandler(接口)和Proxy(类),这个是背景知识需要掌握的。
我们在深入看下上面的getSignatureMap方法,
privatestaticMap<Class<?>,Set<Method>>getSignatureMap(Interceptorinterceptor){ //从Interceptor的类上获取Intercepts注解,说明我们自定义拦截器需要带注解InterceptsinterceptsAnnotation=interceptor.getClass().getAnnotation(Intercepts.class);//issue#if(interceptsAnnotation==null){ thrownewPluginException("No@Interceptsannotationwasfoundininterceptor"+interceptor.getClass().getName());}Signature[]sigs=interceptsAnnotation.value();Map<Class<?>,Set<Method>>signatureMap=newHashMap<>();//解析Interceptor的values属性(Signature[])数组,存入HashMap,Set<Method>>for(Signaturesig:sigs){ Set<Method>methods=MapUtil.computeIfAbsent(signatureMap,sig.type(),k->newHashSet<>());try{ Methodmethod=sig.type().getMethod(sig.method(),sig.args());methods.add(method);}catch(NoSuchMethodExceptione){ thrownewPluginException("Couldnotfindmethodon"+sig.type()+"named"+sig.method()+".Cause:"+e,e);}}returnsignatureMap;}首先需要从Interceptor的类上获取Intercepts注解,说明我们自定义拦截器需要带注解,比如PageHelper插件的定义如下:
<plugins><plugininterceptor="com.github.pagehelper.PageInterceptor"><!--configparamsasthefollowing--><propertyname="param1"value="value1"/></plugin></plugins>0所以我们可以知道,getSignatureMap其实就是拿到我们自定义拦截器声明需要拦截的类以及类对应的方法。
前面说过,当我们调用代理对象时,最终会执行Plugin类的invoker方法,我们看下Plugin的invoker方法,
<plugins><plugininterceptor="com.github.pagehelper.PageInterceptor"><!--configparamsasthefollowing--><propertyname="param1"value="value1"/></plugin></plugins>1Interceptor接口的intercept方法就是我们自定义拦截器需要实现的逻辑,其参数为Invocation,可从Invocation参数中拿到执行方法的对象,方法,方法参数,比如我们可以从statementHandler拿到SQL语句,实现自己的特殊逻辑。
在该方法的结束需要调用invocation#proceed()方法,进行拦截器链的传播。
参考:
blogs.com/chenpi/p/.html
一文带你理解透MyBatis源码
本文分享自华为云社区《一文彻底吃透MyBatis源码!!》,作者:冰 河。
随着互联网的发展,MyBatis逐渐成为Java开发人员必备的框架技术,尤其在大厂面试中常被提及。今天,我们深入剖析MyBatis源码,带你全面理解其底层原理。文章内容丰富,建议先收藏后仔细研究。
MyBatis源码解析,是对JDBC的进一步封装,其核心流程包含获取链接、PreparedStatement、参数封装、SQL执行等步骤。
配置解析从Resources.getResourceAsStream(resource)开始,通过ClassLoader获取指定classpath路径下的Resource。
配置解析过程包括SqlSessionFactoryBuilder创建SqlSessionFactory,以及parser.parse()解析configuration.xml文件,获取Environment、Setting等信息,将所有配置添加到Configuration,作为配置中心。
解析Mapper映射器,通过mapperParser.parse()将namespace(接口类型)与工厂类绑定,生成SqlSessionFactory。
SqlSessionFactory创建过程中,将Configuration作为参数,使用DefaultSqlSessionFactory生成实例。
SqlSession会话创建,mybatis操作数据库时,每次连接都需要创建会话,通过openSession()方法实现,会话内包含执行SQL的Executor,执行器类型和事务类型需要指定。
事务管理实现有两种方式,创建Transaction,生成Executor,获取Mapper对象,通过mapperRegistry.getMapper从knownMappers中取接口类型和工厂类,返回代理对象MapperProxy。
执行SQL时,通过代理对象MapperProxy的invoke()方法调用execute方法,实现查询操作,使用的是selectList方法,无论查询一个或多个。
执行query方法时,创建CacheKey,从BoundSql中获取SQL信息,用于缓存查询结果。最后,从数据库查询并执行doQuery源码,总结了MyBatis源码的整体流程,较为简洁,通过细致研究,可以深入理解框架的核心机制。