Mybatis原理及源码分析

本文转载自微信公众号「三不猴子」,作者sanbuhouzi。转载本文请联系三不猴子公众号。

成都创新互联公司是一家集成都网站设计、成都做网站、网站页面设计、网站优化SEO优化为一体的专业的建站公司,已为成都等多地近百家企业提供网站建设服务。追求良好的浏览体验,以探求精品塑造与理念升华,设计最适合用户的网站页面。 合作只是第一步,服务才是根本,我们始终坚持讲诚信,负责任的原则,为您进行细心、贴心、认真的服务,与众多客户在蓬勃发展的市场环境中,互促共生。

作为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。

 
 
 
 
  1. @Bean
  2. @ConditionalOnMissingBean
  3. public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
  4.   SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
  5.   factory.setDataSource(dataSource);
  6.   factory.setVfs(SpringBootVFS.class);
  7.   if (StringUtils.hasText(this.properties.getConfigLocation())) {
  8.     factory.setConfigLocation(this.resourceLoader.getResource(this.properties.getConfigLocation()));
  9.   }
  10.   Configuration configuration = this.properties.getConfiguration();
  11.   if (configuration == null && !StringUtils.hasText(this.properties.getConfigLocation())) {
  12.     configuration = new Configuration();
  13.   }
  14.   if (configuration != null && !CollectionUtils.isEmpty(this.configurationCustomizers)) {
  15.     for (ConfigurationCustomizer customizer : this.configurationCustomizers) {
  16.       customizer.customize(configuration);
  17.     }
  18.   }
  19.   factory.setConfiguration(configuration);
  20.   if (this.properties.getConfigurationProperties() != null) {
  21.     factory.setConfigurationProperties(this.properties.getConfigurationProperties());
  22.   }
  23.   if (!ObjectUtils.isEmpty(this.interceptors)) {
  24.     factory.setPlugins(this.interceptors);
  25.   }
  26.   if (this.databaseIdProvider != null) {
  27.     factory.setDatabaseIdProvider(this.databaseIdProvider);
  28.   }
  29.   if (StringUtils.hasLength(this.properties.getTypeAliasesPackage())) {
  30.     factory.setTypeAliasesPackage(this.properties.getTypeAliasesPackage());
  31.   }
  32.   if (StringUtils.hasLength(this.properties.getTypeHandlersPackage())) {
  33.     factory.setTypeHandlersPackage(this.properties.getTypeHandlersPackage());
  34.   }
  35.   if (!ObjectUtils.isEmpty(this.properties.resolveMapperLocations())) {
  36.     factory.setMapperLocations(this.properties.resolveMapperLocations());
  37.   }
  38.   return factory.getObject();
  39. }

这里是通过MybatisProperties里面的配置并放入到SqlSessionFactoryBean中,再由SqlSessionFactoryBean得到SqlSessionFactory。看到最后一行return factory.getObject();我们进去看看这个factory.getObject()的逻辑是如何得到一个SqlSessionFactory。

 
 
 
 
  1. @Override
  2. public SqlSessionFactory getObject() throws Exception {
  3.   if (this.sqlSessionFactory == null) {
  4.     afterPropertiesSet();
  5.   }
  6.   return this.sqlSessionFactory;
  7. }

这一步没什么好说的,看看afterPropertiesSet()方法

 
 
 
 
  1. @Override
  2. public void afterPropertiesSet() throws Exception {
  3.   notNull(dataSource, "Property 'dataSource' is required");
  4.   notNull(sqlSessionFactoryBuilder, "Property 'sqlSessionFactoryBuilder' is required");
  5.   state((configuration == null && configLocation == null) || !(configuration != null && configLocation != null),
  6.             "Property 'configuration' and 'configLocation' can not specified with together");
  7.   this.sqlSessionFactory = buildSqlSessionFactory();
  8. }

重点来了,看看这个buildSqlSessionFactory()方法这里的核心目的就是将configurationProperties解析到Configuration对象中。代码太长了我就不贴出来了, buildSqlSessionFactory()的逻辑我画了个图,有兴趣的小伙伴自行看一下。

