Mybatis源码分析

基于Spring boot来分析Mybatis Mapper类生成过程以及自动配置过程,需要对spring boot的自动配置有一定的了解。

Mapper自动装配原理

开门见山,先分析一下Mybatis源码代码入口,可以从Mybatis自动装配入手。

使用mybatis的自动装配终会引入这么一个依赖:mybatis-spring-boot-autoconfigure.这个包可以作为跟踪入口。

来看到这个包里面的这个文件,里面配置了自动装配所需要的配置类

/org/mybatis/spring/boot/mybatis-spring-boot-autoconfigure/1.3.2/mybatis-spring-boot-autoconfigure-1.3.2.jar!/META-INF/spring.factories

内容

# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration

我们主要看MybatisAutoConfiguration类,这里面进行了Mapper动态类的生成与映射。

我们看org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration.MapperScannerRegistrarNotFoundConfiguration,很明显的是一个配置bean,通过@Import注解引入了AutoConfiguredMapperScannerRegistrar配置来进行Bean信息注册。可以推测这里的Bean信息注册应该是Mapper代理类的注册,那么Mapper代理类生成的入口也可以推断为在这里面。

@org.springframework.context.annotation.Configuration
@Import({ AutoConfiguredMapperScannerRegistrar.class })
@ConditionalOnMissingBean(MapperFactoryBean.class)
public static class MapperScannerRegistrarNotFoundConfiguration {

  @PostConstruct
  public void afterPropertiesSet() {
    logger.debug("No {} found.", MapperFactoryBean.class.getName());
  }
}

继续看如下所示org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration.AutoConfiguredMapperScannerRegistrar源码的注册Bean信息方法。可以推测 scanner.doScan(StringUtils.toStringArray(packages))扫描Mapper应该进行了Mapper动态类生成与注册。

@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {

  logger.debug("Searching for mappers annotated with @Mapper");

  ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);

  try {
    if (this.resourceLoader != null) {
      scanner.setResourceLoader(this.resourceLoader);
    }
	//获取包路径
    List<String> packages = AutoConfigurationPackages.get(this.beanFactory);
    if (logger.isDebugEnabled()) {
      for (String pkg : packages) {
        logger.debug("Using auto-configuration base package '{}'", pkg);
      }
    }

    scanner.setAnnotationClass(Mapper.class);
    scanner.registerFilters();
      //执行扫描Mapper
    scanner.doScan(StringUtils.toStringArray(packages));
  } catch (IllegalStateException ex) {
    logger.debug("Could not determine auto-configuration package, automatic mapper scanning disabled.", ex);
  }
}

继续跟踪,doScan

@Override
public Set<BeanDefinitionHolder> doScan(String... basePackages) {
    //扫描并获取BeanDefintion
  Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);

  if (beanDefinitions.isEmpty()) {
    logger.warn("No MyBatis mapper was found in '" + Arrays.toString(basePackages) + "' package. Please check your configuration.");
  } else {
      //根据BeanDefintion生成一个Mapper代理类并且注册
    processBeanDefinitions(beanDefinitions);
  }

  return beanDefinitions;
}

继续跟踪processBeanDefinitions,方法特别长,概括来说主要就是利用FactoryBean的方式进行Mapper代理类的注册,而代理类Bean的生成自然就在FactoryBean内部完成。下面的逻辑可以简单看看,主要看FactoryBean的getBean方法。

