1.MyBatis 源用源码解析:映射文件的加载与解析(上)
2.Mybatis源码剖析(懒加载原理)
3.从源码角度分析 Mybatis 工作原理
4.mybatisçä¼ç¹
5.mybatis插件机制源码解析
6.MyBatis-Plus: 谨慎入坑
MyBatis 源码解析:映射文件的加载与解析(上)
MyBatis 的映射文件是其核心组成部分,用于配置 SQL 语句、码好二级缓存及结果集映射等功能,源用是码好其区别于其他 ORM 框架的重要特色。 在解析映射文件时,源用MyBatis 码好shutil.copy源码通过调用 XMLMapperBuilder#parse 方法实现加载与解析操作。此方法首先判断映射文件是源用否已解析,若未解析则调用 XMLMapperBuilder#configurationElement 方法解析所有配置,码好并注册当前映射文件关联的源用 Mapper 接口。对于处理异常的码好标签,MyBatis 源用会记录至 Configuration 对象并尝试二次解析。 解析流程主要涉及以下几个关键步骤:缓存配置(cache 标签):MyBatis 码好采用缓存设计,分为一级缓存和二级缓存。源用解析 cache 标签时,码好首先获取相关属性配置,源用然后使用 CacheBuilder 创建缓存对象,并记录到 Configuration 对象。
缓存引用(cache-ref 标签):标签默认限定在 namespace 范围内,用于引用其它命名空间中的缓存对象。解析过程中记录引用关系,然后从 Configuration 中获取引用的缓存对象。
结果集映射(resultMap 标签):解析 resultMap 标签配置,构建 ResultMap 对象,并将其记录到 Configuration 中。
SQL 语句(sql 标签):通过 sql 标签配置复用的 SQL 语句片段,解析后记录至 Configuration 的 sqlFragments 属性中。
核心数据库操作(select / insert / update / delete 标签):解析这些标签时,构建 MappedStatement 对象并记录到 Configuration 中。
每个标签解析实现由 MyBatis 提供的多个方法执行,如 XMLMapperBuilder 的 configurationElement 方法和解析具体标签的子方法,如 cacheElement、sqlElement 等。解析过程中,MyBatis 会调用不同的构造器和工厂方法来创建、初始化和配置相应的对象。 在解析完成之后,MyBatis 将所有配置对象封装在 Configuration 对象中,该对象包含所有映射文件中定义的配置信息,供后续的 SQL 语句执行和映射操作使用。Mybatis源码剖析(懒加载原理)
懒加载,即按需加载,robotium源码旨在优化查询性能。以一个包含订单列表的User对象为例,当仅获取用户信息时,若启用懒加载模式,执行SQL不会查询订单列表。需获取订单列表时,才会发起数据库查询。实现方式包括在核心配置文件中设置或在相关映射文件中通过fetchType属性配置懒加载策略。
懒加载的配置如何加载到项目中呢?首先,这些配置保存在全局Configuration对象中,通常在解析核心配置文件的代码中实现。在settingsElement方法中,懒加载配置被保存在lazyLoadingEnabled属性中。对于resultMap标签中collection | association的fetchType属性,其配置通过解析mappers标签下的resultMap标签实现,最终调用buildResultMappingFromContext方法处理子标签。该方法结合全局配置判断是否需要执行懒加载。
懒加载的实现原理涉及动态代理。当调用代理对象的延迟加载属性方法时,如访问a.getB().getName(),代理对象会调用拦截器方法。若发现需要延迟加载,代理对象会单独发送SQL查询关联对象,加载数据后设置属性值,完成方法调用。简而言之,懒加载通过动态代理实现,拦截指定方法并执行数据加载。
深入剖析懒加载源码,会发现它涉及查询和数据处理的多步操作。查询完成后,结果集处理、列值获取、判断是否进行懒加载等步骤共同构建懒加载机制。动态代理在访问对象属性时触发,最终通过Javassist库创建代理对象,实现懒加载逻辑。当访问如userList2.get(0).getOrderList()时,若满足条件,代理对象会调用懒加载查询方法获取数据。pppd 源码判断懒加载条件的关键在于结果集处理阶段,通过访问映射关系和查询映射值来确定是否执行后续懒加载查询。
综上所述,Mybatis的懒加载机制通过动态代理和结果集处理实现,旨在优化性能,按需加载数据,提高查询效率。通过核心配置和映射文件中的配置,懒加载逻辑被加载到项目中,为开发者提供灵活的加载策略。
从源码角度分析 Mybatis 工作原理
本文以入门级示例说明 MyBatis 工作原理,涵盖数据库准备、添加 MyBatis、配置、Mapper、测试程序、生命周期、映射器、架构、SqlSession 机制等内容。通过源码解析详细展现 MyBatis 如何将 Java 代码与数据库操作紧密结合。
数据库准备:针对用户表进行 CRUD 操作,设计数据模型。
添加 MyBatis:Maven 依赖配置,引入 MyBatis 依赖。
MyBatis 配置:XML 配置文件设置数据源、事务管理器。
Mapper:包含 Mapper.xml 和 Mapper.java 文件,实现 SQL 模板与 Java 对象绑定。
测试程序:MyBatisDemo.java 文件,展示如何使用 SqlSession 执行操作。
MyBatis 生命周期:SqlSessionFactoryBuilder、SqlSessionFactory、SqlSession 的创建、使用与释放。
映射器:接口定义与动态代理生成,完成 SQL 与 Java 方法的映射。
架构:配置层、接口层、数据处理层、weiphp源码框架支撑层,展现 MyBatis 体系结构。
SqlSession 内部工作机制:解析 SQL、管理缓存、执行事务、处理结果集。
总结:MyBatis 通过封装、映射、执行等机制,简化了 Java 与数据库的交互过程,实现数据操作的便捷与高效。
mybatisçä¼ç¹
ãã1.ä¼ç¹
ããç®åï¼
ããæäºå¦ä¹ ï¼æäºä½¿ç¨ï¼éè¿ææ¡£åæºä»£ç ï¼å¯ä»¥æ¯è¾å®å ¨çææ¡å®ç设计æè·¯åå®ç°ã
ããå®ç¨ï¼
ããæä¾äºæ°æ®æ å°åè½ï¼æä¾äºå¯¹åºå±æ°æ®è®¿é®çå°è£ ï¼ä¾å¦ado.netï¼ï¼æä¾äºDAOæ¡æ¶ï¼å¯ä»¥ä½¿æ们æ´å®¹æçå¼ååé ç½®æ们çDALå±ã
ããçµæ´»ï¼
ããéè¿sqlåºæ¬ä¸å¯ä»¥å®ç°æ们ä¸ä½¿ç¨æ°æ®è®¿é®æ¡æ¶å¯ä»¥å®ç°çææåè½ï¼æ许æ´å¤ã
ããåè½å®æ´ï¼
ããæä¾äºè¿æ¥ç®¡çï¼ç¼åæ¯æï¼çº¿ç¨æ¯æï¼ï¼åå¸å¼ï¼äºç©ç®¡çï¼éè¿é ç½®ä½å ³ç³»å¯¹è±¡æ å°çæ°æ®è®¿é®å±éè¦è§£å³çé®é¢ãæä¾äºDAOæ¯æï¼å¹¶å¨DAOæ¡æ¶ä¸å°è£ äºADO.NETï¼NHibernateåDataMapperã
ããå¢å¼ºç³»ç»çå¯ç»´æ¤æ§ï¼
ããéè¿æä¾DALå±ï¼å°ä¸å¡é»è¾åæ°æ®è®¿é®é»è¾å离ï¼ä½¿ç³»ç»ç设计æ´æ¸ æ°ï¼æ´æç»´æ¤ï¼æ´æåå æµè¯ãsqlå代ç çå离ï¼æé«äºå¯ç»´æ¤æ§ã
ãã2.缺ç¹
ããæ»åæ§ï¼
ããè¿æ²¡ææ确对.NET2.0çæ¯æãææ°çæ¬å¨2.0ä¸ç¼è¯å¯ä»¥ï¼ä½æäºåå æµè¯ä¸è½éè¿ã
ããä¸æçï¼å·¥ç¨å®è·µè¾å°ï¼
ããIbatisNetå¨å®é 项ç®ä¸ç使ç¨è¾å°ã åªæ¯ç论ä¸å¯è¡.
ããåORMï¼å·¥å ·æ¯æè¾å°ï¼
ããéè¦æ们èªå·±åsqlï¼å¹¶ä¸.NETä¸è¿æªåç°å¯ä»¥èªå¨çæä¸å¡å±ç±»åé ç½®æ件çå·¥å ·ï¼è¿ç¹åNHibernateä¸ä¸æ ·ï¼NHibernateä¼ä¸ºæ们çæ°æ®åºç´æ¥äº§çsqlï¼å¹¶æä¸äºè¾ å©å·¥å ·ãå æ¤ä½¿ç¨Ibatisæ¯NHibernateè¦å¤åä¸äºå·¥ä½ã
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-Plus: 谨慎入坑
在尝试使用Spring-Boot、MyBatis与MyBatis-Plus(简称MP)进行后端开发的项目中,我遇到了不少问题,以下是对其中几个关键问题的总结,旨在为后来者提供参考。
首先,我对于MyBatis-Plus的文档评价为“垃圾”。官方文档既不详尽也不及时,甚至鼓励用户直接阅读源代码以解决问题。文档中对于“条件构造器”的解释模糊不清,仅仅罗列了函数列表和参数说明,并没有深入解释设计思想和功能用途,这导致用户在初次接触时难以理解其作用。以第一个boolean condition为例,文档提到其用途模糊,实际上它用于在执行查询时动态添加条件,避免了多次if语句的繁琐编写,简化了代码。然而,文档中对于这类常见且重要的功能解释不足,使得用户难以快速上手。
其次,MP的架构设计存在强制性,其推崇的架构思想在特定场景下有效,但在大多数业务开发场景中并不适用。例如,它建议避免表间外键、简化业务逻辑以减少数据库负担。然而,对于数据结构复杂、业务流程复杂的项目来说,这种简化可能导致数据不一致的问题,反而需要更复杂的数据处理逻辑。MP在设计上倾向于将关系数据库作为NoSQL数据库使用,忽略了关系数据库的精髓,如确保数据一致性等重要功能。对于违背这种设计前提的情况,MP的分页插件可能无法正确返回结果,如在存在left join的表中获取额外信息时,可能导致数量计算错误。
MP在一些设计细节上还需完善,比如QueryWrapper接口中直接使用字符串作为key,允许直接设置SQL语句作为条件,这在API层面混用不同的设计,降低了IDE的代码检查能力,增加了出错的风险。此外,乐观锁插件只支持在Update时校验Version,不支持删除时校验Version,这种功能上的局限性在一定程度上限制了其应用范围。
综上所述,MyBatis-Plus在某些方面表现出了局限性和设计上的不足,但也有其优势和适用场景。对于数据架构与MP设计思想一致的项目,MP可以提供一些便利。然而,对于复杂业务场景,建议考虑使用MyBatis Generator简化单表操作,或者选用JPA(或Spring Data JPA)等更灵活的框架。
很开心,在使用mybatis的过程中我踩到一个坑。
在实际开发过程中,我遇到了mybatis的一个问题,觉得很有必要记录下来并分享给大家。
这个坑的具体情况是这样的:在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标签,最终实现了预期效果。
总结
这次的经历让我深刻认识到,在开发过程中遇到问题时,要善于分析、思考和总结,才能不断提升自己的技能。
拿到美团、快手offer后收到阿里三面通知,竟然被mybatis挡住了去路
一位求职者在年底之际收获了美团、快手等公司的录用通知,面对阿里三面的邀请,他抱有期待。然而,面试过程却出乎意料地受挫,主要原因是面试官针对MyBatis源码提出了深入的问题。其中包括设计模式的应用、调试模式的实现机制、数据库连接池操作、二级缓存功能的实现以及源码中涉及的术语如“缓存雪崩”。
面试官热衷于考察源码理解,因为这不仅能测试技术深度,还能展现开发者的思维方式和问题解决能力。正如淘宝创始人团队成员多隆,他凭借对源码的深入研究,不仅提升了工作效率,还帮助同事解决难题。掌握源码对于程序员意味着编写高效代码的经验、微观架构设计的提升、工作中的疑难杂症解决,以及学习大牛的思维模式。
然而,学习MyBatis源码并非易事,市面上的教材要么难以理解,要么过于专业。但好消息是,有一份详尽的学习指南,包含了学习文档、视频讲解和思维导图,从入门到精通,涵盖了高级用法和设计模式。对于有需要的朋友,可以通过此资源进行高效学习,点击此处获取:( 点这里)免费领取,我们非常诚挚地邀请您的支持!
此外,对于Java技术、面试问题和架构实战内容,我们也有更多精选资源:Java面试题和实战文档,欢迎感兴趣的朋友点击左下角支持我们。