1.源码分析Mybatis MapperProxy初始化【文并茂】
2.为什么 MyBatis 源码中,没有我那种 if···else
3.å¦ä½å®ç°mybatisçsqlsessiontemplate
4.干掉if else后,代码看起来爽多了!
5.mybatis插件机制源码解析
源码分析Mybatis MapperProxy初始化【文并茂】
源码分析Mybatis MapperProxy初始化,本文基于Mybatis.3.x版本,展现作者阅读源码技巧。2022谷歌地球扫描源码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 源码中,没有我那种 if···else
在 MyBatis 同化源码如何生效源码中,设计模式的巧妙使用是整个框架的精华,共有约种模式,包括创建型、结构型和行为型模式。
创建型模式包括工厂模式、单例模式和建造者模式。工厂模式用于创建 SqlSessionFactory,单例模式确保 Configuration 的唯一实例,建造者模式将 XML 文件解析到对象中。
结构型模式有适配器模式、代理模式、组合模式和装饰器模式。适配器模式使接口不兼容的对象可以协作,代理模式提供 DAO 接口的实现,组合模式用于 SQL 标签组合,装饰器模式允许在不修改结构的情况下增加行为。
行为型模式包括模板模式、策略模式和迭代器模式。模板模式定义算法框架,策略模式允许算法的替换,迭代器模式遍历集合元素。
总结,MyBatis 源码运用设计模式解决复杂问题,合理切割子问题,红翻天源码学习这些方案技术能提高对设计和实现的理解,扩展编码思维,积累经验,成为优秀工程师和架构师。
å¦ä½å®ç°mybatisçsqlsessiontemplate
SqlSession sqlSession = null;
try {
sqlSession = sqlSessionFactory.openSession();
//namespace+id
sqlSession.insert("cn.jarjar.dao.BlogMapper.insertBlog", blog);
sqlSession.commit(true)
} catch (Exception e) {
e.printStackTrace();
sqlSession.rollback(true);
} finally {
sqlSession.close();
}
ä¹å°±æ¯è¦ååå§çjava.sql.Connection对象ä¸æ ·ï¼å¿ é¡»æç §ï¼æ°å»ºè¿æ¥->æ§è¡SQL->æ交ï¼æ¥è¯¢ä¸éè¦ï¼->å¦ææä½æ°æ®åå¨å¼å¸¸éè¦åæ»->éæ¾æ°æ®åºè¿æ¥ã注æ第ä¸ç¹åæåä¸ç¹ï¼æ¯ä¸ªSqlSessionæ°å»ºä¹åå¿ é¡»éæ¾ï¼ä¸ç¶ä¼é ææ°æ®åºè¿æ¥æ³é²çå±é©ãä¹å°±æ¯æå³çSqlSessionæ¯ä¸ªæç¶æç对象ï¼æ¯æ æ³è¿è¡å¤ç¨çï¼æ以åªè½å±éäºrequestæè æ¹æ³çèå´ï¼ä¹å°±æ¯æè°ç线ç¨ä¸å®å ¨ã
ç°è±¡2ï¼å¦æ使ç¨springéæmybatisï¼å®æ¹æä¾äºæ´åå mybatis-spring.jarï¼å¦æå®æé ç½®ä¹åï¼ä½¿ç¨æ¹å¼åå ¶ç®åï¼ç®å示ä¾å¦ä¸ï¼
//æ³¨å ¥springä¸é ç½®çSqlSessionTemplate对象ï¼åä¾
@Resource(name="sqlSessionTemplate")
public SqlSessionTemplate sqlSessionTemplate;
public void saveTestTrans(){
this.sqlSessionTemplate.selectList("testdomain.selectAnySql", "select * from my_blog where id='1'");
}
è¿éçSqlSessionTemplateä¸ä» æ¯åä¾çï¼èä¸ä¸éè¦æå·¥æ°å»ºåå ³éSqlSession
é®é¢1ï¼
é£ä¹é®é¢æ¥äºï¼ä¸ºä»ä¹mybatis-spring.jarä¸çSqlSessionTemplateå¯ä»¥è¢«å¤ä¸ªdaoå¤ç¨ï¼èä¸ä¸ä¼é ææ°æ®è¿æ¥æ³é²å¢ï¼å¹¶ä¸è¿å¯ä»¥èªå¨æ°å»ºåéæ¾æ°æ®åºè¿æ¥ï¼å®æ¹è§£çæ¯å 为SqlSessionTemplateæ¯çº¿ç¨å®å ¨çï¼ä¹å°±æ¯ç¡®ä¿æ¯ä¸ªçº¿ç¨ä½¿ç¨çsqlSessionçå¯ä¸å¹¶ä¸äºç¸å²çªã
é¦å çäºä¸ä¸mybatis-springçæºç ï¼åç°SqlSessionTemplateæ¯éè¿ä»£çæ¦æªåSqlSessionHolderå®ç°çsqlsession线ç¨å®å ¨åèªå¨æ°å»ºåéæ¾è¿æ¥çãçæé å½æ°å½æ°ä¸æ建代çç±»ï¼è¯¥ä»£çç±»å®ç°SqlSessionæ¥å£ï¼å®ä¹äºæ¹æ³æ¦æªå¨ï¼å¦æè°ç¨ä»£çç±»å®ä¾ä¸å®ç°SqlSessionæ¥å£å®ä¹çæ¹æ³ï¼è¯¥è°ç¨å被导åSqlSessionInterceptorçinvokeæ¹æ³ï¼è¿ä¸ªæ¹æ³ä¸èªå¨è¿è¡äºSqlSessionçèªå¨è¯·æ±åéæ¾ï¼å¦æä¸è¢«springæ管åèªå·±æ°å»ºåéæ¾sqlsessionï¼å¦æ被spring管çå使ç¨SqlSessionHolderè¿è¡requestårelaseæä½ï¼
以ä¸ç½åé对SqlSessionTemplateç线ç¨å®å ¨ç¹æ§è¿è¡äºè¯¦ç»çæ¢ç©¶ï¼blogs.com/daxin/p/.html
é®é¢2ï¼
ç¶ååæ³å°è¿æ ·ä¸ä¸ªé®é¢ï¼è½ç¶ç°å¨å ä¹ææ项ç®é½ä½¿ç¨springä½ä¸ºjavaç¨åºçåºæ¬æ¡æ¶ï¼å¦ææä¸ä½¿ç¨spring管çmybatisï¼ä» ä» ä½¿ç¨åå§çmybatisï¼æä¹æ ·æè½æ建ä¸ä¸ªåSqlSessionTemplateç¸ä¼¼ç对象å¢ï¼
é¦å æ³å°å¿ 须使ç¨javaçtreadLocalæ建ä¸ä¸ªsqlsessionç对象ï¼å¦ThreadLocal sqlSession = new ThreadLocal
()ã
ç»è¿æ¥æ¾ï¼åç°mybatisèªèº«å°±æè¿æ ·ä¸ä¸ªç±»å®ç°äºç±»ä¼¼çåè½ï¼ç±»è·¯å¾ï¼org.apache.ibatis.session.SqlSessionManagerï¼ä½æ¯æ²¡æ注éï¼å¯è½åå¨mybatis-springè¿ç§ç¥å¨ä¹åï¼mybatisæ¾å¼äºå¯¹è¿ä¸ªç±»çç»´æ¤ã
该类å®ç°äºSqlSessionFactory, SqlSession并ä¸å¨å ¶ä¸å®ä¹äºä¸ä¸ªtreadLocalçsqlssion对象ï¼åæ¶ä½¿ç¨äºä»£çæ¦æªè¿è¡äºsqlsessionçèªå¨ç®¡çï¼å ·ä½ä»£ç å¯ä»¥èªå·±æ¥é ï¼å¯¹äºç解mybatisåçåjavaç代çæºå¶å¾æ帮å©ã
é£ä¹å个ç®åçç¨åºéªè¯ä¸ä¸SqlSessionManageræ¯å¦ççå¯ä»¥ä¿è¯çº¿ç¨å®å ¨åèªå¨æ°å»ºåéæ¾sqlssionï¼TestSqlManager.java
private static SqlSession sqlSession;
public static SqlSession getSqlSessionTest(){
if(sqlSession == null){
//æ建使ç¨çSqlSessionFactory
SqlSessionFactory sqlSessionFactory = MyBatisUtil.getSqlSessionFactory();
sqlSession = SqlSessionManager.newInstance(sqlSessionFactory);
}
return sqlSession;
}
public static void main(String[] args) throws InterruptedException {
Run run = new Run();
List
threads = new ArrayList
();
for (int i = 0; i < ; i++) {
Thread t = new Thread(run);
threads.add(t);
System.out.println("thread:{ "+t.getName()+"}, start");
t.start();
}
for (Thread t : threads) {
System.out.println("thread:{ "+t.getName()+"},join");
t.join();
}
}
ææ¬æºè£ çmysqlï¼éè¿çæ§è¯å¥ï¼select SUBSTRING_INDEX(host,â:â,1) as ip , count(*) from information_schema.processlist group by ip;åç°æ§è¡è¿ç¨ä¸åå¨è¿æ¥å¹¶åçæ åµï¼ä½æ¯æ§è¡ä¹åå ¨é¨éæ¾æäºã
干掉if else后,代码看起来爽多了!
今天,我们来深入剖析Mybatis框架中的设计模式,看看它如何巧妙地摆脱if/else的困扰,展现其独特魅力!
Mybatis庞大的2万多行源码中,巧妙运用了多种设计模式来优化工程结构,如创建型模式的工厂设计,如SqlSessionFactory的构建。它通过SqlSessionFactory工厂模式,为我们获取会话提供统一接口,每次数据库操作都会通过这个工厂开启新的会话,其中包含了数据源配置、事务处理和SQL执行器的构建。
另外,Configuration作为单例配置类,采用单例模式确保全局唯一,整合了映射、缓存等众多配置,xpath如何抓源码并在SqlSessionFactoryBuilder构建阶段初始化。ErrorContext、LogFactory和Configuration也是采用类似的单例模式,为框架的稳定运行提供支持。
建造者模式在Mybatis中体现在如XMLConfigBuilder等类,通过逐步构建对象,避免了直接设置属性,保持了代码的清晰和可维护性。日志框架的适配则体现了适配器模式,通过统一接口让不同框架能无缝协作,如对Log4j、Log4j2和Slf4J等的适配。
代理模式在MapperProxy的实现中尤为显著,它作为DAO接口的代理,统一了CRUD方法的调用,简化了业务逻辑。此外,组合模式在SQL配置中体现,通过SqlNode接口构建SQL规则树,组合出各种复杂场景。
行为型模式如模板模式和策略模式在Mybatis中也大显身手,BaseExecutor定义了查询和修改的通用流程,而多类型处理器策略模式则通过TypeHandler实现了不同类型数据的处理策略。
迭代器模式在PropertyTokenizer中体现,用于对象关系的解析,提升了代码的灵活性。总之,Mybatis巧妙地运用了约种设计模式,优化了代码结构,使得代码更加简洁和高效。
深入研究源码不仅有助于理解框架工作原理,还能提升技术理解和实践能力,是成为高级工程师和架构师的重要基石。通过学习这些优秀的设计实践,我们可以更好地应对复杂的技术挑战。
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
2024-11-23 12:48
2024-11-23 12:42
2024-11-23 12:03
2024-11-23 11:29
2024-11-23 11:14
2024-11-23 10:58
2024-11-23 10:47
2024-11-23 10:46