Mybatis配置解析1

我们不要陷入细节之中,我们看看中点看看buildSqlSessionFactory() 方法的最后一行this.sqlSessionFactoryBuilder.build(configuration)点进去

 
 
 
 
  1. public SqlSessionFactory build(Configuration config) {
  2.     return new DefaultSqlSessionFactory(config);
  3.   }

通过buildSqlSessionFactory()解析得到的Configuration对象创建一个DefaultSqlSessionFactory(config),到此我们就得到了SqlSessionFactory同时被配置成一个bean了。

我们最终操作都是SqlSession,什么时候会通过SqlSessionFactory得到一个SqlSession呢?

要解决这个问题我们回到最开始的MybatisAutoConfiguration的sqlSessionTemplate(SqlSessionFactory sqlSessionFactory)这个方法,点开SqlSessionTemplate发现它是一个实现了SqlSession到这里我们猜测就是在这里SqlSessionFactory会构建一个SqlSession出来。我们进入new SqlSessionTemplate(sqlSessionFactory)看看源码。

 
 
 
 
  1. public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
  2.    this(sqlSessionFactory, sqlSessionFactory.getConfiguration().getDefaultExecutorType());
  3.  }

再往下看,我们就看到了

 
 
 
 
  1. public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType,
  2.     PersistenceExceptionTranslator exceptionTranslator) {
  3.   notNull(sqlSessionFactory, "Property 'sqlSessionFactory' is required");
  4.   notNull(executorType, "Property 'executorType' is required");
  5.   this.sqlSessionFactory = sqlSessionFactory;
  6.   this.executorType = executorType;
  7.   this.exceptionTranslator = exceptionTranslator;
  8.   this.sqlSessionProxy = (SqlSession) newProxyInstance(
  9.       SqlSessionFactory.class.getClassLoader(),
  10.       new Class[] { SqlSession.class },
  11.       new SqlSessionInterceptor());
  12. }

这里通过动态代理创建了一个SqlSession。

参数映射、SQL解析

我们先看一下MapperFactoryBean类,这个类实现了FactoryBean在bean初始化的时候会调用getObject()方法我们看看这个类下重写的getObject()方法里的内容。

 
 
 
 
  1. @Override
  2.  public T getObject() throws Exception {
  3.    return getSqlSession().getMapper(this.mapperInterface);
  4.  }

这里调用了sqlSession的getMapper()方法。一层一层点进去里面返回的是一个代理对象。最后的执行是由MapperProxy执行。

 
 
 
 
  1. public  T getMapper(Class type, SqlSession sqlSession) {
  2.     final MapperProxyFactory mapperProxyFactory = (MapperProxyFactory) knownMappers.get(type);
  3.     if (mapperProxyFactory == null) {
  4.       throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
  5.     }
  6.     try {
  7.       return mapperProxyFactory.newInstance(sqlSession);
  8.     } catch (Exception e) {
  9.       throw new BindingException("Error getting mapper instance. Cause: " + e, e);
  10.     }
  11.   }

接下来的流程我还是画个流程图,防止小伙伴们走丢。我这里的内容可能未必完全和小标题一样,我主要按照sql执行的流程讲解的。

Mybatis参数绑定

先看一下MapperProxy中的invoke方法,cachedMapperMethod()方法将MapperMethod缓存起来了。

 
 
 
 
  1. @Override
  2. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  3.   try {
  4.     if (Object.class.equals(method.getDeclaringClass())) {
  5.       return method.invoke(this, args);
  6.     } else if (isDefaultMethod(method)) {
  7.       return invokeDefaultMethod(proxy, method, args);
  8.     }
  9.   } catch (Throwable t) {
  10.     throw ExceptionUtil.unwrapThrowable(t);
  11.   }
  12.   final MapperMethod mapperMethod = cachedMapperMethod(method);
  13.   return mapperMethod.execute(sqlSession, args);
  14. }
  15.  private MapperMethod cachedMapperMethod(Method method) {
  16.     MapperMethod mapperMethod = methodCache.get(method);
  17.     if (mapperMethod == null) {
  18.       mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
  19.       methodCache.put(method, mapperMethod);
  20.     }
  21.     return mapperMethod;
  22.   }

