This article source code from mybatis-spring-boot-starter 2.1.2 version

One, foreword

We must have seen these annotations when integrating Mybatis with Spring

  • Mapper is used on the Mapper interface, which is hosted by Spring for management.
  • The @mapperscan command is used to enable packet scanning and scan Mapper interfaces in a path of a project.

1.1 @ MapperScan

@mapperscan is definitely more convenient. Let’s see what it does.

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(MapperScannerRegistrar.class)
@Repeatable(MapperScans.class)
public @interface MapperScan {
Copy the code
/**
 * A {@link ImportBeanDefinitionRegistrar} to allow annotation configuration of MyBatis mapper scanning. Using
 * an @Enable annotation allows beans to be registered via @Component configuration, whereas implementing
 * {@code BeanDefinitionRegistryPostProcessor} will work for XML configuration.
 *
 * @author Michael Lanyon
 * @author Eduardo Macarron
 * @author Putthiphong Boonphong
 *
 * @see MapperFactoryBean
 * @see ClassPathMapperScanner
 * @since1.2.0 * /
public class MapperScannerRegistrar implements ImportBeanDefinitionRegistrar.ResourceLoaderAware {

  / * * * {@inheritDoc}
   * 
   * @deprecatedSince 2.0.2, this method is not used never. */
  @Override
  @Deprecated
  public void setResourceLoader(ResourceLoader resourceLoader) {
    // NOP
  }

  / * * * {@inheritDoc} * /
  @Override
  public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
    // Get the attributes on the @mapperscan annotation
    AnnotationAttributes mapperScanAttrs = AnnotationAttributes
        .fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
    if(mapperScanAttrs ! =null) {
        registerBeanDefinitions(importingClassMetadata, mapperScanAttrs, registry,
          generateBaseBeanName(importingClassMetadata, 0)); }}Copy the code

MapperScan @ Import a class MapperScannerRegistrar. Class, it implements the ImportBeanDefinitionRegistrar interface, So MapperScannerRegistrar now has the ability to register beans with Spring.

Spring in the implementation of ConfigurationClassParser doProcessConfigurationClass () method will use getImports () scan of the @ Import classes. In processImports MapperScannerRegistrar in () will be put into importBeanDefinitionRegistrars list. The back can be ConfigurationClassBeanDefinitionReader loadBeanDefinitionsForConfigurationClass load to the () method.

// Process any @Import annotations
processImports(configClass, sourceClass, getImports(sourceClass), true);
Copy the code
...else if (candidate.isAssignable(ImportBeanDefinitionRegistrar.class)) {
						// Candidate class is an ImportBeanDefinitionRegistrar ->
						// delegate to it to register additional bean definitionsClass<? > candidateClass = candidate.loadClass(); ImportBeanDefinitionRegistrar registrar = BeanUtils.instantiateClass(candidateClass, ImportBeanDefinitionRegistrar.class); ParserStrategyUtils.invokeAwareMethods( registrar,this.environment, this.resourceLoader, this.registry);
						configClass.addImportBeanDefinitionRegistrar(registrar, currentSourceClass.getMetadata());
					}
Copy the code

Take a look at the registerBeanDefinitions method Spring loads on the MapperScannerRegistrar callback

  void registerBeanDefinitions(AnnotationMetadata annoMeta, AnnotationAttributes annoAttrs, BeanDefinitionRegistry registry, String beanName) {

    BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);
    builder.addPropertyValue("processPropertyPlaceHolders".true);
    Class<? extends Annotation> annotationClass = annoAttrs.getClass("annotationClass");
    if(! Annotation.class.equals(annotationClass)) { builder.addPropertyValue("annotationClass", annotationClass);
    }
   ……
    builder.addPropertyValue("basePackage", StringUtils.collectionToCommaDelimitedString(basePackages));

    registry.registerBeanDefinition(beanName, builder.getBeanDefinition());


  }
Copy the code

BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class) Built a MapperScannerConfigurer. Class types of BeanDefinition (bean in Spring), in addition to its set up several attributes: ProcessPropertyPlaceHolders, annotationClass and basePackage annotationClass is to scan the Mapper default annotations, basePackage is to scan the package root.

