SpringBoot环境下Mybatis映射器自动注册的原理

3486 篇文章 107 订阅

先前我们讲了springboot 集成mybatis的过程,只需要通过几步简单的配置,我们就能够通过操作我们的mapper映射器来完成对数据库的操作。不知道大家是否会产生这样的疑惑:我写的映射器只是一个自定义的接口,为啥我就能直接用它来对数据库进行增删改查呢?接下来咱们就来探讨一下这块的原理。

由于mybatis相关的原理涉及的东西较多,所以咱们就对内部的知识点进行逐个的讲解。本篇主要讲解下mybatis starter的作用,以为映射器实例的注册过程。

1、mybatis-spring-boot-starter的自动配置类

我们用springboot集成mybatis时,会在pom文件中引入下面的依赖:

xml复制代码      <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.2.2</version>
        </dependency>

引入之后,maven就会自动帮我们导入mybatis-spring-boot-autoconfigure包。看这个名字,我们能猜想到这是个自动配置包,那我们就免不了要去看下这个jar包里面的META-INF/spring.factories文件了,该文件有如下内容:

ini复制代码# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.mybatis.spring.boot.autoconfigure.MybatisLanguageDriverAutoConfiguration,\
org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration

MybatisAutoConfiguration这个配置类帮我们以下功能:

(1)往spring容器注册SqlSessionFactory和SqlSessionTemplate

(2)往spring容器注册AutoConfiguredMapperScannerRegistrar,后面需要它来完成标记有@Mapper注解的映射器的注册工作

为方便大家预览,此处呈现MybatisAutoConfiguration的代码如下:

kotlin复制代码package org.mybatis.spring.boot.autoconfigure;

import java.beans.PropertyDescriptor;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import javax.sql.DataSource;

import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.mapping.DatabaseIdProvider;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.scripting.LanguageDriver;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.ExecutorType;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.type.TypeHandler;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.mapper.MapperFactoryBean;
import org.mybatis.spring.mapper.MapperScannerConfigurer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanWrapper;
import org.springframework.beans.BeanWrapperImpl;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.ListableBeanFactory;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.boot.autoconfigure.AutoConfigurationPackages;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.env.Environment;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;

/**
 * {@link EnableAutoConfiguration Auto-Configuration} for Mybatis. Contributes a {@link SqlSessionFactory} and a
 * {@link SqlSessionTemplate}.
 *
 * If {@link org.mybatis.spring.annotation.MapperScan} is used, or a configuration file is specified as a property,
 * those will be considered, otherwise this auto-configuration will attempt to register mappers based on the interface
 * definitions in or under the root auto-configuration package.
 *
 * @author Eddú Meléndez
 * @author Josh Long
 * @author Kazuki Shimizu
 * @author Eduardo Macarrón
 */
@org.springframework.context.annotation.Configuration
@ConditionalOnClass({ SqlSessionFactory.class, SqlSessionFactoryBean.class })
@ConditionalOnSingleCandidate(DataSource.class)
@EnableConfigurationProperties(MybatisProperties.class)
@AutoConfigureAfter({ DataSourceAutoConfiguration.class, MybatisLanguageDriverAutoConfiguration.class })
public class MybatisAutoConfiguration implements InitializingBean {

  private static final Logger logger = LoggerFactory.getLogger(MybatisAutoConfiguration.class);

  private final MybatisProperties properties;

  private final Interceptor[] interceptors;

  private final TypeHandler[] typeHandlers;

  private final LanguageDriver[] languageDrivers;

  private final ResourceLoader resourceLoader;

  private final DatabaseIdProvider databaseIdProvider;

  private final List<ConfigurationCustomizer> configurationCustomizers;

  private final List<SqlSessionFactoryBeanCustomizer> sqlSessionFactoryBeanCustomizers;