private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
  GenericBeanDefinition definition;
  for (BeanDefinitionHolder holder : beanDefinitions) {
    definition = (GenericBeanDefinition) holder.getBeanDefinition();

    if (logger.isDebugEnabled()) {
      logger.debug("Creating MapperFactoryBean with name '" + holder.getBeanName() 
        + "' and '" + definition.getBeanClassName() + "' mapperInterface");
    }

    // the mapper interface is the original class of the bean
    // but, the actual class of the bean is MapperFactoryBean
     //设置FactoryBean所需要产出的Mapper 代理Bean的接口类型
   definition.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName()); // issue #59
   //设置FactoryBean的类型,实际就是org.mybatis.spring.mapper.MapperFactoryBean
    definition.setBeanClass(this.mapperFactoryBean.getClass());

    definition.getPropertyValues().add("addToConfig", this.addToConfig);

    boolean explicitFactoryUsed = false;
    if (StringUtils.hasText(this.sqlSessionFactoryBeanName)) {
      definition.getPropertyValues().add("sqlSessionFactory", new RuntimeBeanReference(this.sqlSessionFactoryBeanName));
      explicitFactoryUsed = true;
    } else if (this.sqlSessionFactory != null) {
      definition.getPropertyValues().add("sqlSessionFactory", this.sqlSessionFactory);
      explicitFactoryUsed = true;
    }

    if (StringUtils.hasText(this.sqlSessionTemplateBeanName)) {
      if (explicitFactoryUsed) {
        logger.warn("Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
      }
      definition.getPropertyValues().add("sqlSessionTemplate", new RuntimeBeanReference(this.sqlSessionTemplateBeanName));
      explicitFactoryUsed = true;
    } else if (this.sqlSessionTemplate != null) {
      if (explicitFactoryUsed) {
        logger.warn("Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
      }
      definition.getPropertyValues().add("sqlSessionTemplate", this.sqlSessionTemplate);
      explicitFactoryUsed = true;
    }

    if (!explicitFactoryUsed) {
      if (logger.isDebugEnabled()) {
        logger.debug("Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'.");
      }
      definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
    }
  }
}

继续跟踪org.mybatis.spring.mapper.MapperFactoryBean源码,getObject方法如下,到这里就可以知道getSqlSession().getMapper(this.mapperInterface);应该就是生成Mapper代理类的地方,然后这里返回的代理类通过FactoryBean注册到spring容器中。

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

Mapper代理类的生成过程

跟踪getSqlSession().getMapper(this.mapperInterface);可以知道Mapper生成过程。

跟踪调用链:

  • org.apache.ibatis.session.defaults.DefaultSqlSession#getMapper
    • org.apache.ibatis.session.Configuration#getMapper
      • org.apache.ibatis.binding.MapperRegistry#getMapper

来到这里源码,通过代理类工厂mapperProxyFactory新加你一个实例,这个实例就是Mapper的代理类。那么

mapperProxyFactory是何时生成的?给个思路,其实就是在org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration#sqlSessionFactory这个方法里面包含了这个过程。也就是在构建sqlSessionFactory的时候生成mapperProxyFactory,大概过程就是

  • 扫描Mapper文件
  • 解析xml
  • 构建mapperProxyFactory
  • 放入knownMappers
@SuppressWarnings("unchecked")
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);
  }
}

继续跟踪mapperProxyFactory.newInstance(sqlSession);很传统的Java动态代理代码,那么代理逻辑想必都是在mapperProxy的invoke中实现的。

@SuppressWarnings("unchecked")
protected T newInstance(MapperProxy<T> mapperProxy) {
  return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}

public T newInstance(SqlSession sqlSession) {
  final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
  return newInstance(mapperProxy);
}

跟踪org.apache.ibatis.binding.MapperProxy#invoke,就可以看到最终依靠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);
  }
    //根据接口方法获取MapperMethod对象,对象包含返回对象类型,映射sql等
  final MapperMethod mapperMethod = cachedMapperMethod(method);
    //Mapper方法执行过程
  return mapperMethod.execute(sqlSession, args);
}

总结上面两点过程就是一个了解Mapper接口是如何生成动态类来执行操作以及如何注册到容器的过程。下面分析的是Mapper代理类是如何实际执行的。包括参数映射,结果集映射等。

执行过程

跟踪mapperMethod.execute方法内部,接着以执行executeForMany(sqlSession, args);为例往下跟踪,会执行sqlSession.selectList(command.getName(), param);接下来实际执行会通过代理的方式执行一些预先处理逻辑,我们跳过跟踪,直接在org.apache.ibatis.session.defaults.DefaultSqlSession#selectList(java.lang.String, java.lang.Object, org.apache.ibatis.session.RowBounds)断点查看。

  private <E> Object executeForMany(SqlSession sqlSession, Object[] args) {
    List<E> result;
    Object param = method.convertArgsToSqlCommandParam(args);
    if (method.hasRowBounds()) {
      RowBounds rowBounds = method.extractRowBounds(args);
      result = sqlSession.<E>selectList(command.getName(), param, rowBounds);
    } else {
      result = sqlSession.<E>selectList(command.getName(), param);
    }
    // issue #510 Collections & arrays support
    if (!method.getReturnType().isAssignableFrom(result.getClass())) {
      if (method.getReturnType().isArray()) {
        return convertToArray(result);
      } else {
        return convertToDeclaredCollection(sqlSession.getConfiguration(), result);
      }
    }
    return result;
  }