MapperScannerConfigurer. Class, Spring integration Mybatis core classes Its role is the Dao class in scanning project, to create for Mybatis Mapper object namely MapperProxy. We’ll talk more about that in a minute.

1.2 @ Mapper

SpringBoot can automatically scan Mapper classes when integrating Mybatis

  • Auto-scan your mappers, link them to the SqlSessionTemplate and register them to Spring context so they can be injected into your beans

MybatisAutoConfiguration is used by @mapper find Usage

MybatisAutoConfiguration implements the InitializingBean interface, so afterPropertiesSet() must be implemented. Let’s look at the implementation

@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 {
Copy the code
 /** * 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(a) {
      logger.debug(
          "Not found configuration for registering mapper bean using @MapperScan, MapperFactoryBean and MapperScannerConfigurer."); }}Copy the code

@ ConditionalOnMissingBean ({MapperFactoryBean. Class, MapperScannerConfigurer class}) I don’t need to say more. We looked at the. If you don’t have these two kinds of Import AutoConfiguredMapperScannerRegistrar class, and MapperScannerRegistrar class have the same effect. But I recommend using @mapperscan to save yourself some trouble!

 public static class AutoConfiguredMapperScannerRegistrar implements BeanFactoryAware, ImportBeanDefinitionRegistrar {

    private BeanFactory beanFactory;

    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
     ……
      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); Stream of (beanWrapper. GetPropertyDescriptors ()) / / Need to mybatis - spring 2.0.2 +. Filter (x - > x.g etName () equals ("lazyInitialization")).findAny()
          .ifPresent(x -> builder.addPropertyValue("lazyInitialization"."${mybatis.lazy-initialization:false}"));
      registry.registerBeanDefinition(MapperScannerConfigurer.class.getName(), builder.getBeanDefinition());
    }
Copy the code

Second, the MapperScannerConfigurer

  • BeanNameAwareWhen creating a bean, the bean name is passedsetBeanName()Set in the bean so that external programs can pass throughgetBeanName()Gets the name of the bean.
  • ApplicationContextAware: Arbitrary implementationApplicationContextAwareWhen subclasses are created, Spring willApplicationContextObject is automatically injected into the current bean.
  • InitializingBean:InitializingBeanThe interface provides the bean with a post-initialization method for the property, which only includesafterPropertiesSetMethod, which any class that inherits this interface executes after the bean’s properties are initialized.
  • BeanDefinitionRegistryPostProcessor: the standardBeanFactoryPostProcessorSPI extension to allow in generalBeanFactoryPostProcessorRegister more bean definitions before the inspection begins. Usually when we want to customizeBeanDefinitionUsed.MapperScannerConfigurerThe main implementation is to scan specific packages and create Mapper objects and hand them over to Spring for management.
  • BeanFactoryPostProcessor:BeanFactoryThe rear processor,BeanFactoryPostProcessorAfter the bean definition file is loaded by the Spring container and before the bean is instantiated, it can be used in thepostProcessBeanFactory()As neededBeanDefinitionMake changes, such as changing the scope of the bean from Singleton to Prototype.

2.1 BeanDefinitionRegistryPostProcessor

MapperScannerConfigurer BeanDefinitionRegistryPostProcessor interface is achieved, So the Spring container will be invokeBeanDefinitionRegistryPostProcessors () callback postProcessBeanDefinitionRegistry () method.

  @Override
  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.setMapperFactoryBeanClass(this.mapperFactoryBeanClass);
    if (StringUtils.hasText(lazyInitialization)) {
      scanner.setLazyInitialization(Boolean.valueOf(lazyInitialization));
    }
    scanner.registerFilters();
    scanner.scan(
        StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
  }
Copy the code
  • Set up heresqlSessionFactoryandsqlSessionTemplateBeanNameThe subsequent Mapper generation will naturally be managed by them
  • ClassPathMapperScanner was calledscan()methods

2.2 ClassPathMapperScanner

public int scan(String... basePackages) {
		int beanCountAtScanStart = this.registry.getBeanDefinitionCount();

		doScan(basePackages);

Copy the code
  @Override
  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;
  }
Copy the code
  • Super.doscan () calls the parent’s doScan method, parses the configuration file, and builds the corresponding BeanDefinitionHolder object.
  • ProcessBeanDefinitions () added BeanDefinitions to Mybatis

2.2.1 doScan

/**
	 * Perform a scan within the specified base packages,
	 * returning the registered bean definitions.
	 * <p>This method does <i>not</i> register an annotation config processor
	 * but rather leaves this up to the caller.
	 * @param basePackages the packages to check for annotated classes
	 * @return set of beans registered if any for tooling registration purposes (never {@code null})
	 */
	protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
		Assert.notEmpty(basePackages, "At least one base package must be specified");
		Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();
		for (String basePackage : basePackages) {
		// Scan the classpath of the candidate component
			Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
			for (BeanDefinition candidate : candidates) {
				ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
				candidate.setScope(scopeMetadata.getScopeName());
				            // Generate the bean name
				String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
				if (candidate instanceof AbstractBeanDefinition) {
					postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
				}
				if (candidate instanceof AnnotatedBeanDefinition) {
					AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
				}
				if (checkCandidate(beanName, candidate)) {
					BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
					definitionHolder =
							AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
					beanDefinitions.add(definitionHolder);
					// Register the bean definition
					registerBeanDefinition(definitionHolder, this.registry); }}}return beanDefinitions;
	}
