java面试

mybatis学习笔记

2019-06-12  本文已影响4人  那些年搬过的砖
Mybatis与spring集成时,在spring.xml中会配置两个重要的类,SqlSessionFactoryBean与MapperScannerConfigure。用于完成mybatis的加载与配置。
一、Mybatis核心类图

核心类是SqlSessionFactory和SqlSession

mybatis类图.png
二、Mybatis加载过程

Mybatis自带的加载过程,集成到spring之后,加载过程会有一些变化,主要是bean要提交给spring容器来管理

mybatis加载过程.png
三、SqlSessionFactoryBean介绍
spring配置mybatis的加载入口
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
    <property name="dataSource" ref="dataSource"/>
    <property name="mapperLocations" value="classpath:com/rsms/iot/mapper/*.xml"/>
    <property name="configLocation" value="classpath:mybatis/mybatis-config.xml"/>
</bean>

configLocation指定mybatis的全局配置位置

mapperLocations指定隐射文件的配置位置

SqlSessionFactoryBean实现接口InitializingBean,在bean加载完成后执行afterPropertiesSet完成SqlSessionFactory的创建。SqlSessionFactoryBean重新实现了mybatis原生的SqlSessionFactory的构建过程。在buildSqlSessionFactory()方法中通过XMLConfigBuilder完成对全局配置文件的解析,并构造Configuration对象。Configuration对象是全局唯一的提供了mybatis的所有配置项。

然后解析mapper文件

if (!isEmpty(this.mapperLocations)) {
      for (Resource mapperLocation : this.mapperLocations) {
        if (mapperLocation == null) {
          continue;
        }

        try {
          XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
              configuration, mapperLocation.toString(), configuration.getSqlFragments());
          xmlMapperBuilder.parse();
        } catch (Exception e) {
          throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", e);
        } finally {
          ErrorContext.instance().reset();
        }

        if (LOGGER.isDebugEnabled()) {
          LOGGER.debug("Parsed mapper file: '" + mapperLocation + "'");
        }
      }
    } else {
      if (LOGGER.isDebugEnabled()) {
        LOGGER.debug("Property 'mapperLocations' was not specified or no matching resources found");
      }
}

mapper文件是通过XMLMapperBuilder解析

public void parse() {
    if (!configuration.isResourceLoaded(resource)) {
      configurationElement(parser.evalNode("/mapper"));
      configuration.addLoadedResource(resource);
      bindMapperForNamespace();
    }

    parsePendingResultMaps();
    parsePendingChacheRefs();
    parsePendingStatements();
}
private void configurationElement(XNode context) {
    try {
      String namespace = context.getStringAttribute("namespace");
      if (namespace == null || namespace.equals("")) {
        throw new BuilderException("Mapper's namespace cannot be empty");
      }
      builderAssistant.setCurrentNamespace(namespace);
      cacheRefElement(context.evalNode("cache-ref"));
      cacheElement(context.evalNode("cache"));
      parameterMapElement(context.evalNodes("/mapper/parameterMap"));
      resultMapElements(context.evalNodes("/mapper/resultMap"));
      sqlElement(context.evalNodes("/mapper/sql"));
      //过滤出所有的curd sql片段
      buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing Mapper XML. Cause: " + e, e);
    }
}

循环解析所有mapper.xml文件,过滤出curd sql片段,构造MappedStatment(一个sql是一个MappedStatment)存放到Configuration对象的mappedStatements中,mappedStatements是StrictMap,继承hashmap,在存放的时候会存放两份,一份是id+namespace作为key值,一份是id作为key值。value值就是MappedStatment对象。

在xml文件解析完成之后,通过sqlSessionFactoryBuilder构造SqlSessionFactory对象

this.sqlSessionFactoryBuilder.build(configuration);
public SqlSessionFactory build(Configuration config) {
    return new DefaultSqlSessionFactory(config);
}

这样就完成了SqlSessionFactory创建。

四、MapperScannerConfigurer介绍
<bean id="mapperScannerConfigurer" class="org.mybatis.spring.mapper.MapperScannerConfigurer">
    <property name="basePackage" value="com.rsms.iot.mapper"/>
    <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
