作为java程序员Mybatis应该是一个必须了解的框架。它的源码大小只有spring的1/5,也是H ibernate1/ 5。与其他流行框架相比,Mybatis源码无疑是学习成本最低的。作为年轻人看的第一个框架源码,无疑是非常优秀的。
不要深入研究不熟悉事物的细节。我们首先需要粗略地了解一下架构。 MyBatis架构分为三层,即基础支撑层、核心处理层和接口层。
基础支撑层是Mybatis框架的基础设施,为整个Mybatis框架提供非常基础的功能。 (篇幅有限,下面我们只对部分模块做简单分析)
类型转换模块,我们在Mybatis中使用< typeAliase >标签来定义别名,使用类型转换模块来实现。类型转换模块最重要的功能是实现Mybatis中的JDBC类型与Java类型之间的转换。主要体现在:
SQL模板绑定用户输入参数的场景,将Java类型转换为JDBC类型
将结果集中的JDBC类型转换为Java类型。
Log模块,生成Log,定位异常。
反射工具,封装了原生Java反射。
绑定模块,当我们在Mybatis中执行方法时,我们通过SqlSession获取Mapper接口中的代理。该代理通过 Binding 模块关联 Mapper.xml 文件中的 SQL。值得注意的是,这个过程发生在编译过程中。错误可以提前到编译时间。
数据源模块,数据源是ORM的核心组件之一。 Mybatis默认的数据源非常好,而且Mybatis还支持第三方数据源的集成。
缓存模块,缓存模块的好坏直接影响到这个ORM的性能。 Mybatis提供了两级缓存,也支持第三方缓存中间件集成。
解析器模块,主要解析config.xml配置文件和mapper.xml文件。
事务管理模块,事务模块控制数据库的事务机制,对数据库事务进行抽象,同时还集成了spring框架。下面结合源码详细分析详细的处理逻辑。
核心处理层是我们学习Mybatis原理时花费80%时间的地方。核心处理层是MyBatis的核心实现所在,涉及MyBatis的初始化以及执行一条SQL语句的整个过程。
初始化MyBatis和执行SQL语句的整个过程还包括配置分析。在实际开发中,我们一般使用spring boot starter的自动配置。我们以该项目为起点,层层剥离Mybatis。首先打开org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration首先明确MybatisAutoConfiguration的目的是为了获取一个SqlSessionFactory。
org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration
@Bean @ConditionalOnMissingBean public SqlSessionFactory sqlSessionFactory(数据源数据源)抛出异常{ SqlSessionFactoryBean工厂=new SqlSessionFactoryBean(); factory.setDataSource(dataSource); factory.setVfs(springbootVFS.类); if(StringUtils.hasText()这个.properties.getConfigLocation())) { factory.setConfigLocation(这个.resourceLoader.getResource(这个.properties.getConfigLocation())); } 配置配置 = this.properties.getConfiguration() : .properties.getConfigLocation())) { 配置 = new 配置(); } if(配置!= null&&!CollectionUtils.isE空(这个.configurationCustomizers)) { for(ConfigurationCustomizer 自定义程序:这个.configurationCustomizers) { customizer.customize(configuration); } } factory.setConfiguration(配置); if( 这个.properties.getConfigurationProperties() != null ) { factory.setConfigurationProperties(这个.properties.getConfigurationProperties()); } if(!ObjectUtils.isEmpty(这个.interceptors)) { factory.setPlugins(this.interceptors); } if(这个.databaseIdProvider!= null) { factory.setDatabaseIdProvider(this.databaseIdProvider); } if(StringUtils.hasLength(这个.properties.getTypeAliasesPackage())) { factory.setTypeAliasesPackage(this.properties.getTypeAliasesPackage()); .properties.getTypeHandlersPackage())) { factory.setTypeHandlersPackage(这个.properties.getTypeHandlersPackage()); } if (!ObjectUtils .isEmpty( this.properties.resolveMapperLocations())) { factory.setMapperLocations(this.prop erties.resolveMapperLocations()); factory.getObject(); }
这里将MybatisProperties中的配置放入SqlSessionFactoryBean中,然后从SqlSessionFactoryBean中获取SqlSessionFactory。看到最后一行returnfactory.getObject();我们进去看看factory.getObject()的逻辑是如何获取一个SqlSessionFactory的。
returnfactory.getObject();
@Overridepublic SqlSessionFactory getObject()抛出 例外 { 如果 (这个.sqlSessionFactory == null) { afterPropertiesSet(); } 返回这个.sqlSessionFactory; }
这一步没什么好说的,看看afterPropertiesSet()方法
afterPropertiesSet()
@Overridepublic void afterPropertiesSet()抛出异常{ notNull(dataSource, “属性‘dataSource’是必需的”); notNull(sqlSessionFactoryBuilder, “属性‘sqlSessionFactoryBuilder’是必需的” ); 状态((配置==null && configLocation == null) || !(配置!= null && configLocation != null), “属性‘configuration’和‘configLocation’不能一起指定”); this.sqlSessionFactory = buildSqlSessionFactory(); }
重点来了,看看这个buildSqlSessionFactory()方法这里的核心目的就是将configurationProperties解析到Configuration对象中。代码太长了我就不贴出来了,buildSqlSessionFactory() 的逻辑我画了个图,有兴趣的小伙伴自行看一下。
buildSqlSessionFactory()
先别纠结细节,我们看中点buildSqlSessionFactory()方法的最后一行www.sychzs.cn(configuration) 点击进入
www.sychzs.cn(configuration) 点击进入
public SqlSessionFactory build(配置配置) { 返回新DefaultSqlSessionFactory(config); }
通过buildSqlSessionFactory()解析的Configuration对象创建一个DefaultSqlSessionFactory(config),这里我们得到 SqlSessionFactory 也配置为 bean 。
DefaultSqlSessionFactory(config)
SqlSessionFactory
解决这个问题,我们回到原来的MybatisAutoConfiguration的sqlSessionTemplate(SqlSessionFactory sqlSessionFactory)这个方法,点击SqlSessionTemplate 发现是一个实现 SqlSession此时我们猜测这就是 SqlSessionFactory 将构建一个 SqlSession 。我们去new SqlSessionTemplate(sqlSessionFactory)看一下源码。
MybatisAutoConfiguration
sqlSessionTemplate(SqlSessionFactory sqlSessionFactory)
SqlSessionTemplate
SqlSession
new SqlSessionTemplate(sqlSessionFactory)
公共 SqlSessionTemplate(SqlSessionFactory sqlSessionFactory) { 这个(sqlSessionFactory, sqlSessionFactory.getConfiguration().getDefaultExecutorType() );}
再往下看,我们看到
公共 SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType, PersistenceExceptionTranslator 异常翻译器) { notNull(sqlSessionFactory, "属性'sqlSessionFactory'是必需"); notNull(executorType,"属性'executorType'是必需的"); 这个.sqlSessionFactory = sqlSessionFactory; 这个.executorType = executorType; 这个.exceptionTranslator = 例外翻译; 这个.sqlSessionProxy = (SqlSession) newProxyInstance( SqlSessionFactory .类.getClassLoader(), new 类[] { SqlSession.类 }, 新 SqlSessionInterceptor()) ;}
这里通过动态代理创建了一个SqlSession。
我们首先看一下MapperFactoryBean类。该类实现了FactoryBean。 bean初始化的时候,getObject( )方法我们来看看这个类重写的getObject()方法中的内容。
MapperFactoryBean
FactoryBean
getObject( )
getObject()
这里调用了sqlSession的getMapper()方法。逐层点击会返回一个代理对象。最终执行由MapperProxy执行。
sqlSession
getMapper()
MapperProxy
public T getMapper(类类型,SqlSession sqlSession) { 最终MapperProxyFactory mapperProxyFactory = (MapperProxyFactory) knownMappers.get(type); if(mapperProxyFactory == 空) { 扔 newBindingException("类型"+类型+"对于MapperRegistry来说是未知的。" ); } 尝试 { 返回 mapperProxyFactory.newInstance(sqlSession); } catch (例外 e) { 投掷 新 BindingException("获取映射器实例时出错。原因:" + e, e); } }
接下来的流程我还是画个流程图,防止小伙伴们走丢。我这里的内容可能完全和小标题一样,我主要执行sql执行的流程的讲解是按照的。
先看MapperProxy中的invoke方法,cachedMapperMethod()的方法缓存MapperMethod。
invoke
cachedMapperMethod()的方法缓存MapperMethod。
MapperMethod
@Overridepublic 对象调用(对象代理,方法方法,对象[]参数) 投掷可投掷{ 尝试 { if(对象。类.等于(方法.getDeclaringClass())) { return method.invoke(this ,args); } 其他if (isDefaultMethod(method)) { return invokeDefaultMethod(proxy, method, args); } } 抓住(可投掷) { 投掷 ExceptionUtil.unwrapThrowable(t); } 最终MapperMethodmapperMethod=cachedMapperMethod(method); returnmapperMethod.execute(sqlSession, args);}privateMapperMethodcachedMapperMethod(方法方法) { MapperMethod mapperMethod = methodCache .get(方法);(mappermethod ==null){地图perMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()); methodCache.put(method, mapperMethod); } 返回mapper方法; }
我们正在向下看 mapperMethod.execute(sqlSession, args) 方法。
mapperMethod.execute(sqlSession, args)
public 对象 执行(SqlSession sqlSession, Object[] args) { 对象结果; 开关(命令。 getType()) { case插入:{ 对象 param = method.convertArgsToSqlCommandParam(args); 结果 = rowCountResult(sqlSession.insert(command.getName(), param) ); 断裂; } 案例 更新:{ 对象参数= method.convertArgsToSqlCommandParam(args); 结果= rowCountResult( sqlSession.update(command.getName(), param)); break; } 案例删除:{ 对象参数=方法。 ConvertArgsToSqlCommandParam(args); 结果 = rowCountResult(sqlSession.delete(command.getName(), param)); break; } 案例 SELECT: if (method.returnsVoid() && method.hasResultHandler()) { executeWithResultHandler(sqlSessi)on, args); 结果 = null; } else if (method.returnsMany()) { 结果 = executeForMany (sqlSession, args); } else if (method.returnsMap()) { 结果 = executeForMap(sqlSession, args); } else if (method.returnsCursor()) { 结果= executeForCursor(sqlSession, args); } else { 对象参数= method.convertArgsToSqlCommandParam(args); 结果= www.sychzs.cnOne(command.getName(), param); } 打破; 情况刷新: 结果 = sqlSession.flushStatements(); break; 默认: 投掷 新BindingException(“未知执行方法:”+command.getName()); } if(结果==null && method.getReturnType().isPrimitive() && !method.returnsVoid()) { 抛出 new BindingException( "映射器方法 '" + command.getName() T+“尝试从具有原始返回类型的方法返回 Null (“+METHOD.GetRETURNTYPE ()+”)。”); } 返回结果;}
method.convertArgsToSqlCommandParam(args) )这里是处理参数转换的逻辑。由于篇幅和时间的限制,还有很多细节我们就不详细阐述了。有兴趣的朋友可以根据上面的图片自己看一下。我们来看看SQL的执行过程是怎样的。整体流程如下图。
method.convertArgsToSqlCommandParam(args) )
我们不会分析每一个执行者。我只会选择一个SimpleExecutor来详细跟随源码。我们先看一下图,免得自己一头雾水。
@Overridepublic List doQuery(映射语句 ms、对象参数、RowBounds rowBounds、ResultHandler resultHandler、BoundSqlboundSql)抛出 SQLException { 语句 stmt = null; 尝试 { 配置配置= ms.getConfiguration(); StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql); stmt = prepareStatement(handler, ms.getStatementLog()); 返回 处理程序。query(stmt, resultHandler); } 最后 { closeStatement(stmt); } }
这里获取了配置,创建了一个StatementHandler,计费操作,具体执行根据创建的计费方法,最后执行查询方法
查询
@Overridepublic List 查询(语句语句, ResultHandler resultHandler) 抛出 SQLException { String sql =boundSql.getSql();statement.execute(sql);return resultS etHandler.handleResultSets(语句);}
至此,我们就梳理完了整个Mybatis的执行流程,并分析了源码。由于篇幅有限,很多地方没有详细分析,但我们也贴出了图片,希望对您有所帮助。