1.MyBatis源码解析之基础模块—TypeHandler
2.一文带你理解透MyBatis源码
3.MyBatis源码之MyBatis中SQL语句执行过程
4.mybatis插件机制源码解析
5.MyBatis7:MyBatis插件及示例----打印每条SQL语句及其执行时间
MyBatis源码解析之基础模块—TypeHandler
MyBatis源码解析之基础模块—TypeHandler
在MyBatis的码视上一章节中,我们探讨了Plugin模块的频教拦截器配置和自定义。接下来,码视我们将深入理解数据库与Java对象之间转换的频教核心机制,即Type模块的码视源码。 Type模块位于org.apache.ibatis.type,频教99茶馆源码其架构设计包含IntegerTypeHandler和UnknownTypeHandler等实现类,码视用于处理不同类型的频教转换。JdbcType枚举定义了常见的码视数据库数据类型,MappedTypes和MappedJdbcTypes注解用于标注Java类型和数据库类型的频教映射。 对于类型转换,码视TypeHandler是频教核心接口,它定义了处理方法。码视BaseTypeHandler是频教抽象基类,采用模板方法模式,码视提供了通用逻辑,而具体实现由子类如IntegerTypeHandler完成。对于没有明确泛型类型的转换,UnknownTypeHandler则负责处理。 TypeAliasRegister负责注册Java常用数据类型的别名,而TypeHandlerRegister是类型转换器的注册中心,MyBatis在初始化时已经自动注册了常用TypeHandler。ResultSetWrapper则负责包装ResultSet,提供类型转换器的获取,最终由ResultSetHandler处理实际的数据处理。 总结来说,Type模块在MyBatis中负责数据的类型转换,通过TypeHandler和相关的注册机制,确保了数据库操作与Java对象之间的江苏溯源码防伪无缝对接。在实际开发中,无需过多配置,MyBatis就能自动完成类型转换,使得开发更为便捷。一文带你理解透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信息,sentinel源码是什么用于缓存查询结果。最后,从数据库查询并执行doQuery源码,总结了MyBatis源码的整体流程,较为简洁,通过细致研究,可以深入理解框架的核心机制。
MyBatis源码之MyBatis中SQL语句执行过程
MyBatis源码之MyBatis中SQL语句执行过程
MyBatis编程时主要有两种方式执行SQL语句。
方式一,通过SqlSession接口的selectList方法调用,进入DefaultSqlSession的实现,最终调用executor的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插件机制源码解析
引言
本篇源码解析基于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
MyBatis7:MyBatis插件及示例----打印每条SQL语句及其执行时间
Plugins
MyBatis允许在特定点拦截映射语句执行调用。默认情况下,MyBatis允许通过插件来拦截方法调用。这些类方法的详细信息可以通过查看每个方法的签名来发现,它们的源代码存在于MyBatis发行包中。理解覆盖方法的行为至关重要,因为修改或覆盖可能会打破MyBatis的核心功能。谨慎使用插件。
插件示例:打印每条SQL语句及其执行时间
实现插件以打印每条真正执行的SQL语句及其执行时间。MyBatis日志可以记录SQL,但存在一些问题。编写MyBatis插件很简单,只需实现Interceptor接口。
插件代码示例:
注解@Intercepts和@Signature是必需的,因为Plugin的wrap方法会取用这两个注解的参数。@Intercepts中定义多个@Signature,表示符合特定条件的方法会被拦截。
选择StatementHandler进行拦截,而不是Executor,原因是StatementHandler允许访问SQL语句和参数,而Executor更侧重于执行操作。此外,StatementHandler提供更细粒度的控制。
使用setProperties方法配置插件属性,并通过plugin方法生成代理。代理由MyBatis的Plugin类生成,使用Proxy类确保满足方法签名的接口生成代理。
核心是intercept方法,这里实现拦截器的主要逻辑。确保最终返回invocation.proceed(),保持拦截器的层层调用。
配置插件的XML文件
在config.xml中配置插件,简单明了。每个子标签代表一个插件,interceptor表示拦截器的完整路径,具体配置由个人决定。
使用插件实例
配置插件后,即可使用SqlCostInterceptor,这是一个通用插件。尽管CRUD不同,仍可打印完整SQL语句及其执行时间。
插件Demo
插件示例仅作为演示,可能无法覆盖所有场景。需要根据具体需求进行调整。即使不修改代码,插件仍能美化SQL,去除换行符。
总结
MyBatis插件机制强大,用于解决各种问题,如打印SQL语句、记录执行时间、实现分页或分表。编写有效拦截器的关键在于理解接口及其相关方法的功能。