</bean>
MapperScannerConfigure的postProcessBeanDefinitionRegistry方法中,会通过ClassPathMapperScanner扫描basePackage下的所有接口。
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
    if (this.processPropertyPlaceHolders) {
      processPropertyPlaceHolders();
    }

    ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
    scanner.setAddToConfig(this.addToConfig);
    scanner.setAnnotationClass(this.annotationClass);
    scanner.setMarkerInterface(this.markerInterface);
    scanner.setSqlSessionFactory(this.sqlSessionFactory);
    scanner.setSqlSessionTemplate(this.sqlSessionTemplate);
    scanner.setSqlSessionFactoryBeanName(this.sqlSessionFactoryBeanName);
    scanner.setSqlSessionTemplateBeanName(this.sqlSessionTemplateBeanName);
    scanner.setResourceLoader(this.applicationContext);
    scanner.setBeanNameGenerator(this.nameGenerator);
    scanner.registerFilters();
    scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
}
ClassPathMapperScanner中通过doScan将原接口类型改造成MapperFactoryBean。为每个接口类创建动态代理。可以从下面源码看出
public Set<BeanDefinitionHolder> doScan(String... basePackages) {
    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 {
      processBeanDefinitions(beanDefinitions);
    }

    return beanDefinitions;
}
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
      // mapper接口是原bean类型,但是实际类型是MapperFactoryBean
      definition.getPropertyValues().add("mapperInterface", definition.getBeanClassName());
      definition.setBeanClass(this.mapperFactoryBean.getClass());

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

      ......
    }
}
上面代码可以看出MapperScannerConfigure是为了避免一个一个定义MapperFactoryBean而添加的批量处理mapper接口的方法,根据basepackages路径,将mapper接口批量改造成MapperFactoryBean,但是元数据中依然保存了原接口类型信息,可以从下图看出。
3.png
五、MapperFactoryBean的装配

上一节中我们知道mapper接口最后都被改造成MapperFactoryBean,而MapperFactoryBean继承SqlSessionDaoSupport,而SqlSessionDaoSupport又继承InitializingBean,所以所有的MapperFactoryBean最后都会执行afterPropertiesSet完成自动装配。(DaoSupport->afterPropertiesSet()方法)

之后通过MapperFactoryBean的getObject获取代理对象(MapperProxy)注入到spring容器中。我们看看spring是如何获取代理对象的。

MapperFactoryBean类的getObject方法:
public T getObject() throws Exception {
    return getSqlSession().getMapper(this.mapperInterface);
}

调用SqlSessionTemplate的getMapper方法:
public <T> T getMapper(Class<T> type) {
    return getConfiguration().getMapper(type, this);
}

接着调用Configuration的getMapper方法:
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    return mapperRegistry.getMapper(type, sqlSession);
}

再调用MapperRegistry的getMapper方法:
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实例化一个代理对象
public T newInstance(SqlSession sqlSession) {
    final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
    return newInstance(mapperProxy);
}

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

Proxy.newProxyInstance就是jdk自带的动态代理完成MapperProxy的构造。
完成每个Mapper接口的代理对象的构造之后就会注入到spring容器管理。
六、mapper的调用过程

根据第三节我们知道,一系列mapper结构改造成MapperFactoryBean后进一步构造出其代理对象MapperProxy,所以调用mapper接口方法时,实际是调用MapperProxy的invoke方法,而该方法中又会调用MapperMethod的execute方法。如下代码

MapperProxy的invoke方法
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    if (Object.class.equals(method.getDeclaringClass())) {
      try {
        return method.invoke(this, args);
      } catch (Throwable t) {
        throw ExceptionUtil.unwrapThrowable(t);
      }
    }
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    return mapperMethod.execute(sqlSession, args);
}

下面以列表查询为例,看下mybatis的执行过程,当然不同的执行语句调用的Executor是不同的,这里仅供参考。这张图片是网上找到的图片(自己用破解版的“某软件”辛辛苦苦画的图没保存下来,破解版好坑爹,懒的再画了)DefaultSqlSession之前,即调用mapper接口时,实际是调用MapperProxy的invoke方法,MapperProxy再调用MapperMethod的execute方法,MapperMethod才会调用DefaultSqlSession。


执行过程.png
上一篇 下一篇

猜你喜欢

热点阅读