关于mybatis:Mybatis原理及源码分析

1次阅读

共计 9916 个字符,预计需要花费 25 分钟才能阅读完成。

Mybatis 原理及源码剖析

作为 Java 程序员 Mybatis 应该是一个必会框架了,其源码体量只有 Spring 的 1 /5,也是 Hibernate 的 1 /5,相比于其余风行框架 Mybatis 源码无疑是学习老本最低的,当做年轻人看的第一个框架源码,无疑是十分好的。

整体架构

对于一个生疏的事物切勿一头扎进细节里,咱们先要观其大略看看架构脉络,MyBatis 分为三层架构,别离是根底撑持层、外围解决层和接口层。

Mybatis 整体架构

根底撑持层

根底撑持层是这个 Mybatis 框架的基建,为整个 Mybatis 框架提供十分根底的性能。(篇幅无限上面咱们只对局部模块做简略的剖析)

  1. 类型转换模块,咱们在 Mybatis 中应用 < typeAliase > 标签定义一个别名就是应用类型转换模块实现的。类型转换模块最重要的性能还是实现了 Mybatis 中 JDBC 类型和 Java 类型之间的转换。次要体现在:
  2. 在 SQL 模板绑定用户参入实参的场景中,将 Java 类型转换成 JDBC 类型
  3. 在后果集中,将 JDBC 类型转换成 Java 类型。

    Mybatis 类型转换

  4. 日志模块,产生日志,定位异样。
  5. 反射工具,对原生的 Java 反射作了一些封装。
  6. Binding 模块 ,咱们在执行 Mybatis 中的办法时都是通过 SqlSession 来获 Mapper 接口中的代理,这个代理将 Mapper.xml 文件中 SQL 进行关联就是通过 Binding 模块来实现的。值得注意点是 这个过程是产生在编译期。能够将谬误提前到编译期。
  7. 数据源模块,数据源对于一个 ORM 来说算是十分外围的组件之一了。Mybatis 默认的数据源是十分杰出的,Mybatis 同时也反对集成第三方的数据源。
  8. 缓存模块,缓存模块的好坏间接影响这个 ORM 的性能。Mybatis 提供了两级缓存,同时也反对第三方缓存中间件集成。

    Mybatis 缓存

  9. 解析器模块,次要是 config.xml 配置文件解析,和 mapper.xml 文件解析。
  10. 事务管理模块,事务模块对数据库的事务机制进行管制,对数据库事务进行了形象,同时也对 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 呢?

要解决这个问题咱们回到最开始的 MybatisAutoConfigurationsqlSessionTemplate(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);
  }

这里调用了 sqlSessiongetMapper()办法。一层一层点进去外面返回的是一个代理对象。最初的执行是由 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 的执行流程,剖析了其中的源码,因为篇幅无限很多中央都没有粗疏的剖析,然而也贴出了图,心愿能帮忙到你。

正文完
 0