  public MybatisAutoConfiguration(MybatisProperties properties, ObjectProvider<Interceptor[]> interceptorsProvider,
      ObjectProvider<TypeHandler[]> typeHandlersProvider, ObjectProvider<LanguageDriver[]> languageDriversProvider,
      ResourceLoader resourceLoader, ObjectProvider<DatabaseIdProvider> databaseIdProvider,
      ObjectProvider<List<ConfigurationCustomizer>> configurationCustomizersProvider,
      ObjectProvider<List<SqlSessionFactoryBeanCustomizer>> sqlSessionFactoryBeanCustomizers) {
    this.properties = properties;
    this.interceptors = interceptorsProvider.getIfAvailable();
    this.typeHandlers = typeHandlersProvider.getIfAvailable();
    this.languageDrivers = languageDriversProvider.getIfAvailable();
    this.resourceLoader = resourceLoader;
    this.databaseIdProvider = databaseIdProvider.getIfAvailable();
    this.configurationCustomizers = configurationCustomizersProvider.getIfAvailable();
    this.sqlSessionFactoryBeanCustomizers = sqlSessionFactoryBeanCustomizers.getIfAvailable();
  }

  @Override
  public void afterPropertiesSet() {
    checkConfigFileExists();
  }

  private void checkConfigFileExists() {
    if (this.properties.isCheckConfigLocation() && StringUtils.hasText(this.properties.getConfigLocation())) {
      Resource resource = this.resourceLoader.getResource(this.properties.getConfigLocation());
      Assert.state(resource.exists(),
          "Cannot find config location: " + resource + " (please add config file or check your Mybatis configuration)");
    }
  }

