【cs源码怎么运行】【automapper源码】【放歌源码】mybatis if 源码解析

1.mybatis 码解if Դ?????
2.从源码角度分析 Mybatis 工作原理
3.MyBatis 源码解析:映射文件的加载与解析(上)
4.一文搞懂Mybatis插件原理
5.mybatis插件机制源码解析

mybatis if 源码解析

mybatis if Դ?????

       在实际开发过程中,我遇到了mybatis的码解一个问题,觉得很有必要记录下来并分享给大家。码解

       这个坑的码解具体情况是这样的:在mybatis中,OgnlOps.equal(0,码解"")返回的是true,这违背了我们的码解cs源码怎么运行常识,并且会带来一些问题。码解

       接下来,码解我将按照遇到问题 -> 分析问题 -> 解决问题的码解思路,用追踪源码的码解方法,对这个问题进行剖析。码解

       同时,码解我会分享一下我是码解如何通过逆向排查的方法,通过Debug模式找到关键源码,码解并解决这个问题的码解。

       本文源码:mybatis 3.5.3版本。

       背景介绍和需求分析

       为了简化问题,我们假设有一个订单表,表结构如下:

       为了方便说明,我们假设表里面只有两条数据:订单号为的订单状态为0(关闭),订单号为的订单状态为1(开启)。

       已经开发好的功能是模糊查询订单名称,接口如下:

       现在需要在已有功能上添加一个根据状态过滤订单的功能。

       假设某个页面有这样的一个下拉框,可以根据订单状态过滤订单数据。

       准备开发

       现在明确了需求,根据订单状态进行过滤。

       很简单,最主要的automapper源码修改地方就是对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、配置、Mapper、测试程序、生命周期、映射器、架构、SqlSession 机制等内容。通过源码解析详细展现 MyBatis 如何将 Java 代码与数据库操作紧密结合。

       数据库准备:针对用户表进行 CRUD 操作,设计数据模型。

       添加 MyBatis:Maven 依赖配置,引入 MyBatis 依赖。

       MyBatis 配置:XML 配置文件设置数据源、事务管理器。

       Mapper:包含 Mapper.xml 和 Mapper.java 文件,实现 SQL 模板与 Java 对象绑定。

       测试程序:MyBatisDemo.java 文件,展示如何使用 SqlSession 执行操作。

       MyBatis 生命周期:SqlSessionFactoryBuilder、SqlSessionFactory、-1.0源码SqlSession 的创建、使用与释放。

       映射器:接口定义与动态代理生成,完成 SQL 与 Java 方法的映射。

       架构:配置层、接口层、数据处理层、框架支撑层,展现 MyBatis 体系结构。

       SqlSession 内部工作机制:解析 SQL、管理缓存、执行事务、处理结果集。

       总结:MyBatis 通过封装、映射、执行等机制,简化了 Java 与数据库的交互过程,实现数据操作的便捷与高效。

MyBatis 源码解析:映射文件的加载与解析(上)

       MyBatis 的映射文件是其核心组成部分,用于配置 SQL 语句、二级缓存及结果集映射等功能,是其区别于其他 ORM 框架的重要特色。

       在解析映射文件时,MyBatis 通过调用 XMLMapperBuilder#parse 方法实现加载与解析操作。此方法首先判断映射文件是否已解析,若未解析则调用 XMLMapperBuilder#configurationElement 方法解析所有配置,并注册当前映射文件关联的 Mapper 接口。对于处理异常的strerror源码标签,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插件原理

       Mybatis插件原理详解,让你轻松实现自定义功能

       在深入Mybatis源码时,你可能会遇到名为"plugin"的包,这实际上用于扩展框架的功能,如PageHelper分页插件。本文将揭示插件的运作机制,教你如何理解其内部工作并自行实现,如分页或慢SQL统计。

       使用PageHelper插件分页的简单步骤是:一,在pom.xml中添加依赖;二,在mybatis-config.xml中配置插件;三,在需要分页的mapper接口前调用startPage方法,并确保每次查询后关闭Page对象以避免数据冲突。

       接下来,我们将探索插件的总体流程:首先,通过拦截器接口实现动态代理,如PageInterceptor,它会在Executor的query方法执行前后添加自定义逻辑。拦截器通过Invocation对象获取目标对象和方法参数,实现增强功能。

       分页拦截器如PageInterceptor拦截query方法,若需要分页,会获取原始SQL并添加limit语句。配置插件时,在mybatis-config.xml中添加相应拦截器,如``标签和Interceptor实现类的全限定名。

       当我们创建SqlSession时,拦截器通过Configuration的newExecutor方法注册并应用到Executor对象上,利用JDK动态代理生成增强的代理对象,从而在执行SQL时调用拦截器的intercept方法。

       自定义慢SQL统计插件同样基于Interceptor,只需配置目标对象为StatementHandler,拦截方法为query、update或batch,然后在配置文件中添加插件的全限定名。

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>1

       Interceptor接口的intercept方法就是我们自定义拦截器需要实现的逻辑,其参数为Invocation,可从Invocation参数中拿到执行方法的对象,方法,方法参数,比如我们可以从statementHandler拿到SQL语句,实现自己的特殊逻辑。

       在该方法的结束需要调用invocation#proceed()方法,进行拦截器链的传播。

       参考:

       blogs.com/chenpi/p/.html

更多内容请点击【休闲】专栏