DefaultSqlSession#selectList(java.lang.String, java.lang.Object, org.apache.ibatis.session.RowBounds)代码执行selectList;针对下面的代码做一下解析。

首先是根据statement获取到在解析xml文件配置得到的配置对象,然后我们Mybatis中最重要的Executor会担任执行者角色,实际执行数据库查询操作。

但是需要注意的是如果我们使用了插件,那么 executor.query这个操作将会被代理执行,换句话说应该是executor本身就是一个代理类,里面的代理逻辑包装了实际executor.query逻辑。这里暂时不分析有插件的情况。

@Override
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
  try {
      //从配置获取Mapper xml文件解析得到的sql,参数,结果类型等信息对象
    MappedStatement ms = configuration.getMappedStatement(statement);
      //如果有配置插件,则通过代理执行;否则直接对应executor实例的query方法。
    return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
  } catch (Exception e) {
    throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
  } finally {
    ErrorContext.instance().reset();
  }
}

在无插件的情况下继续跟踪以下调用链路

  • org.apache.ibatis.executor.CachingExecutor#query(org.apache.ibatis.mapping.MappedStatement, java.lang.Object, org.apache.ibatis.session.RowBounds, org.apache.ibatis.session.ResultHandler, org.apache.ibatis.cache.CacheKey, org.apache.ibatis.mapping.BoundSql)
    • org.apache.ibatis.executor.BaseExecutor#query(org.apache.ibatis.mapping.MappedStatement, java.lang.Object, org.apache.ibatis.session.RowBounds, org.apache.ibatis.session.ResultHandler, org.apache.ibatis.cache.CacheKey, org.apache.ibatis.mapping.BoundSql)
      • org.apache.ibatis.executor.BaseExecutor#queryFromDatabase
private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
  List<E> list;
    //设置标志位,防止并发使用当前查询请求时,前一个请求没完成,后一个请求用了未完成请求的一级缓存
  localCache.putObject(key, EXECUTION_PLACEHOLDER);
  try {
    list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
  } finally {
      //完成请求,去除标记
    localCache.removeObject(key);
  }
  localCache.putObject(key, list);
  if (ms.getStatementType() == StatementType.CALLABLE) {
    localOutputParameterCache.putObject(key, parameter);
  }
  return list;
}

继续跟踪doQuery

@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
    StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
      //Statement处理操作,类似于jdbc中Statement相关操作封装。获取连接,Statement等
    stmt = prepareStatement(handler, ms.getStatementLog());
    return handler.<E>query(stmt, resultHandler);
  } finally {
    closeStatement(stmt);
  }
}

在这里改为由StatementHandler继续执行,继续跟踪handler.query(stmt, resultHandler);

  • org.apache.ibatis.executor.statement.RoutingStatementHandler#query
    • org.apache.ibatis.executor.statement.PreparedStatementHandler#query

下面都是熟悉的jdbc代码,最终resultSetHandler. handleResultSets(ps);处理返回的查询结果,并且映射为用户配置的对象

@Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
  PreparedStatement ps = (PreparedStatement) statement;
  ps.execute();
  return resultSetHandler.<E> handleResultSets(ps);
}

结果集映射

resultSetHandler. handleResultSets(ps);跟踪下去就是结果集映射处理。可以推测的是根据根据结果集,利用反射机制进行新对象的创建以及值的设置。实际上实现也是这样,可以直接在

org.apache.ibatis.executor.resultset.DefaultResultSetHandler#getRowValue(org.apache.ibatis.executor.resultset.ResultSetWrapper, org.apache.ibatis.mapping.ResultMap)断点处理查看。