  @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()));
    }
    applyConfiguration(factory);
    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 (this.properties.getTypeAliasesSuperType() != null) {
      factory.setTypeAliasesSuperType(this.properties.getTypeAliasesSuperType());
    }
    if (StringUtils.hasLength(this.properties.getTypeHandlersPackage())) {
      factory.setTypeHandlersPackage(this.properties.getTypeHandlersPackage());
    }
    if (!ObjectUtils.isEmpty(this.typeHandlers)) {
      factory.setTypeHandlers(this.typeHandlers);
    }
    if (!ObjectUtils.isEmpty(this.properties.resolveMapperLocations())) {
      factory.setMapperLocations(this.properties.resolveMapperLocations());
    }
    Set<String> factoryPropertyNames = Stream
        .of(new BeanWrapperImpl(SqlSessionFactoryBean.class).getPropertyDescriptors()).map(PropertyDescriptor::getName)
        .collect(Collectors.toSet());
    Class<? extends LanguageDriver> defaultLanguageDriver = this.properties.getDefaultScriptingLanguageDriver();
    if (factoryPropertyNames.contains("scriptingLanguageDrivers") && !ObjectUtils.isEmpty(this.languageDrivers)) {
      // Need to mybatis-spring 2.0.2+
      factory.setScriptingLanguageDrivers(this.languageDrivers);
      if (defaultLanguageDriver == null && this.languageDrivers.length == 1) {
        defaultLanguageDriver = this.languageDrivers[0].getClass();
      }
    }
    if (factoryPropertyNames.contains("defaultScriptingLanguageDriver")) {
      // Need to mybatis-spring 2.0.2+
      factory.setDefaultScriptingLanguageDriver(defaultLanguageDriver);
    }
    applySqlSessionFactoryBeanCustomizers(factory);
    return factory.getObject();
  }

  private void applyConfiguration(SqlSessionFactoryBean factory) {
    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);
  }

  private void applySqlSessionFactoryBeanCustomizers(SqlSessionFactoryBean factory) {
    if (!CollectionUtils.isEmpty(this.sqlSessionFactoryBeanCustomizers)) {
      for (SqlSessionFactoryBeanCustomizer customizer : this.sqlSessionFactoryBeanCustomizers) {
        customizer.customize(factory);
      }
    }
  }

  @Bean
  @ConditionalOnMissingBean
  public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
    ExecutorType executorType = this.properties.getExecutorType();
    if (executorType != null) {
      return new SqlSessionTemplate(sqlSessionFactory, executorType);
    } else {
      return new SqlSessionTemplate(sqlSessionFactory);
    }
  }

  /**
   * This will just scan the same base package as Spring Boot does. If you want more power, you can explicitly use
   * {@link org.mybatis.spring.annotation.MapperScan} but this will get typed mappers working correctly, out-of-the-box,
   * similar to using Spring Data JPA repositories.
   */
  public static class AutoConfiguredMapperScannerRegistrar
      implements BeanFactoryAware, EnvironmentAware, ImportBeanDefinitionRegistrar {

    private BeanFactory beanFactory;
    private Environment environment;

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

      if (!AutoConfigurationPackages.has(this.beanFactory)) {
        logger.debug("Could not determine auto-configuration package, automatic mapper scanning disabled.");
        return;
      }

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

      List<String> packages = AutoConfigurationPackages.get(this.beanFactory);
      if (logger.isDebugEnabled()) {
        packages.forEach(pkg -> logger.debug("Using auto-configuration base package '{}'", pkg));
      }

      BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);
      builder.addPropertyValue("processPropertyPlaceHolders", true);
      builder.addPropertyValue("annotationClass", Mapper.class);
      builder.addPropertyValue("basePackage", StringUtils.collectionToCommaDelimitedString(packages));
      BeanWrapper beanWrapper = new BeanWrapperImpl(MapperScannerConfigurer.class);
      Set<String> propertyNames = Stream.of(beanWrapper.getPropertyDescriptors()).map(PropertyDescriptor::getName)
          .collect(Collectors.toSet());
      if (propertyNames.contains("lazyInitialization")) {
        // Need to mybatis-spring 2.0.2+
        builder.addPropertyValue("lazyInitialization", "${mybatis.lazy-initialization:false}");
      }
      if (propertyNames.contains("defaultScope")) {
        // Need to mybatis-spring 2.0.6+
        builder.addPropertyValue("defaultScope", "${mybatis.mapper-default-scope:}");
      }

      // for spring-native
      boolean injectSqlSession = environment.getProperty("mybatis.inject-sql-session-on-mapper-scan", Boolean.class,
          Boolean.TRUE);
      if (injectSqlSession && this.beanFactory instanceof ListableBeanFactory) {
        ListableBeanFactory listableBeanFactory = (ListableBeanFactory) this.beanFactory;
        Optional<String> sqlSessionTemplateBeanName = Optional
            .ofNullable(getBeanNameForType(SqlSessionTemplate.class, listableBeanFactory));
        Optional<String> sqlSessionFactoryBeanName = Optional
            .ofNullable(getBeanNameForType(SqlSessionFactory.class, listableBeanFactory));
        if (sqlSessionTemplateBeanName.isPresent() || !sqlSessionFactoryBeanName.isPresent()) {
          builder.addPropertyValue("sqlSessionTemplateBeanName",
              sqlSessionTemplateBeanName.orElse("sqlSessionTemplate"));
        } else {
          builder.addPropertyValue("sqlSessionFactoryBeanName", sqlSessionFactoryBeanName.get());
        }
      }
      builder.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);

      registry.registerBeanDefinition(MapperScannerConfigurer.class.getName(), builder.getBeanDefinition());
    }

    @Override
    public void setBeanFactory(BeanFactory beanFactory) {
      this.beanFactory = beanFactory;
    }

    @Override
    public void setEnvironment(Environment environment) {
      this.environment = environment;
    }

    private String getBeanNameForType(Class<?> type, ListableBeanFactory factory) {
      String[] beanNames = factory.getBeanNamesForType(type);
      return beanNames.length > 0 ? beanNames[0] : null;
    }

  }

  /**
   * If mapper registering configuration or mapper scanning configuration not present, this configuration allow to scan
   * mappers based on the same component-scanning path as Spring Boot itself.
   */
  @org.springframework.context.annotation.Configuration
  @Import(AutoConfiguredMapperScannerRegistrar.class)
  @ConditionalOnMissingBean({ MapperFactoryBean.class, MapperScannerConfigurer.class })
  public static class MapperScannerRegistrarNotFoundConfiguration implements InitializingBean {

    @Override
    public void afterPropertiesSet() {
      logger.debug(
          "Not found configuration for registering mapper bean using @MapperScan, MapperFactoryBean and MapperScannerConfigurer.");
    }

  }

}

2、为映射器接口生成BeanDefinition对象

MybatisAutoConfiguration的内部类MapperScannerRegistrarNotFoundConfiguration通过@Import的方式,将AutoConfiguredMapperScannerRegistrar注册到了spring容器中,代码如下所示:

less复制代码  @org.springframework.context.annotation.Configuration
  @Import(AutoConfiguredMapperScannerRegistrar.class)
  @ConditionalOnMissingBean({ MapperFactoryBean.class, MapperScannerConfigurer.class })
  public static class MapperScannerRegistrarNotFoundConfiguration implements InitializingBean {

    @Override
    public void afterPropertiesSet() {
      logger.debug(
          "Not found configuration for registering mapper bean using @MapperScan, MapperFactoryBean and MapperScannerConfigurer.");
    }
  }

