1.Mybatis源码剖析(懒加载原理)
2.mybatis插件机制源码解析
3.把Mybatis 码注Generator生成的代码加上想要的注释
4.三万字带你彻底吃透MyBatis源码!!码注
5.MyBatis 码注源码解析:映射文件的加载与解析(上)
Mybatis源码剖析(懒加载原理)
懒加载,即按需加载,码注旨在优化查询性能。码注以一个包含订单列表的码注简单贺卡flash源码User对象为例,当仅获取用户信息时,码注若启用懒加载模式,码注执行SQL不会查询订单列表。码注需获取订单列表时,码注才会发起数据库查询。码注实现方式包括在核心配置文件中设置或在相关映射文件中通过fetchType属性配置懒加载策略。码注
懒加载的码注配置如何加载到项目中呢?首先,这些配置保存在全局Configuration对象中,码注通常在解析核心配置文件的码注代码中实现。在settingsElement方法中,懒加载配置被保存在lazyLoadingEnabled属性中。对于resultMap标签中collection | association的fetchType属性,其配置通过解析mappers标签下的resultMap标签实现,最终调用buildResultMappingFromContext方法处理子标签。该方法结合全局配置判断是否需要执行懒加载。
懒加载的实现原理涉及动态代理。当调用代理对象的延迟加载属性方法时,如访问a.getB().getName(),代理对象会调用拦截器方法。若发现需要延迟加载,代理对象会单独发送SQL查询关联对象,加载数据后设置属性值,完成方法调用。简而言之,懒加载通过动态代理实现,拦截指定方法并执行数据加载。
深入剖析懒加载源码,按键页游源码会发现它涉及查询和数据处理的多步操作。查询完成后,结果集处理、列值获取、判断是否进行懒加载等步骤共同构建懒加载机制。动态代理在访问对象属性时触发,最终通过Javassist库创建代理对象,实现懒加载逻辑。当访问如userList2.get(0).getOrderList()时,若满足条件,代理对象会调用懒加载查询方法获取数据。判断懒加载条件的关键在于结果集处理阶段,通过访问映射关系和查询映射值来确定是否执行后续懒加载查询。
综上所述,Mybatis的懒加载机制通过动态代理和结果集处理实现,旨在优化性能,按需加载数据,提高查询效率。通过核心配置和映射文件中的配置,懒加载逻辑被加载到项目中,为开发者提供灵活的加载策略。
mybatis插件机制源码解析
引言
本篇源码解析基于MyBatis3.5.8版本。
首先需要说明的是,本篇文章不是mybatis插件开发的教程,而是从源码层面分析mybatis是如何支持用户自定义插件开发的。
mybatis的插件机制,让其扩展能力大大增加。比如我们项目中经常用到的PageHelper,这就是一款基于mybatis插件能力开发的产品,它的功能是让基于mybatis的数据库分页查询更容易使用。
当然基于插件我们还可以开发其它功能,比如在执行sql前打印日志、python查看kde源码做权限控制等。
正文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方法,
/***该方法会遍历用户定义的linux系统ftp源码插件实现类(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方法。
如果对这一段不是hr人才源码很理解,建议先了解下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 Generator生成的代码加上想要的注释
在日常开发工作中,Mybatis Generator常用于根据表结构生成实体类与Mapper文件。然而,其默认生成的代码通常缺少详细的注释,这会增加后续维护和理解代码的难度。因此,通常会配置Mybatis Generator不自动生成注释,但这样又引入了在生成代码后手动添加注释的繁琐工作。 为解决此问题,可以通过重写Mybatis Generator的CommentGenerator接口,实现自定义注释生成,减少重复劳动。以下步骤展示了如何实现这一目标。使用Java方式执行Mybatis Generator
在IntelliJ IDEA中,首先创建一个Maven项目,并在pom.xml文件中引入相关jar包。 接下来,在项目中创建generatorConfig.xml文件,通常将其放置在src/main/resources目录下。通过运行包含generatorConfig.xml的main方法,验证默认生成注释的情况,并确认其通常设置为不生成注释。 实现CommentGenerator接口,重写相关方法以自定义注释内容。修改generatorConfig.xml文件,将commentGenerator配置项替换为自定义的实现类。 运行生成器,检查生成的注释是否符合预期。使用Maven方式执行Mybatis Generator 在Pom.xml文件中增加Maven插件配置,确保在生成器依赖中引入实现CommentGenerator接口的jar包,并确保该jar包已安装到本地仓库。这样,执行Maven命令时,生成器将能够正确识别并使用自定义注释生成类。源码分析
执行Mybatis Generator的main方法,其主要功能包括解析配置文件和调用生成java文件与Mapper文件的方法。解析xml配置文件时,通过Document形式读取并解析标签属性,将解析结果存储在Configuration实例中。在生成文件时,根据Context的type属性反射创建实现CommentGenerator接口的类实例,并调用其方法生成注释。 生成实体类文件注释时,会调用addModelClassComment方法;生成字段注释时,调用addFieldComment方法;生成Get方法注释时,调用addGetterComment方法。这些方法的执行实现了自定义注释的生成。总结
通过实现自定义的CommentGenerator接口,可以轻松地将Mybatis Generator生成的代码添加上所需注释,大大减轻了手动添加注释的负担。使用Maven方式执行生成器时,需要确保引入插件依赖,并确保自定义jar包已正确安装。这种方法不仅适用于Mybatis Generator,也适用于其他使用生成器技术的开发场景。 本文由京东物流 王建乐撰写,如需转载,请注明来源:京东云开发者社区 自猿其说 Tech。三万字带你彻底吃透MyBatis源码!!
随着互联网的迅猛发展,MyBatis逐渐成为了Java开发者不可或缺的框架技术。许多大厂在面试中偏好问及MyBatis的底层原理及源码实现,这表明了其在技术栈中的重要性。本文旨在全面解析MyBatis源码,帮助开发者深入理解这一强大的框架。为了方便学习,推荐大家先收藏后仔细研读。
MyBatis源码在封装了JDBC之后,实现了对数据库操作的高级抽象。无论是获取连接、预编译语句、参数封装还是执行SQL,其核心步骤并未改变。
解析过程始于通过`ClassLoader.getResourceAsStream`方法获取配置文件路径。这个过程确保了MyBatis能正确加载配置信息,进而解析XML文件,构建配置中心。
解析XML文件的关键在于`parseConfiguration`和`mapperParser.parse`方法。前者用于解析配置文件中的`Environment`、`Setting`等信息,后者则专注于解析Mapper映射器,将其与工厂类进行绑定。
构建`SqlSessionFactory`的过程涉及解析Mapper映射器,生成`MappedStatement`对象,以及将接口类型与工厂类绑定。最终,`DefaultSqlSessionFactory`被创建,用于管理会话生命周期。
会话的创建通过`openSession`方法完成,该方法实例化了`Executor`来执行SQL。`Executor`的配置则决定了事务管理和执行器类型。同时,`Transaction`的管理分为两种方式,以确保数据的一致性和完整性。
获取Mapper对象时,通过`mapperRegistry.getMapper`方法,该方法从`MapperRegistry`的`knownMappers`中获取接口类型和对应的工厂类。代理对象`MapperProxy`由JDK动态代理生成,用于执行实际的数据库操作。
执行SQL时,调用代理对象的`invoke`方法,进而调用`execute`方法。无论是查询还是其他操作,均遵循此流程。在查询场景下,`selectOne`与`selectList`功能实现相同,仅在参数处理上有所差异。
`MappedStatement`对象负责存储SQL信息,包括执行策略、参数类型等。`CacheKey`的生成则基于`BoundSql`内容,用于缓存结果,提高效率。
通过以上解析,我们可以看到MyBatis源码的简洁与高效。深入理解其结构与机制,不仅有助于提高开发效率,还能增强对数据库操作的理解。总的来说,MyBatis的源码并不复杂,只需耐心研读,两三天内即可掌握其核心。
MyBatis 源码解析:映射文件的加载与解析(上)
MyBatis 的映射文件是其核心组成部分,用于配置 SQL 语句、二级缓存及结果集映射等功能,是其区别于其他 ORM 框架的重要特色。 在解析映射文件时,MyBatis 通过调用 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 语句执行和映射操作使用。