Copy the code

Performs a scan in the specified base package, returning the registered bean definition. Before MapperScanner execution, doscan processConfigBeanDefinitions () will be performed to scan for Config.

Spring scan BeanDefinition

2.2.2 processBeanDefinitions

private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
    GenericBeanDefinition definition;
    for (BeanDefinitionHolder holder : beanDefinitions) {
      definition = (GenericBeanDefinition) holder.getBeanDefinition();
      String beanClassName = definition.getBeanClassName();
      LOGGER.debug(() -> "Creating MapperFactoryBean with name '" + holder.getBeanName() + "' and '" + beanClassName
          + "' mapperInterface");

      // the mapper interface is the original class of the bean
      // but, the actual class of the bean is MapperFactoryBean
      definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName); // issue #59
     MapperFactoryBeanClass = mapPerFactoryBean.class;

      definition.setBeanClass(this.mapperFactoryBeanClass);

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

      boolean explicitFactoryUsed = false;
      if (StringUtils.hasText(this.sqlSessionFactoryBeanName)) {
      // Put the SqlSessionFactory into the MapperFactoryBean property. The SqlSessionFactory is automatically obtained at instantiation time.
        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.");
        }
        // If the sqlSessionTemplate is not empty, it is put into the property so that Spring can get the corresponding sqlSessionTemplate when instantiating MapperFactoryBean.
        definition.getPropertyValues().add("sqlSessionTemplate".this.sqlSessionTemplate);
        explicitFactoryUsed = true;
      }

      if(! explicitFactoryUsed) { LOGGER.debug(() ->"Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'."); definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE); } definition.setLazyInit(lazyInitialization); }}Copy the code

The processBeanDefinitions() method sets the BeanDefinition class to MapperFactoryBean, and in Spring we get the built instance from the FactoryBean object’s getObject() method. SqlSessionFactory and SqlSessionTemplate are also set in the MapperFactoryBean property (SqlSessionTemplate is not empty).

Note: This is just a modification of the BeanDefinition; the Mapper is not initialized yet.

Third, MapperFactoryBean

3.1 DaoSupport


/**
 * Generic base class for DAOs, defining template methods for DAO initialization.
 *
 * <p>Extended by Spring's specific DAO support classes, such as:
 * JdbcDaoSupport, JdoDaoSupport, etc.
 *
 * @author Juergen Hoeller
 * @since 1.2.2
 * @see org.springframework.jdbc.core.support.JdbcDaoSupport
 */
public abstract class DaoSupport implements InitializingBean {

	/** Logger available to subclasses. */
	protected final Log logger = LogFactory.getLog(getClass());