AutoConfiguredMapperScannerRegistrar实现了ImportBeanDefinitionRegistrar接口。我在之前的文章中说过,通过@Import方式导入的类,都可以被当做一个配置类。AutoConfiguredMapperScannerRegistrar只完成了一件事,就是将MapperScannerConfigurer注册到了spring容器中,当然这时spring中的MapperScannerConfigurer还只是一个BeanDefinition对象。那么MapperScannerConfigurer又是用来干嘛的呢?

2.1、MapperScannerConfigurer映射器扫描器的配置器

MapperScannerConfigurer实现了BeanDefinitionRegistryPostProcessor接口,在它的postProcessBeanDefinitionRegistry方法中,它通过手动创建ClassPathMapperScanner对象,由ClassPathMapperScanner对象来完成映射器的注册工作。此处简单说下BeanDefinitionRegistryPostProcessor接口,它的作用就是提供一个拓展点,允许我们实现该接口,然后覆盖它的postProcessBeanDefinitionRegistry方法,并在该方法中完成BeanDefinition对象的注册工作。记住哦,是BeanDefinition对象,spring在初始化bean的阶段就会根据BeanDefinition对象来完成bean的初始化工作。

2.2、ClassPathMapperScanner类路径映射器扫描器

它的全路径是org.mybatis.spring.mapper.ClassPathMapperScanner,专门用来扫描类路径下指定包目录下标有@Mapper注解的映射器接口,然后为我们的每一个映射器接口都生成一个BeanDefinition对象,存放到spring容器中。其中每一个映射器接口对应的BeanDefinition对象的bean类型为MyFactoryBean类型,代码如下:

typescript复制代码private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {  
   。。。省略部分代码
   definition.setBeanClass(this.mapperFactoryBeanClass);
   definition.getPropertyValues().add("addToConfig", this.addToConfig);
   definition.setAttribute("factoryBeanObjectType", beanClassName);
   。。。省略部分代码
}

其中mapperFactoryBeanClass是ClassPathMapperScanner的成员变量,它的定义如下:

php复制代码private Class<? extends MapperFactoryBean> mapperFactoryBeanClass = MapperFactoryBean.class;

后续spring在初始化bean的阶段,会调用MyFactoryBean的getObject方法生成我们的映射器接口实例。

3、初始化bean阶段执行的流程

经过前面的步骤,spring容器中已经有了完整的BeanDefinition对象列表,接下来spring会继续执行,根据这些BeanDefinition对象生成实际的bean对象。

3.1、生成SqlSessionFactory的bean实例对象

spring容器中存储着SqlSessionFactoryBean的bean定义信息,spring初始化bean的时候,会根据该bean定义信息,调用它的getObject方法,生成真正的sqlSessionFactory对象。在这过程中,会涉及到下列操作:

(1)生成mybatis的全局Configuration对象,configuration对象可以是根据mybatis的xml配置文件解析而来

(2)如果有自定义的mybatis插件拦截器plugins,则将它们加到configuration的拦截器列表。

kotlin复制代码if (!ObjectUtils.isEmpty(this.plugins)) {
            Stream.of(this.plugins).forEach((plugin) -> {
                targetConfiguration.addInterceptor(plugin);
                LOGGER.debug(() -> {
                    return "Registered plugin: '" + plugin + "'";
                });
            });
        }

(3)如果有自定义的类型处理器typeHandlers,则将它们加到configuration的类型处理器注册中心对象中

kotlin复制代码if (!ObjectUtils.isEmpty(this.typeHandlers)) {
            Stream.of(this.typeHandlers).forEach((typeHandler) -> {
                targetConfiguration.getTypeHandlerRegistry().register(typeHandler);
                LOGGER.debug(() -> {
                    return "Registered type handler: '" + typeHandler + "'";
                });
            });
        }