我们在往下看mapperMethod.execute(sqlSession, args)方法。

 
 
 
 
  1. public Object execute(SqlSession sqlSession, Object[] args) {
  2.   Object result;
  3.   switch (command.getType()) {
  4.     case INSERT: {
  5.     Object param = method.convertArgsToSqlCommandParam(args);
  6.       result = rowCountResult(sqlSession.insert(command.getName(), param));
  7.       break;
  8.     }
  9.     case UPDATE: {
  10.       Object param = method.convertArgsToSqlCommandParam(args);
  11.       result = rowCountResult(sqlSession.update(command.getName(), param));
  12.       break;
  13.     }
  14.     case DELETE: {
  15.       Object param = method.convertArgsToSqlCommandParam(args);
  16.       result = rowCountResult(sqlSession.delete(command.getName(), param));
  17.       break;
  18.     }
  19.     case SELECT:
  20.       if (method.returnsVoid() && method.hasResultHandler()) {
  21.         executeWithResultHandler(sqlSession, args);
  22.         result = null;
  23.       } else if (method.returnsMany()) {
  24.         result = executeForMany(sqlSession, args);
  25.       } else if (method.returnsMap()) {
  26.         result = executeForMap(sqlSession, args);
  27.       } else if (method.returnsCursor()) {
  28.         result = executeForCursor(sqlSession, args);
  29.       } else {
  30.         Object param = method.convertArgsToSqlCommandParam(args);
  31.         result = sqlSession.selectOne(command.getName(), param);
  32.       }
  33.       break;
  34.     case FLUSH:
  35.       result = sqlSession.flushStatements();
  36.       break;
  37.     default:
  38.       throw new BindingException("Unknown execution method for: " + command.getName());
  39.   }
  40.   if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
  41.     throw new BindingException("Mapper method '" + command.getName() 
  42.         + " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
  43.   }
  44.   return result;
  45. }

method.convertArgsToSqlCommandParam(args)这里就是处理参数转换的逻辑。还有很多细节由于篇幅有限以及时间仓促我们不做过多的赘述,感兴趣的小伙伴可以结合上面的图自己看看。下面我们看SQL的执行流程是怎么样的。整体流程如下图。

Mybatis执行流程

我们就不对每一个执行器都分析,我只挑一个SimpleExecutor来具体跟一下源码。我们还是先看看图吧,防止自己把自己搞蒙。

以simpleExecutor为例的执行流程

 
 
 
 
  1. @Override
  2. public  List doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
  3.   Statement stmt = null;
  4.   try {
  5.     Configuration configuration = ms.getConfiguration();
  6.     StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
  7.     stmt = prepareStatement(handler, ms.getStatementLog());
  8.     return handler.query(stmt, resultHandler);
  9.   } finally {
  10.     closeStatement(stmt);
  11.   }
  12. }

这里获取了Configuration,创建了一个StatementHandler,预处理操作,具体执行的根据创建的预处理方法,最后执行query方法

 
 
 
 
  1. @Override
  2. public  List query(Statement statement, ResultHandler resultHandler) throws SQLException {
  3.   String sql = boundSql.getSql();
  4.   statement.execute(sql);
  5.   return resultSetHandler.handleResultSets(statement);
  6. }

到此我们整理了整个Mybatis的执行流程,分析了其中的源码,由于篇幅有限很多地方都没有细致的分析,但是也贴出了图,希望能帮助到你。

本文标题:Mybatis原理及源码分析
转载来源:http://www.36103.cn/qtweb/news35/39735.html

网站建设、网络推广公司-创新互联,是专注品牌与效果的网站制作,网络营销seo公司;服务项目有等

广告

声明:本网站发布的内容(图片、视频和文字)以用户投稿、用户转载内容为主,如果涉及侵权请尽快告知,我们将会在第一时间删除。文章观点不代表本网站立场,如需处理请联系客服。电话:028-86922220;邮箱:631063699@qq.com。内容未经允许不得转载,或转载时需注明来源: 创新互联