	@Override
	public final void afterPropertiesSet(a) throws IllegalArgumentException, BeanInitializationException {
		// Let abstract subclasses check their configuration.
		checkDaoConfig();

		// Let concrete implementations initialize themselves.
		try {
			initDao();
		}
		catch (Exception ex) {
			throw new BeanInitializationException("Initialization of DAO failed", ex); }}Copy the code

As you can see from the documentation comments, DaoSupport defines template methods for initialization

  • CheckDaoConfig () checks or builds dao configuration information
/**
	 * Abstract subclasses must override this to check their configuration.
	 * <p>Implementors should be marked as {@code final} if concrete subclasses
	 * are not supposed to override this template method themselves.
	 * @throws IllegalArgumentException in case of illegal configuration
	 */
Copy the code
  • InitDao () initializes daO-related methods
/**
	 * Concrete subclasses can override this for custom initialization behavior.
	 * Gets called after population of this instance's bean properties.
	 * @throws Exception if DAO initialization fails
	 * (will be rethrown as a BeanInitializationException)
	 * @see org.springframework.beans.factory.BeanInitializationException
	 */
Copy the code

3.2 MapperFactoryBean

  @Override
  protected void checkDaoConfig(a) {
    super.checkDaoConfig();

    notNull(this.mapperInterface, "Property 'mapperInterface' is required");

    Configuration configuration = getSqlSession().getConfiguration();
    if (this.addToConfig && ! configuration.hasMapper(this.mapperInterface)) {
      try {
        configuration.addMapper(this.mapperInterface);
      } catch (Exception e) {
        logger.error("Error while adding the mapper '" + this.mapperInterface + "' to configuration.", e);
        throw new IllegalArgumentException(e);
      } finally{ ErrorContext.instance().reset(); }}}Copy the code
  • mapperInterfaceIt’s just the Mapper interface that we just scanned
  • If not already registered, callconfiguration.addMapper()Add it inconfigurationIn the
protected final MapperRegistry mapperRegistry = new MapperRegistry(this); ...public <T> void addMapper(Class<T> type) {
    mapperRegistry.addMapper(type);
  }
Copy the code

Let’s look at the core class MapperRegistry

3.3 MapperRegistry

public class MapperRegistry {

  private final Configuration config;
  private finalMap<Class<? >, MapperProxyFactory<? >> knownMappers =new HashMap<>();
Copy the code
  • Here,ConfigurationIs the global configuration object of Mybatis. Contains various configurations for XML or annotation parsing.
  • knownMappersPut isMapperandMapperProxyFactoryIndicates whether Mapper has been added.

Two important methods in MapperRegistry are addMapper and getMapper. AddMapper creates MapperProxyFactory mapping for *Mapper. GetMapper, as its name implies, obtains MapperProxyFactory object

3.3.1 addMapper

  public <T> void addMapper(Class<T> type) {
    if (type.isInterface()) {
      if (hasMapper(type)) {
        throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
      }
      boolean loadCompleted = false;
      try {
        knownMappers.put(type, new MapperProxyFactory<>(type));
        // It's important that the type is added before the parser is run
        // otherwise the binding may automatically be attempted by the
        // mapper parser. If the type is already known, it won't try.
        MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
        parser.parse();
        loadCompleted = true;
      } finally {
        if(! loadCompleted) { knownMappers.remove(type); }}}}Copy the code
  • A registered Mapper throws an exception
  • forMappercreateMapperProxyFactoryObject and put it in the map
  • If the XML file corresponding to Mapper is not loaded, the XML binding operation is triggered to establish the relationship between THE SQL statement in XML and Mapper. More on this later.

3.3.2 rainfall distribution on 10-12 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); }}Copy the code
  • From typeknownMappersGet the correspondingMapperProxyFactoryobject
  • NewInstance (sqlSession) Creates a MapperProxy object to observegetMapperThe stack call is not hard to find, and is called back when scanning Mapper injectionMapperFactoryBeanthegetObject()Method to generate a proxy object for Mapper.MapperProxyThis ends the creation of the. MapperProxy associates SQL statements in XMl files or annotations, which we’ll discuss later.