(4)根据我们在application.yml文件中配置的mybatis.mapper-locations属性值,使用XMLMapperBuilder解析对应路径下的xml格式的mybatis映射文件,根据映射文件中的命名空间namespace的值,找到对应的类(或接口)的class对象,并将其加入到configuration的映射器注册中心mapperRegistry。而且针对映射文件中的每个sql语句,都会将它解析成一个MappedStatement对象,并将这个MappedStatement对象加入到configuration的mappedStatements(Map类型)对象中,mappedStatements的key就是每个MappedStatement对象的id,而id的值就是映射文件中namespace的值+"."+sql语句的id。我们以orderMapper.xml文件中的findbyId这个映射语句为例,它被解析后生成的MappedStatement对象id就是com.xk.mybatis.springboot.mapper.OrderMapper.findById

csharp复制代码<mapper namespace="com.xk.mybatis.springboot.mapper.OrderMapper">

    <select id="findById" parameterType="Long" resultType="com.xk.mybatis.springboot.entity.Order">
        select * from t_order where id=#{id};
    </select>

</mapper>

核心代码如下:

kotlin复制代码targetConfiguration.setEnvironment(new Environment(this.environment, (TransactionFactory)(this.transactionFactory == null ? new SpringManagedTransactionFactory() : this.transactionFactory), this.dataSource));
        if (this.mapperLocations != null) {
            if (this.mapperLocations.length == 0) {
                LOGGER.warn(() -> {
                    return "Property 'mapperLocations' was specified but matching resources are not found.";
                });
            } else {
                Resource[] var3 = this.mapperLocations;
                int var4 = var3.length;

                for(int var5 = 0; var5 < var4; ++var5) {
                    Resource mapperLocation = var3[var5];
                    if (mapperLocation != null) {
                        try {
                            //解析映射文件
                            XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(), targetConfiguration, mapperLocation.toString(), targetConfiguration.getSqlFragments());
                            xmlMapperBuilder.parse();
                        } catch (Exception var19) {
                            throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", var19);
                        } finally {
                            ErrorContext.instance().reset();
                        }

                        LOGGER.debug(() -> {
                            return "Parsed mapper file: '" + mapperLocation + "'";
                        });
                    }
                }
            }
        }

其中xmlMapperBuilder.parse()的实现如下,详情大家可以自己去看一下:

scss复制代码public void parse() {
    if (!configuration.isResourceLoaded(resource)) {
      configurationElement(parser.evalNode("/mapper"));
      configuration.addLoadedResource(resource);
      //根据映射文件中的命名空间namespace的值,找到对应的类(或接口)的class对象,并将其加入到configuration的映射器注册中心mapperRegistry
      bindMapperForNamespace();
    }

    parsePendingResultMaps();
    parsePendingCacheRefs();
    //将映射文件中的每一个sql语句都解析成一个MappedStatement对象
    parsePendingStatements();
  }

3.2、生成SqlSessionTemplate的bean实例对象

SqlSessionTemplate实现了SqlSession接口,内部维护了一个sqlSessionProxy对象,而sqlSessionProxy本身是通过jdk动态代理创建的一个SqlSession的代理对象。SqlSessionTemplate的构造方法如下:

ini复制代码public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) {
        Assert.notNull(sqlSessionFactory, "Property 'sqlSessionFactory' is required");
        Assert.notNull(executorType, "Property 'executorType' is required");
        this.sqlSessionFactory = sqlSessionFactory;
        this.executorType = executorType;
        this.exceptionTranslator = exceptionTranslator;
        this.sqlSessionProxy = (SqlSession)Proxy.newProxyInstance(SqlSessionFactory.class.getClassLoader(), new Class[]{SqlSession.class}, new SqlSessionTemplate.SqlSessionInterceptor());
    }

其中SqlSessionInterceptor类的实现如下:

kotlin复制代码private class SqlSessionInterceptor implements InvocationHandler {
        private SqlSessionInterceptor() {
        }
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            //通过sqlSessionFactory生成sqlSession对象
            SqlSession sqlSession = SqlSessionUtils.getSqlSession(SqlSessionTemplate.this.sqlSessionFactory, SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator);

            Object unwrapped;
            try {
                //通过反射执行目标方法
                Object result = method.invoke(sqlSession, args);
                if (!SqlSessionUtils.isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
                    sqlSession.commit(true);
                }
                unwrapped = result;
            } catch (Throwable var11) {
                unwrapped = ExceptionUtil.unwrapThrowable(var11);
                if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) {
                    SqlSessionUtils.closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
                    sqlSession = null;
                    Throwable translated = SqlSessionTemplate.this.exceptionTranslator.translateExceptionIfPossible((PersistenceException)unwrapped);
                    if (translated != null) {
                        unwrapped = translated;
                    }
                }
                throw (Throwable)unwrapped;
            } finally {
                if (sqlSession != null) {
                    SqlSessionUtils.closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
                }
            }
            return unwrapped;
        }
    }

