1.MyBatis源码之MyBatis中SQL语句执行过程
2.Mybatis源码剖析(懒加载原理)
3.2万多行MyBatis源码,标标签你知道里面用了多少种设计模式吗?
4.mybatis插件机制源码解析
5.源码分析Mybatis 签源MapperProxy初始化图文并茂
6.[Mybatis]-[基础支持层]-插件-plugin标签解析
MyBatis源码之MyBatis中SQL语句执行过程
MyBatis源码之MyBatis中SQL语句执行过程
MyBatis编程时主要有两种方式执行SQL语句。
方式一,详解通过SqlSession接口的标标签selectList方法调用,进入DefaultSqlSession的签源实现,最终调用executor的详解etc钓鱼同步源码query方法,使用MappedStatement封装SQL语句。标标签
方式二,签源调用SqlSession接口的详解getMapper(Class type)方法,通过工厂创建接口的标标签代理对象,调用MapperProxy的签源invoke方法,进一步执行MappedStatement,详解调用sqlSession的标标签方法。
创建动态代理类会执行MapperProxy类中的签源invoke方法,判断方法是详解否是Object的方法,如果是直接调用,否则执行cachedInvoker()方法,获取缓存中的MapperMethodInvoker,如果没有则创建一个,内部封装了MethodHandler。当cacheInvoker返回了PalinMethodInvoker实例后,调用其invoke方法,执行execute()方法,调用sqlSession的方法。
查询SQL执行流程:调用关系明确,主要步骤包括调用关系。
增删改SQL执行流程:主要步骤清晰,最后执行的都是update方法,因为insert、update、delete都对数据库数据进行改变。执行流程为:
具体的执行流程图如下所示。
Mybatis源码剖析(懒加载原理)
懒加载,即按需加载,旨在优化查询性能。以一个包含订单列表的User对象为例,当仅获取用户信息时,若启用懒加载模式,黄瓜视频app源码搭建执行SQL不会查询订单列表。需获取订单列表时,才会发起数据库查询。实现方式包括在核心配置文件中设置或在相关映射文件中通过fetchType属性配置懒加载策略。
懒加载的配置如何加载到项目中呢?首先,这些配置保存在全局Configuration对象中,通常在解析核心配置文件的代码中实现。在settingsElement方法中,懒加载配置被保存在lazyLoadingEnabled属性中。对于resultMap标签中collection | association的fetchType属性,其配置通过解析mappers标签下的resultMap标签实现,最终调用buildResultMappingFromContext方法处理子标签。该方法结合全局配置判断是否需要执行懒加载。
懒加载的实现原理涉及动态代理。当调用代理对象的延迟加载属性方法时,如访问a.getB().getName(),代理对象会调用拦截器方法。若发现需要延迟加载,代理对象会单独发送SQL查询关联对象,加载数据后设置属性值,完成方法调用。简而言之,懒加载通过动态代理实现,拦截指定方法并执行数据加载。
深入剖析懒加载源码,会发现它涉及查询和数据处理的多步操作。查询完成后,结果集处理、列值获取、判断是否进行懒加载等步骤共同构建懒加载机制。动态代理在访问对象属性时触发,最终通过Javassist库创建代理对象,实现懒加载逻辑。当访问如userList2.get(0).getOrderList()时,若满足条件,代理对象会调用懒加载查询方法获取数据。判断懒加载条件的汇编器源码剖析关键在于结果集处理阶段,通过访问映射关系和查询映射值来确定是否执行后续懒加载查询。
综上所述,Mybatis的懒加载机制通过动态代理和结果集处理实现,旨在优化性能,按需加载数据,提高查询效率。通过核心配置和映射文件中的配置,懒加载逻辑被加载到项目中,为开发者提供灵活的加载策略。
2万多行MyBatis源码,你知道里面用了多少种设计模式吗?
在MyBatis的两万多行的框架源码中,设计模式的巧妙使用是整个框架的精华。
MyBatis中主要使用了以下设计模式:工厂模式、单例模式、建造者模式、适配器模式、代理模式、组合模式、装饰器模式、模板模式、策略模式和迭代器模式。
具体来说,工厂模式用于SqlSessionFactory的创建,单例模式用于Configuration的管理,建造者模式用于ResultMap的构建,适配器模式用于统一日志接口,代理模式用于MapperProxy的实现,组合模式用于SQL标签的组合,装饰器模式用于二级缓存操作,模板模式用于定义SQL执行流程,策略模式用于多类型处理器的实现,迭代器模式用于字段解析的实现。
通过运用这些设计模式,MyBatis成功地实现了复杂场景的解耦,并将问题合理切割为若干子问题,以提高理解和解决的效率。
总的在线网页获取源码来说,MyBatis大约运用了种左右的设计模式,这使得框架在处理复杂问题时能够更加高效和灵活。
学习源码不仅可以帮助我们更好地理解设计模式和设计原则,更能够扩展我们的编码思维,积累实际应用的经验。
希望本文的分享能够帮助到您,同时也推荐您阅读《手写MyBatis:渐进式源码实践》一书,了解更多关于MyBatis的知识。
mybatis插件机制源码解析
引言
本篇源码解析基于MyBatis3.5.8版本。
首先需要说明的是,本篇文章不是mybatis插件开发的教程,而是从源码层面分析mybatis是如何支持用户自定义插件开发的。
mybatis的插件机制,让其扩展能力大大增加。比如我们项目中经常用到的PageHelper,这就是一款基于mybatis插件能力开发的产品,它的功能是让基于mybatis的数据库分页查询更容易使用。
当然基于插件我们还可以开发其它功能,比如在执行sql前打印日志、做权限控制等。
正文mybatis插件也叫mybatis拦截器,它支持从方法级别对mybatis进行拦截。整体架构图如下:
解释下几个相关概念:
Interceptor拦截器接口,用户自定义的拦截器就是实现该接口。
InterceptorChain拦截器链,其内部维护一个interceptorslist,表示拦截器链中所有的拦截器,并提供增加或获取拦截器链的方法。比如有个核心的方法是pluginAll。该方法用来生成代理对象。
Invocation拦截器执行时的上下文环境,其实就是目标方法的调用信息,包含目标对象、调用的方法信息、参数信息。核心方法是proceed。该方法的主要目的就是进行处理链的传播,执行完拦截器的高防接口html源码方法后,最终需要调用目标方法的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 MapperProxy初始化图文并茂
源码分析Mybatis MapperProxy初始化,本文基于Mybatis.3.x版本,展现作者阅读源码技巧。MapperScannerConfigurer作为Spring整合Mybatis的核心类,负责扫描项目中Dao类,并创建Mybatis的Maper对象即MapperProxy对象。
在项目配置文件中,关注到与Mapper相关的配置信息。源码分析的行文思路如下,可能会比较枯燥,但先给出MapperProxy的创建序列图,有助于理解。
MapperScannerConfigurer类图,实现Spring Bean生命周期相关功能。核心类及其作用简述如下:
BeanDefinitionRegistryPostProcessor负责设置SqlSessionFactory,生成的Mapper最终受该SqlSessionFactory管辖。
ClassPathMapperScanner的scan方法进行扫描动作,具体实现由ClassPathBeanDefinitionScanner的doScan方法和ClassPathMapperScanner的内部方法共同完成。
ClassPathMapperScanner#doScan方法首先调用父类方法,接着配置文件并构建对应的BeanDefinitionHolder对象。对这些BeanDefinitions进行处理,对Bean进行加工,加入Mybatis特性。
MapperFactoryBean作为创建Mapper的FactoryBean对象,其beanClass为MapperFactoryBean,初始化实例为MapperFactoryBean。在实例化时自动获取SqlSessionFactory或SqlSessionTemplate,用于创建具体的Mapper实例。
MapperFactoryBean的checkDaoConfig方法实现Mapper与Mapper.xml文件的关联注册。MapperRegistry负责管理注册的Mapper,核心类图展示了其关键属性和方法。
MapperRegistry#addMapper方法完成MapperProxy的注册,但实际的MapperProxy创建在getMapper方法中,根据接口获取MapperProxyFactory,调用newInstance创建MapperProxy对象。
至此,Mybatis Mapper的初始化构造过程完成一半,即MapperScannerConfigurer通过包扫描,构建MapperProxy。剩余部分,即MapperProxy与*.Mapper.xml文件中SQL语句的关联流程,将在下一篇文章中详细说明。通过MapperProxy对象的创建,为后续SQL执行流程做准备。
更多文章请关注:线报酱
[Mybatis]-[基础支持层]-插件-plugin标签解析
该系列文章针对 Mybatis 3.5.1 版本
一、Mybatis 插件的作用
Mybatis 针对SQL映射语句执行过程进行拦截处理,而对应的拦截器 Mybaits 又称之为插件(这些插件就是Mybatis的扩展点)。
在 Mybaits 中允许用插件来拦截的方法包括:
通过插件的方式可以实现SQL打印、分页等插件功能实现。
二、Mybatis 插件配置
插件代码逻辑实现后还需要加载到 Mybatis 中才能生效,Mybatis 提供了配置标签,用来声明。
插件在mybatis-config.xml 中的配置案例,如下:
通过 标签,然后在指定的属性 interceptor 配置插件实现类的全路径即可。
三、`plugin` 标签解析
再来回顾一下,XMLConfigBuilder解析时序简图,如下:
在时序图中加载解析在XMLConfigBuilder#pluginElement中完成,相关解析代码如下:
如上述代码,`plugin` 的解析流程很简单
1、加载 `plugin` 下的子标签
2、获取 `plugin` 中 interceptor 属性中的 class 全路径名
3、class 必须实现了 Interceptor接口,如果满足,通过反射实例化类
4、把类加载到存放拦截器的拦截器容器,拦截器链 InterceptorChain
简单来看一下InterceptorChain 代码定义
如上述代码所示,InterceptorChain 本身就是一个容器,用来存放所有从 `plugin` 读取到的拦截器对象。
而这里的拦截器列表,在使用过程中,会通过代理的方式,对目标对象层层代理,通过责任链的方式实现代码执行前后的层层过滤,相关逻辑图如下:
四、interceptor 过滤链代理处理代码
上面提到了 interceptor 过滤链的实现是通过代理的方式层层包裹实现的,下面来简单阅读代理流程源码
Mybatis 中只针对 Executor、ParameterHandler、ResultSetHandler、StatementHandler,这四种情况追加了过滤连的处理。
相关的处理方法入口为InterceptorChain#pluginAll,如下图
如上述代码,遍历所有的插件,调用插件本身的 plugin 方法来处理,也就是 Interceptor#plugin,来看通用实现
(也可以自定义实现逻辑),代码如下
如上述代码,通用的代理逻辑交由工具类 Plugin 来实现,
接着来看一下 Plugin#wrap 方法源码
如上述源码所示,通过配对当前 interceptor 是否符合目标对象 target ,如果配对,构建相应的代理对象。
以此类推,随后实现如下图的效果:
五、总结
通过上述源码解析能够知道一个插件,也就是一个 Interceptor 的定义需要满足两个条件
1、该插件实现类实现了 Interceptor 接口
2、该插件实现类通过注解 @Intercepts 指定了该插件需要拦截的对象,也就是 Executor、ParameterHandler、ResultSetHandler、StatementHandler 中的一种或者多种
`plugin` 标签中配置的是一系列拦截器,这些拦截器通过代理的方式组合起来实现了过滤器链。
而这些过滤器数据存储在 InterceptorChain 中,最终数据仍然会存在 Configuration 中,相关的 Configuration 逻辑图如下:
一文搞懂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,然后在配置文件中添加插件的全限定名。