Mybatis和Spring是如何整合的

176 阅读2分钟

需要了解的前置知识:

  • Bean的生命周期
  • JDK动态代理
  • FactoryBean实例化

在spring的配置文件中引入下面两个bean,spring启动的时候,会加载这两个bean

<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
    <property name="dataSource" ref="dataSource"/>
    <property name="configLocation" value="classpath:mybatis-config.xml"/>
    <property name="mapperLocations" value="classpath:mapper/*.xml"/>
    <property name="typeAliasesPackage" value="com.tt.spring.demo.pojo"/>
</bean>

<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
    <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
    <property name="basePackage" value="com.tt.spring.demo.dao"/>
</bean>

MapperScannerConfigurer实现了BeanDefinitionRegistryPostProcessor,因此spring会先进行实例化,并调用postProcessBeanDefinitionRegistry方法扫描BeanDefinition

在MapperScannerConfigurer类的postProcessBeanDefinitionRegistry方法中,创建了ClassPathMapperScanner对象,并调用了scan方法进行bean扫描

public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
    if (this.processPropertyPlaceHolders) {
      processPropertyPlaceHolders();
    }

    ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);

	// ...省略一堆set方法
    
    scanner.scan(
        StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
  }

在ClassPathMapperScanner的doScan方法中,根据指定的basePackages,扫描指定的bean(通过重写isCandidateComponent方法,让接口可以作为beanDefinition)

  protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
    return beanDefinition.getMetadata().isInterface() && beanDefinition.getMetadata().isIndependent();
  }

扫描完所有指定路径下的beanDefinition后,调用MapperScannerConfigurer的processBeanDefinitions方法对beanDefinition进行增强,我们重点看两个地方:

  private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
      for (BeanDefinitionHolder holder : beanDefinitions) {
        definition.setBeanClass(this.mapperFactoryBeanClass);	// (1)
        
      if (StringUtils.hasText(this.sqlSessionFactoryBeanName)) {
          // 我们在上面定义了sqlSessionFactoryBeanName,因此会走这个分支
        definition.getPropertyValues().add("sqlSessionFactory",
            new RuntimeBeanReference(this.sqlSessionFactoryBeanName)); // (2)
        explicitFactoryUsed = true;
      } else if (this.sqlSessionFactory != null) {
        definition.getPropertyValues().add("sqlSessionFactory", this.sqlSessionFactory);
        explicitFactoryUsed = true;
      }
  }
  • 代码(1)处,设置了beanClass,该beanDefinition的实例实际就是mapperFactoryBeanClass对应的实例,默认为MapperFactoryBean.class
  • 代码(2)处,spring创建实例时会先创建sqlSessionFactoryBeanName实例,然后调用setSqlSessionFactory方法设置该属性

最后spring将这些beanDefinitions都注册到spring中,等到后面将这些beanDefinition实例化

实例化:

由于在BeanDefinition中设置了beanClass为MapperFactoryBean,因此实例化该bean实际创建的是MapperFactoryBean对象,它是一个FactoryBean,会调用getObject方法创建bean对象

下面我们看下MapperFactoryBean的getObject方法

  // MapperFactoryBean.class
  @Override
  public T getObject() throws Exception {
    return getSqlSession().getMapper(this.mapperInterface);
  }
  // MapperRegistry.class
  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);	(1)
    } catch (Exception e) {
      throw new BindingException("Error getting mapper instance. Cause: " + e, e);
    }
  }

在代码(1)处真正实例化,继续往里看如何实例化的

  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<>(sqlSession, mapperInterface, methodCache);
    return newInstance(mapperProxy);
  }

实际就是创建了MapperProxy的代理对象,mapperInterface就是我们在Dao层创建的mapper接口,因此当我们在Service层注入的Dao层对象,实际就是MapperProxy的代理对象,调用入口就是MapperProxy类的invoke方法,最终会调用下面的方法执行增删改查操作

  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:
        // ...省略部分代码
        result = sqlSession.selectOne(command.getName(), param);
        break;
      case FLUSH:
        result = sqlSession.flushStatements();
        break;
      default:
        throw new BindingException("Unknown execution method for: " + command.getName());
    }
    // ...省略部分代码
    return result;
  }