当我们在代码中调用sqlSessionTemplate的select方法时,会执行如下的方法:

typescript复制代码public <T> T selectOne(String statement, Object parameter) {
        return this.sqlSessionProxy.selectOne(statement, parameter);
 }

对sqlSessionTemplate的所有操作,都会由它内部的sqlSessionProxy代理对象完成,而这又会进一步执行上面SqlSessionInterceptor类的invoke方法。invoke内部就是通过sqlSessionFactory对象创建新的sqlsession对象,然后由这个新的sqlSession对象完成对数据库的操作。

3.3、生成映射器接口对应的bean实例对象

前面我们说了,spring容器中存储的关于映射器接口的bean定义信息指定的bean的类型是MyFactoryBean类型,MyFactoryBean是一个工厂bean,spring根据映射器接口的BeanDefinition对象初始化bean的时候,就会调用MyFactoryBean的getObject方法生成真正的映射器接口实例。getObject方法实现如下:

kotlin复制代码public T getObject() throws Exception {
    return this.getSqlSession().getMapper(this.mapperInterface);
}

其中getSqlSession()就是返回我们3.2节生成的sqlSessionTemplate对象。

3.3.1、进入SqlSessionTemplate的getMapper方法,

typescript复制代码public <T> T getMapper(Class<T> type) {
    return this.getConfiguration().getMapper(type, this);
}

其中getConfiguration()就是3.1节中创建SqlSessionFactory对象时生成的mybatis全局的configuration对象。

3.3.2、进入Configuration类的getMapper方法

typescript复制代码public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
  return mapperRegistry.getMapper(type, sqlSession);
}

3.3.3、进入MapperRegistry的getMapper方法

typescript复制代码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 {
     // 返回mapper接口的代理对象
    return mapperProxyFactory.newInstance(sqlSession);
  } catch (Exception e) {
    throw new BindingException("Error getting mapper instance. Cause: " + e, e);
  }
}

上面这里会获取MapperProxyFactory对象,MapperProxyFactory通过调用它自身的newInstance方法,创建映射器接口(mapper接口)的代理对象,

3.3.4、进入MapperProxyFactory的newInstance方法

代码如下所示:

typescript复制代码  // 第二步
  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);
  }

到这里,我们自定义的映射器接口的实例对象(代理对象)就通过JDK动态代理的方式创建成功了,接下来spring就会将其注入到容器中,供我们在应用系统层面直接使用,比如在我们的OrderService中直接调用OrderMapper。有一点要说下,其实映射器接口的实例对象的真实类型是org.apache.ibatis.binding.MapperProxy,经过了动态代理这一步,我们可以理解为这个代理对象也已经实现了映射器接口,比如实现了我们的OrderMapper接口,所以spring才能帮我们把代理对象注入到我们的OrderService中。

4、执行映射器接口方法

本部分我们以在orderService中调用orderMapper.findById()方法为例,讲解映射器接口方法的执行流程。

上面3.3.4部分我们提到:spring容器中映射器实例对象的真实类型是org.apache.ibatis.binding.MapperProxy,那么MapperProxy又是个啥呢?

4.1、MapperProxy

MapperProxy实现了InvocationHandler接口,上面3.3.4部分newInstance方法创建MapperProxy对象,就是为了通过JDK自身的Proxy类创建映射器接口的代理对象(这就是jdk的动态代理的实现)。当被代理的映射器接口的方法被调用时,就会执行MapperProxy内部的invoke方法,invoke内部实现如下:

java复制代码  @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 {
       //调用MapperMethodInvoker的invoke方法,内部再调用MapperMethod,最终会执行我们的sql语句
        return cachedInvoker(method).invoke(proxy, method, args, sqlSession);
      }
    } catch (Throwable t) {
      throw ExceptionUtil.unwrapThrowable(t);
    }
  }

 //获取MapperMethod方法的调用器,通过调用器完成对底层MappedStatement的调用
  private MapperMethodInvoker cachedInvoker(Method method) throws Throwable {
    try {
      return MapUtil.computeIfAbsent(methodCache, method, m -> {
        if (m.isDefault()) {
          try {
            if (privateLookupInMethod == null) {
              return new DefaultMethodInvoker(getMethodHandleJava8(method));
            } else {
              return new DefaultMethodInvoker(getMethodHandleJava9(method));
            }
          } catch (IllegalAccessException | InstantiationException | InvocationTargetException
              | NoSuchMethodException e) {
            throw new RuntimeException(e);
          }
        } else {
          return new PlainMethodInvoker(new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
        }
      });
    } catch (RuntimeException re) {
      Throwable cause = re.getCause();
      throw cause == null ? re : cause;
    }
  }

4.2、PlainMethodInvoker

因为我们映射器接口里面一般不会写默认方法,所以上面MapperProxy的invoke方法中cachedInvoker(method)方法一般会返回一个PlainMethodInvoker对象,并且给PlainMethodInvoker传递了一个MapperMethod对象。

sql复制代码return new PlainMethodInvoker(new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));

PlainMethodInvoker的invoke方法会调用MapperMethod.execute方法,invoke实现如下:

typescript复制代码@Override
    public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable {
      return mapperMethod.execute(sqlSession, args);
    }

4.3、MapperMethod

MapperMethod是对我们当前调用的映射器接口内部方法的一个包装,上面PlainMethodInvoker通过调用new MapperMethod(mapperInterface, method, sqlSession.getConfiguration())构造器来创建MapperMethod对象,我们看下它的构造方法实现:

arduino复制代码public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
  this.command = new SqlCommand(config, mapperInterface, method);
  this.method = new MethodSignature(config, mapperInterface, method);
}

构造函数的参数mapperInterface就是我们的映射器接口名,我们观察到构造函数里面创建了一个SqlCommand对象,我们来看下SqlCommand的实现:

ini复制代码public static class SqlCommand {

    private final String name;
    private final SqlCommandType type;

    public SqlCommand(Configuration configuration, Class<?> mapperInterface, Method method) {
      final String methodName = method.getName();
      final Class<?> declaringClass = method.getDeclaringClass();
      MappedStatement ms = resolveMappedStatement(mapperInterface, methodName, declaringClass,
          configuration);
      if (ms == null) {
        if (method.getAnnotation(Flush.class) != null) {
          name = null;
          type = SqlCommandType.FLUSH;
        } else {
          throw new BindingException("Invalid bound statement (not found): "
              + mapperInterface.getName() + "." + methodName);
        }
      } else {
        name = ms.getId();
        type = ms.getSqlCommandType();
        if (type == SqlCommandType.UNKNOWN) {
          throw new BindingException("Unknown execution method for: " + name);
        }
      }
    }
    ...省略部分代码
}

SqlCommand是MapperMethod的一个内部类,用来维护映射器接口中的方法的sql操作类型,它会通过获取映射器接口的全限定名+"."+接口方法名来从configuration中获取MappedStatement对象,并把MappedStatement对象的名称赋值给SqlCommand对象的name属性,把MappedStatement对象的sql操作类型(select、update、insert或delete)赋值给SqlCommand对象的type属性。为啥要提这两个属性呢?因为下面要用到。

MapperMethod的execute方法实现如下:

ini复制代码  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);
          if (method.returnsOptional()
              && (result == null || !method.getReturnType().equals(result.getClass()))) {
            result = Optional.ofNullable(result);
          }
        }
        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;
  }

在execute中,我们又看到了一个熟悉的身影:sqlSession,它就是我们前面讲的sqlSessionTemplate对象。我们调用orderMapper.findById方法时,就会执行到下面这里:

ini复制代码         Object param = method.convertArgsToSqlCommandParam(args);
         result = sqlSession.selectOne(command.getName(), param);

前面提到过command.getName()的值就是MappedStatement对象的id值,再细一点说就是:映射器接口的全限定名+"."+接口方法名,而执行sqlSession.selectOne的流程就相当于直接操作sqlSessionTemplate了。

5、总结

本篇主要讲了映射器自动注册的流程,以及调用映射器接口方法时涉及到的一些流程。我们也提到了一个很重要的概念:MappedStatement。映射文件中的每个sql映射语句都对应一个MappedStatement对象,而通过映射器接口的方法名,我们又能找到唯一的MappedStatement对象,进而执行最终的sql映射语句。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值