您现在的位置是:亿华云 > 域名
Mybatis原理及源码分析
亿华云2025-10-03 20:26:57【域名】9人已围观
简介本文转载自微信公众号「三不猴子」,作者sanbuhouzi。转载本文请联系三不猴子公众号。作为Java程序员Mybatis应该是一个必会框架了,其源码体量只有Spring 的1/5,也是Hiberna
本文转载自微信公众号「三不猴子」,原源码作者sanbuhouzi。理及转载本文请联系三不猴子公众号。分析
作为Java程序员Mybatis应该是原源码一个必会框架了,其源码体量只有Spring 的理及1/5,也是分析Hibernate的1/5 ,相比于其他流行框架Mybatis源码无疑是原源码学习成本最低的,当做年轻人看的理及第一个框架源码,无疑是分析非常好的。
整体架构
对于一个陌生的原源码事物切勿一头扎进细节里,我们先要观其大概看看架构脉络,理及MyBatis 分为三层架构,分析分别是原源码基础支撑层、核心处理层和接口层。理及
Mybatis 整体架构
基础支撑层
基础支撑层是分析这个Mybatis框架的基建,为整个Mybatis框架提供非常基础的功能。(篇幅有限下面我们只对部分模块做简单的分析)
1.类型转换模块,我们在Mybatis中使用< typeAliase >标签定义一个别名就是使用类型转换模块实现的云服务器提供商。类型转换模块最重要的功能还是实现了Mybatis中JDBC类型和Java类型之间的转换。主要体现在:
在SQL模板绑定用户参入实参的场景中,将Java类型转换成JDBC类型 在结果集中,将JDBC类型转换成Java类型。Mybatis类型转换
2.日志模块,产生日志,定位异常。
3.反射工具,对原生的Java反射作了一些封装。
4.Binding模块,我们在执行Mybatis中的方法时都是通过SqlSession来获Mapper接口中的代理,这个代理将Mapper.xml文件中SQL进行关联就是通过Binding模块来实现的。值得注意点是这个过程是发生在编译期。可以将错误提前到编译期。
5.数据源模块,数据源对于一个ORM来说算是非常核心的组件之一了。Mybatis默认的数据源是源码库非常出色的,Mybatis同时也支持集成第三方的数据源。
6.缓存模块,缓存模块的好坏直接影响这个ORM的性能。Mybatis提供了两级缓存,同时也支持第三方缓存中间件集成。
Mybatis缓存
7.解析器模块,主要是config.xml配置文件解析,和mapper.xml文件解析。
8.事务管理模块,事务模块对数据库的事务机制进行控制,对数据库事务进行了抽象,同时也对spring框架进行了整合。详细的处理逻辑下文会结合源码详细解析。
核心处理层
核心处理层是我们在学习Mybatis原理的时候需要花80%时间的地方。核心处理层是 MyBatis 核心实现所在,其中涉及 MyBatis 的初始化以及执行一条 SQL 语句的全流程。
配置解析
MyBatis 的初始化以及执行一条 SQL 语句的全流程中也包含了配置解析,网站模板我们在现实开发中一般都是使用spring boot starter的自动配置。我们一项目启动为起点一层一层剥开Mybatis的流程。先打开org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration首先明确一点就是MybatisAutoConfiguration的目的就是要得到一个SqlSessionFactory。
@Bean @ConditionalOnMissingBean public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception { SqlSessionFactoryBean factory = new SqlSessionFactoryBean(); factory.setDataSource(dataSource); factory.setVfs(SpringBootVFS.class); if (StringUtils.hasText(this.properties.getConfigLocation())) { factory.setConfigLocation(this.resourceLoader.getResource(this.properties.getConfigLocation())); } Configuration configuration = this.properties.getConfiguration(); if (configuration == null && !StringUtils.hasText(this.properties.getConfigLocation())) { configuration = new Configuration(); } if (configuration != null && !CollectionUtils.isEmpty(this.configurationCustomizers)) { for (ConfigurationCustomizer customizer : this.configurationCustomizers) { customizer.customize(configuration); } } factory.setConfiguration(configuration); 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()); } return factory.getObject(); }这里是通过MybatisProperties里面的配置并放入到SqlSessionFactoryBean中,再由SqlSessionFactoryBean得到SqlSessionFactory。看到最后一行return factory.getObject();我们进去看看这个factory.getObject()的逻辑是如何得到一个SqlSessionFactory。
@Override public SqlSessionFactory getObject() throws Exception { if (this.sqlSessionFactory == null) { afterPropertiesSet(); } return this.sqlSessionFactory; }这一步没什么好说的,看看afterPropertiesSet()方法
@Override public void afterPropertiesSet() throws Exception { notNull(dataSource, "Property dataSource is required"); notNull(sqlSessionFactoryBuilder, "Property sqlSessionFactoryBuilder is required"); state((configuration == null && configLocation == null) || !(configuration != null && configLocation != null), "Property configuration and configLocation can not specified with together"); this.sqlSessionFactory = buildSqlSessionFactory(); }重点来了,看看这个buildSqlSessionFactory()方法这里的核心目的就是将configurationProperties解析到Configuration对象中。代码太长了我就不贴出来了, buildSqlSessionFactory()的逻辑我画了个图,有兴趣的小伙伴自行看一下。
Mybatis配置解析1
我们不要陷入细节之中,我们看看中点看看buildSqlSessionFactory() 方法的最后一行this.sqlSessionFactoryBuilder.build(configuration)点进去
public SqlSessionFactory build(Configuration config) { return new DefaultSqlSessionFactory(config); }通过buildSqlSessionFactory()解析得到的Configuration对象创建一个DefaultSqlSessionFactory(config),到此我们就得到了SqlSessionFactory同时被配置成一个bean了。
我们最终操作都是SqlSession,什么时候会通过SqlSessionFactory得到一个SqlSession呢?
要解决这个问题我们回到最开始的MybatisAutoConfiguration的sqlSessionTemplate(SqlSessionFactory sqlSessionFactory)这个方法,点开SqlSessionTemplate发现它是一个实现了SqlSession到这里我们猜测就是在这里SqlSessionFactory会构建一个SqlSession出来。我们进入new SqlSessionTemplate(sqlSessionFactory)看看源码。
public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory) { this(sqlSessionFactory, sqlSessionFactory.getConfiguration().getDefaultExecutorType()); }再往下看,我们就看到了
public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) { notNull(sqlSessionFactory, "Property sqlSessionFactory is required"); notNull(executorType, "Property executorType is required"); this.sqlSessionFactory = sqlSessionFactory; this.executorType = executorType; this.exceptionTranslator = exceptionTranslator; this.sqlSessionProxy = (SqlSession) newProxyInstance( SqlSessionFactory.class.getClassLoader(), new Class[] { SqlSession.class }, new SqlSessionInterceptor()); }这里通过动态代理创建了一个SqlSession。
参数映射、SQL解析
我们先看一下MapperFactoryBean类,这个类实现了FactoryBean在bean初始化的时候会调用getObject()方法我们看看这个类下重写的getObject()方法里的内容。
@Override public T getObject() throws Exception { return getSqlSession().getMapper(this.mapperInterface); }这里调用了sqlSession的getMapper()方法。一层一层点进去里面返回的是一个代理对象。最后的执行是由MapperProxy执行。
public <T> T getMapper(Class<T> type, SqlSession sqlSession) { final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type); if (mapperProxyFactory == null) { throw new BindingException("Type " + type + " is not known to the MapperRegistry."); } try { return mapperProxyFactory.newInstance(sqlSession); } catch (Exception e) { throw new BindingException("Error getting mapper instance. Cause: " + e, e); } }接下来的流程我还是画个流程图,防止小伙伴们走丢。我这里的内容可能未必完全和小标题一样,我主要按照sql执行的流程讲解的。
Mybatis参数绑定
先看一下MapperProxy中的invoke方法,cachedMapperMethod()方法将MapperMethod缓存起来了。
@Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { try { if (Object.class.equals(method.getDeclaringClass())) { return method.invoke(this, args); } else if (isDefaultMethod(method)) { return invokeDefaultMethod(proxy, method, args); } } catch (Throwable t) { throw ExceptionUtil.unwrapThrowable(t); } final MapperMethod mapperMethod = cachedMapperMethod(method); return mapperMethod.execute(sqlSession, args); } private MapperMethod cachedMapperMethod(Method method) { MapperMethod mapperMethod = methodCache.get(method); if (mapperMethod == null) { mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()); methodCache.put(method, mapperMethod); } return mapperMethod; }我们在往下看mapperMethod.execute(sqlSession, args)方法。
public Object execute(SqlSession sqlSession, Object[] args) { Object result; switch (command.getType()) { case INSERT: { Object param = method.convertArgsToSqlCommandParam(args); result = rowCountResult(sqlSession.insert(command.getName(), param)); break; } case UPDATE: { Object param = method.convertArgsToSqlCommandParam(args); result = rowCountResult(sqlSession.update(command.getName(), param)); break; } case DELETE: { Object param = method.convertArgsToSqlCommandParam(args); result = rowCountResult(sqlSession.delete(command.getName(), param)); break; } case SELECT: if (method.returnsVoid() && method.hasResultHandler()) { executeWithResultHandler(sqlSession, args); result = null; } else if (method.returnsMany()) { result = executeForMany(sqlSession, args); } else if (method.returnsMap()) { result = executeForMap(sqlSession, args); } else if (method.returnsCursor()) { result = executeForCursor(sqlSession, args); } else { Object param = method.convertArgsToSqlCommandParam(args); result = sqlSession.selectOne(command.getName(), param); } break; case FLUSH: result = sqlSession.flushStatements(); break; default: throw new BindingException("Unknown execution method for: " + command.getName()); } if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) { throw new BindingException("Mapper method " + command.getName() + " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ")."); } return result; }method.convertArgsToSqlCommandParam(args)这里就是处理参数转换的逻辑。还有很多细节由于篇幅有限以及时间仓促我们不做过多的赘述,感兴趣的小伙伴可以结合上面的图自己看看。下面我们看SQL的执行流程是怎么样的。整体流程如下图。
Mybatis执行流程
我们就不对每一个执行器都分析,我只挑一个SimpleExecutor来具体跟一下源码。我们还是先看看图吧,防止自己把自己搞蒙。
以simpleExecutor为例的执行流程
@Override public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException { Statement stmt = null; try { Configuration configuration = ms.getConfiguration(); StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql); stmt = prepareStatement(handler, ms.getStatementLog()); return handler.<E>query(stmt, resultHandler); } finally { closeStatement(stmt); } }这里获取了Configuration,创建了一个StatementHandler,预处理操作,具体执行的根据创建的预处理方法,最后执行query方法
@Override public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException { String sql = boundSql.getSql(); statement.execute(sql); return resultSetHandler.<E>handleResultSets(statement); }到此我们整理了整个Mybatis的执行流程,分析了其中的源码,由于篇幅有限很多地方都没有细致的分析,但是也贴出了图,希望能帮助到你。
很赞哦!(5)
上一篇: 公司和个人选域名方法一样吗?有什么不同?
下一篇: 国际域名转移的费用和处理步骤是什么?
相关文章
- 只要我们做的是从目前的市场情况选择域名,从简单易记,从个性特征上,我们就可以找到一个好域名进行注册。域名注册进行域名记录和解析以及绑定网站后,客户可以通过URL登录您的网站。
- 2020 智能网联 “新四跨”活动成功举办 百度Apollo自主研发C-V2X成果强势登场
- C语言中几种特殊标准定义和用法
- 有趣的 Scala 语言:简洁的 Scala 语法
- 互联网中的地址是数字的IP地址,域名解析的作用主要就是为了便于记忆。
- ReentrantLock核心原理,绝对干货
- 分布式系统的一些基础理论
- 2年了,那些运行了Github上灭霸脚本的人后来都怎么样了?
- 第五步:重复第四步,直到找到正确的纪录。
- IT工程师都需要掌握的容器技术之Docker容器网络