private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap) throws SQLException {
  final ResultLoaderMap lazyLoader = new ResultLoaderMap();
    //创建结果对象
  Object rowValue = createResultObject(rsw, resultMap, lazyLoader, null);
  if (rowValue != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
    final MetaObject metaObject = configuration.newMetaObject(rowValue);
    boolean foundValues = this.useConstructorMappings;
    if (shouldApplyAutomaticMappings(resultMap, false)) {
        //给结果设置属性值
      foundValues = applyAutomaticMappings(rsw, resultMap, metaObject, null) || foundValues;
    }
    foundValues = applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, null) || foundValues;
    foundValues = lazyLoader.size() > 0 || foundValues;
    rowValue = foundValues || configuration.isReturnInstanceForEmptyRow() ? rowValue : null;
  }
  return rowValue;
}

插件是如何工作的?

回到之前说的executor执行可能会被代理的代码,如果有插件的话,这里的executor就是一个代理对象。下面看是如何实现的。以分页插件为例。

@Override
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
  try {
      //从配置获取Mapper xml文件解析得到的sql,参数,结果类型等信息对象
    MappedStatement ms = configuration.getMappedStatement(statement);
      //如果有配置插件,则通过代理执行;否则直接对应executor实例的query方法。
    return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
  } catch (Exception e) {
    throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
  } finally {
    ErrorContext.instance().reset();
  }
}

先看executor是如何注入到这个类的,代码如下。是在创建DefaultSqlSession的时候就注入的,猜测代理对象的生成逻辑应该就是在新建DefaultSqlSession时一起进行的。

public DefaultSqlSession(Configuration configuration, Executor executor, boolean autoCommit) {
  this.configuration = configuration;
  this.executor = executor;
  this.dirty = false;
  this.autoCommit = autoCommit;
}

跟踪DefaultSqlSession新建的逻辑,都会有以下代码

final Executor executor = configuration.newExecutor(tx, execType);
return new DefaultSqlSession(configuration, executor, autoCommit);

所以是在configuration.newExecutor(tx, execType)中建立executor,代码如下。在这里所有插件将会对executor进行包装带来。

public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
  executorType = executorType == null ? defaultExecutorType : executorType;
  executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
  Executor executor;
  if (ExecutorType.BATCH == executorType) {
    executor = new BatchExecutor(this, transaction);
  } else if (ExecutorType.REUSE == executorType) {
    executor = new ReuseExecutor(this, transaction);
  } else {
    executor = new SimpleExecutor(this, transaction);
  }
  if (cacheEnabled) {
    executor = new CachingExecutor(executor);
  }
    //所有插件对executor进行包装代理
  executor = (Executor) interceptorChain.pluginAll(executor);
  return executor;
}

代理逻辑。可以看出如果是多个插件,还会对已经包装的对象再进行一次包装,多重包装。

public Object pluginAll(Object target) {
  for (Interceptor interceptor : interceptors) {
    target = interceptor.plugin(target);
  }
  return target;
}

我们以分页插件为例看一下,包装逻辑的实现。com.github.pagehelper.PageInterceptor#plugin

@Override
public Object plugin(Object target) {
    //TODO Spring bean 方式配置时,如果没有配置属性就不会执行下面的 setProperties 方法,就不会初始化,因此考虑在这个方法中做一次判断和初始化
    //TODO https://github.com/pagehelper/Mybatis-PageHelper/issues/26
    return Plugin.wrap(target, this);
}

org.apache.ibatis.plugin.Plugin#wrap

public static Object wrap(Object target, Interceptor interceptor) {
  Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
  Class<?> type = target.getClass();
    //判断插件是否适用当前目标
  Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
  if (interfaces.length > 0) {
      //生成代理类
    return Proxy.newProxyInstance(
        type.getClassLoader(),
        interfaces,
        new Plugin(target, interceptor, signatureMap));
  }
  return target;
}

根据上面的所有信息可以得出一个结论就是代理逻辑都是放在Plugin中的invoke。在这里面再调用插件的拦截逻辑。分页插件的话是会计算一个分页sql传递给executor的执行方法,从而实现分页。

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  try {
    Set<Method> methods = signatureMap.get(method.getDeclaringClass());
    if (methods != null && methods.contains(method)) {
        //调用拦截器的拦截逻辑,里面会继续调用executor的执行方法
      return interceptor.intercept(new Invocation(target, method, args));
    }
    return method.invoke(target, args);
  } catch (Exception e) {
    throw ExceptionUtil.unwrapThrowable(e);
  }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值