当前位置: 首页 > 科技观察

Mybatis原理及源码分析

时间:2023-03-15 09:09:29 科技观察

本文转载自微信公众号《三不猴》,作者三不猴子。转载本文请联系三部猴公众号。作为Java程序员,Mybatis应该是不可避免的框架。它的源码大小只有Spring的1/5,Hibernate的1/5。与其他流行框架相比,Mybatis源码无疑是学习成本最低的。作为一个年轻人,第一个框架的源码无疑是非常好的。整体架构不要深入研究不熟悉的细节。让我们来看看架构。MyBatis分为三层,分别是基础支撑层、核心处理层和接口层。Mybatis整体架构基础支撑层基础支撑层是Mybatis框架的基础设施,为整个Mybatis框架提供非常基础的功能。(限于篇幅,下面只对部分模块做简单分析)1、类型转换模块,我们在Mybatis中使用标签定义一个别名,使用类型转换模块实现。类型转换模块最重要的功能是实现Mybatis中JDBC类型和Java类型的转换。主要体现在:在SQL模板绑定用户输入实参的场景中,将Java类型转换为JDBC类型在结果集中,将JDBC类型转换为Java类型。Mybatis类型转换2、日志模块产生日志,定位异常。3.反射工具,封装了原生Java反射。4.绑定模块,我们在执行Mybatis中的方法时,使用SqlSession获取Mapper接口中的agent。该代理通过Binding模块关联Mapper.xml文件中的SQL。值得注意的是,这个过程发生在编译时。错误可以提前到编译时。5.数据源模块,数据源是ORM的核心组件之一。Mybatis默认的数据源很好,Mybatis也支持第三方数据源的集成。6.缓存模块,缓存模块的好坏直接影响到这个ORM的性能。Mybatis提供了二级缓存,也支持第三方缓存中间件集成。Mybatiscache7.Parser模块,主要是config.xml配置文件解析,以及mapper.xml文件解析。8.事务管理模块,事务模块控制数据库的事务机制,对数据库事务进行抽象,同时还集成了spring框架。下面结合源码详细分析详细的处理逻辑。核心处理层核心处理层是我们需要花80%时间学习Mybatis原理的地方。核心处理层是MyBatis的核心实现,涉及到MyBatis的初始化和执行一条SQL语句的整个过程。配置分析MyBatis的初始化和一条SQL语句的执行的整个过程也包括配置分析。在实际开发中,我们一般使用springbootstarter的自动配置。我们的项目以Mybatis的进程为起点,层层剥离。首先打开org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration首先要明确的是,MybatisAutoConfiguration的作用是获取一个SqlSessionFactory。@Bean@ConditionalOnMissingBeanpublicSqlSessionFactorysqlSessionFactory(DataSourcedataSource)throwsException{SqlSessionFactoryBeanfactory=newSqlSessionFactoryBean();factory.setDataSource(dataSource);factory.setVfs(SpringBootVFS.class);如果(StringUtils.hasText(this.properties.getConfigLocation.setConfig)){factory(this.resourceLoader.getResource(this.properties.getConfigLocation()));}Configurationconfiguration=this.properties.getConfiguration();if(configuration==null&&!StringUtils.hasText(this.properties.getConfigLocation())){configuration=newConfiguration();}if(configuration!=null&&!CollectionUtils.isEmpty(this.configurationCustomizers)){for(ConfigurationCustomizercustomizer:this.configurationCustomizers){customizer.customize(配置);}}factory.setConfiguration(配置);if(this.properties.getConfigurationProperties()!=null){factory.setConfigurationProperties(this.properties.getConfigurationProperties());}if(!ObjectUtils.isEmpty(this.interceptors)){factory.setPlugins(this.interceptors);}if(this.databaseIdProvider!=null){factory.setDatabaseIdProvider(this.databaseIdProvider);}if(StringUtils.hasLength(this.properties.getTypeAliasesPackage())){factory.setTypeAliasesPackage(this.properties.getTypeAliasesPackage());}if(StringUtils.hasLength(this.properties.getTypeHandlersPackage())){factory.setTypeHandlersPackage(this.properties.getTypeHandlersPackage());}if(!ObjectUtils.isEmpty(this.properties.resolveMapperLocations())){factory.setMapperLocations(this.properties.resolveMapperLocations());}returnfactory.getObject();}这里是MybatisProperties中的配置,放入SqlSessionFactoryBean中,从SqlSessionFactoryBean获取SqlSessionFactory并查看最后一行returnfactory.getObject();我们进去看看这个factory.getObject()的逻辑是怎么得到一个SqlSessionFactory的。@OverridepublicSqlSessionFactorygetObject()throwsException{if(this.sqlSessionFactory==null){afterPropertiesSet();}returnthis.sqlSessionFactory;}这一步没什么好说的,看afterPropertiesSet()方法@OverridepublicvoidafterPropertiesSet()throwsException{notNull(dataSource,"属性'dataSource'isrequired");notNull(sqlSessionFactoryBuilder,"属性'sqlSessionFactoryBuilder'isrequired");state((configuration==null&&configLocation==null)||!(configuration!=null&&configLocation!=null),"Property'配置'和'configLocation'不能一起指定");this.sqlSessionFactory=buildSqlSessionFactory();}重点来了,看一下buildSqlSessionFactory()方法。这里的核心目的是将configurationProperties解析成Configuration对象。代码太长,就不贴了。我画了一张buildSqlSessionFactory()的逻辑图。有兴趣的朋友可以自己看看。Mybatis配置分析1先不纠结,先看buildSqlSessionFactory()方法最后一行this.sqlSessionFactoryBuilder.build(configuration)点击publicSqlSessionFactorybuild(Configurationconfig){returnnewDefaultSqlSessionFactory(config);}通过buildSqlSessionFactory()ParsedConfiguration对象创建了一个DefaultSqlSessionFactory(config),所以我们获取SqlSessionFactory并将其配置为一个bean。我们最后的操作是SqlSession。我们什么时候会通过SqlSessionFactory得到一个SqlSession呢?为了解决这个问题,我们回到原来MybatisAutoConfiguration的sqlSessionTemplate(SqlSessionFactorysqlSessionFactory)方法。点击SqlSessionTemplate,发现是一个实现了SqlSession的SqlSession。我们在这里猜测是SqlSessionFactory会在这里建立一个SqlSession。我们进入newSqlSessionTemplate(sqlSessionFactory)查看源码。publicSqlSessionTemplate(SqlSessionFactorysqlSessionFactory){this(sqlSessionFactory,sqlSessionFactory.getConfiguration().getDefaultExecutorType());}再往下看,我们就看到了publicSqlSessionTemplate(SqlSessionFactorysqlSessionFactory,ExecutorTypeexecutorType,PersistenceExceptionTranslatorexceptionTranslator){notNull(sqlSessionFactory,"Property'sqlSessionFactory'isrequired");notNull(executorType,"Property'executorType'isrequired");this.sqlSessionFactory=sqlSessionFactory;this.executorType=executorType;this.exceptionTranslator=exceptionTranslator;this.sqlSessionProxy=(SqlSession)newProxyInstance(SqlSessionFactory.class.getClassLoader(),newClass[]{SqlSession.class},newSqlSessionInterceptor());}这里通过动态代理创建了一个SqlSession。参数映射、SQL解析我们先来看一下MapperFactoryBean类。此类实现FactoryBean在初始化bean时将调用的getObject()方法。我们来看看这个类下重写的getObject()方法的内容。@OverridepublicTgetObject()throwsException{returngetSqlSession().getMapper(this.mapperInterface);}这里调用了sqlSession的getMapper()方法。逐层点击会返回一个代理对象。最终执行由MapperProxy执行。publicTgetMapper(Classtype,SqlSessionsqlSession){finalMapperProxyFactorymapperProxyFactory=(MapperProxyFactory)knownMappers.get(type);如果(mapperProxyFactory==null){thrownewBindingException("Type"+type+"isnotknowntotheMapperRegistry.");}try{returnmapperProxyFactory.newInstance(sqlSession);}catch(Exceptione){thrownewBindingException("Errorgettingmapperinstance.Cause:"+e,e);}}接下来的流程我画个流程图防止小的伙伴丢失。这里的内容可能和小标题不完全一样,我主要是按照sql执行的过程来讲解的。Mybatis参数绑定先看MapperProxy中的invoke方法,cachedMapperMethod()方法缓存了MapperMethod。@OverridepublicObjectinvoke(Objectproxy,Methodmethod,Object[]args)throwsThrowable{try{if(Object.class.equals(method.getDeclaringClass())){returnmethod.invoke(this,args);}elseif(isDefaultMethod(method)){returninvokeDefaultMethod(proxy,method,args);}}catch(Throwablet){throwExceptionUtil.unwrapThrowable(t);}finalMapperMethodmapperMethod=cachedMapperMethod(方法);returnmapperMethod.execute(sqlSession,args);}privateMapperMethodcachedMapperMethod(Methodmethod){MapperMethodmapperMethod.=get(method);if(mapperMethod==null){mapperMethod=newMapperMethod(mapperInterface,method,sqlSession.getConfiguration());methodCache.put(method,mapperMethod);}returnmapperMethod;}我们在往下看mapperMethod.execute(sqlSession,args)方法。publicObjectexecute(SqlSessionsqlSession,Object[]args){Objectresult;switch(command.getType()){caseINSERT:{Objectparam=method.convertArgsToSqlCommandParam(args);result=rowCountResult(sqlSession.insert(command.getName(),param));break;}caseUPDATE:{Objectparam=method.convertArgsToSqlCommandParam(args);result=rowCountResult(sqlSession.update(command.getName(),param));break;}caseDELETE:{Objectparam=method.convertArgsToSqlCommandParam(args);结果=rowCountResult(sqlSession.delete(command.getName(),param));break;}caseSELECT:if(method.returnsVoid()&&method.hasResultHandler()){executeWithResultHandler(sqlSession,args);result=null;}elseif(method.returnsMany()){result=executeForMany(sqlSession,args);}elseif(method.returnsMap()){result=executeForMap(sqlSession,args);}elseif(method.returnsCursor()){result=executeForCursor(sqlSession),args);}else{Objectparam=method.convertArgsToSqlCommandParam(args);result=sqlSession.selectOne(command.getName(),param);}break;caseFLUSH:result=sqlSession.flushStatements();break;default:thrownewBindingException("Unknownexecutionmethodfor:"+command.getName());}if(result==null&&method.getReturnType().isPrimitive()&&!method.returnsVoid()){thrownewBindingException("Mappermethod'"+command.getName()+"attemptedtoreturnullfrommethodwithaprimitivereturntype("+method.getReturnType()+").");}returnresult;}method.convertArgsToSqlCommandParam(args)这里是处理参数转换逻辑上还是有很多细节的。限于篇幅和时间,我们不再赘述。有兴趣的朋友可以结合上图自行看一看。让我们看看SQL执行过程是怎样的。整体流程如下图所示。Mybatis执行过程中的每一个executor我们就不分析了。我只挑了一个SimpleExecutor来详细跟进源码。我们先看图,免得自欺欺人。以simpleExecutor为例的执行过程@OverridepublicListdoQuery(MappedStatementms,Objectparameter,RowBoundsrowBounds,ResultHandlerresultHandler,BoundSqlboundSql)throwsSQLException{Statementsstmt=null;try{Configurationconfiguration=ms.getConfiguration();StatementHandlerStateHandler=configurationwrapper,ms,parameter,rowBounds,resultHandler,boundSql);stmt=prepareStatement(handler,ms.getStatementLog());returnhandler.query(stmt,resultHandler);}finally{closeStatement(stmt);}}到这里配置,创建一个StatementHandler,预处理操作,具体根据创建的预处理方法执行,最后执行查询方法@OverridepublicListquery(Statementstatement,ResultHandlerresultHandler)throwsSQLException{Stringsql=boundSql.getSql();statement.execute(sql);returnresultSetHandler.handleResultSets(statement);}至此我们梳理了整个Mybatis的执行流程,分析了源码。由于篇幅有限,很多地方都没有详细分析,不过也贴出这里的图片,希望对大家有所帮助。