How to integrate Mybatis with Spring? The source code is not difficult!

Keywords: Programming Mybatis Spring Java less

Spring integrates Mybtais with the following configuration (all roads lead to Rome, and the way is not unique).

private static final String ONE_MAPPER_BASE_PACKAGE = "com.XXX.dao.mapper.one";
@Bean
public MapperScannerConfigurer oneMapperScannerConfigurer() {
    MapperScannerConfigurer mapperScannerConfigurer = new MapperScannerConfigurer();
    mapperScannerConfigurer.setBasePackage(ONE_MAPPER_BASE_PACKAGE);
    mapperScannerConfigurer.
                setSqlSessionFactoryBeanName("oneSqlSessionFactoryBean");
    return mapperScannerConfigurer;
}
@Primary
@Bean(name="oneSqlSessionFactoryBean")
public SqlSessionFactoryBean oneSqlSessionFactoryBean( @Qualifier("oneDataSource") DruidDataSource oneDataSource) {
    return getSqlSessionFactoryBeanDruid(oneDataSource,ONE_MAPPER_XML);
}

In less than 20 lines of code, Spring integrated Mybatis.

Amazing!!! What happened behind this?

Also start with MapperScannerConfigurer and SqlSessionFactoryBean.

MapperScannerConfigurer

Class annotation

  • beanDefinitionRegistryPostProcessor recursively searches the interfaces from base package to register them as MapperFactoryBean. Note that the interface must contain at least one method, and its implementation class will be ignored.

  • Before 1.0.1, beanfactory postprocessor was extended. After 1.0.2, BeanDefinitionRegistryPostProcessor was extended. For specific reasons, please refer to https://jira.springsource.org/browse/SPR-8269

  • basePackage can be configured with multiple, separated by commas or semicolons.

  • With annotationClass or markerInterface, you can set the interface for the specified scan. By default, these two properties are empty, and all interfaces under basePackage will be scanned.

  • MapperScannerConfigurer automatically injects sqlsessionfactory or sqlsessiontemplate for the bean it creates. If there are multiple sqlsessionfactories, you need to set sqlSessionFactoryBeanName or sqlSessionTemplateBeanName to specify the specific sqlsessionfactory or sqlsessiontemplate to be injected.

  • You cannot pass in an object with placeholders (for example, an object that contains placeholders for the user name and password of a database). You can use beanName to defer actual object creation until all placeholder replacements are complete. Note that MapperScannerConfigurer supports the use of placeholders for its own properties, using the format ${property}.

Key methods of class diagram searching

From the class diagram, MapperScannerConfigurer implements the beandefinitionregistrypostprocessor, initializingbean, applicationcontextaware and beannameaware interfaces. The specific meanings of each interface are as follows:

  • ApplicationContextAware: when the spring container is initialized, it will automatically inject ApplicationContext
  • BeanNameAware: set the name of the current Bean in Spring
  • The InitializingBean interface only includes the afterpropertieset method, which will execute when initializing the bean
  • BeanDefinitionRegistryPostProcessor: an extension to BeanFactoryPostProcessor that allows the registration of multiple bean definitions before BeanFactoryPostProcessor execution. The method to be extended is postProcessBeanDefinitionRegistry.

Query. The afterpropertieset method of MapperScannerConfigurer is as follows, without specific extension information.

@Override public void afterPropertiesSet() throws Exception {
notNull(this.basePackage, "Property 'basePackage' is required"); 
}

Combined with MapperScannerConfigurer's annotation and class diagram analysis, the core method is determined as: postProcessBeanDefinitionRegistry

postProcessBeanDefinitionRegistry analysis

@Override
public void postProcessBeanDefinitionRegistry(
                    BeanDefinitionRegistry registry) {
  if (this.processPropertyPlaceHolders) {
      //1. Placeholder property processing
    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);
  //2. Set filter
  scanner.registerFilters();
  //3. Scan java files
  scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, 
          ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
}

It can be seen from the source code that ClassPathMapperScanner is entrusted with all work except processPropertyPlaceHolders

processPropertyPlaceHolders processing placeholders

Previously, BeanDefinitionRegistryPostProcessor was called before BeanFactoryPostProcessor was executed.

This means that the class PropertyResourceConfigurer that Spring handles placeholders has not been executed yet!

How does MapperScannerConfigurer support the use of placeholders for its properties? The answer to all this lies in

processPropertyPlaceHolders.

private void processPropertyPlaceHolders() {
  Map<String, PropertyResourceConfigurer> prcs =
       applicationContext.getBeansOfType(PropertyResourceConfigurer.class);
  if (!prcs.isEmpty() && applicationContext 
                      instanceof GenericApplicationContext) {
    BeanDefinition mapperScannerBean = 
            ((GenericApplicationContext) applicationContext)
                        .getBeanFactory().getBeanDefinition(beanName);
    // PropertyResourceConfigurer directly replaces placeholders without exposing methods,
    // Create a BeanFactory containing MapperScannerConfigurer
    // Then perform the post-processing of BeanFactory
    DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
    factory.registerBeanDefinition(beanName, mapperScannerBean);

    for (PropertyResourceConfigurer prc : prcs.values()) {
      prc.postProcessBeanFactory(factory);
    }
    PropertyValues values = mapperScannerBean.getPropertyValues();
    this.basePackage = updatePropertyValue("basePackage", values);
    this.sqlSessionFactoryBeanName =
            updatePropertyValue("sqlSessionFactoryBeanName", values);
    this.sqlSessionTemplateBeanName = 
            updatePropertyValue("sqlSessionTemplateBeanName", values);
  }
}

After reading processPropertyPlaceHolders, you can summarize how MapperScannerConfigurer supports the use of placeholders for its own properties

  1. Find all registered beans of type PropertyResourceConfigurer

  2. Use new DefaultListableBeanFactory() to simulate the Spring environment, register MapperScannerConfigurer in the BeanFactory, and perform post-processing of BeanFactory to replace the placeholder.

registerFilters method of ClassPathMapperScanner

MapperScannerConfigurer has one class comment:

Through annotationClass or markerInterface, you can set the interface to be scanned. By default, these two attributes are empty, and all interfaces under basePackage will be scanned scanner.registerFilters(), which is the setting of annotationClass and markerInterface.

public void registerFilters() {
  boolean acceptAllInterfaces = true;

  // If annotationClass is specified,
  if (this.annotationClass != null) {
    addIncludeFilter(new AnnotationTypeFilter(this.annotationClass));
    acceptAllInterfaces = false;
  }
  // Override AssignableTypeFilter to ignore matches on the actual tag interface
  if (this.markerInterface != null) {
    addIncludeFilter(new AssignableTypeFilter(this.markerInterface) {
      @Override
      protected boolean matchClassName(String className) {
        return false;
      }
    });
    acceptAllInterfaces = false;
  }

  if (acceptAllInterfaces) {
    // Handle all interfaces by default
    addIncludeFilter(new TypeFilter() {
      @Override
      public boolean match(
      MetadataReader metadataReader, 
      MetadataReaderFactory metadataReaderFactory) throws IOException {
        return true;
      }
    });
  }

  // Does not contain java files ending in package info
  // Package info.java package level documentation and package level comments
  addExcludeFilter(new TypeFilter() {
    @Override
    public boolean match(MetadataReader metadataReader, 
    MetadataReaderFactory metadataReaderFactory) throws IOException {
      String className = metadataReader.getClassMetadata().getClassName();
      return className.endsWith("package-info");
    }
  });
}

Although the filter is set, it depends on the scanner.scan method.

scan method of ClassPathMapperScanner

public int scan(String... basePackages) {
   int beanCountAtScanStart = this.registry.getBeanDefinitionCount();
   doScan(basePackages);
   // Register annotation configuration processor
   if (this.includeAnnotationConfig) {
      AnnotationConfigUtils
                  .registerAnnotationConfigProcessors(this.registry);
   }
   return (this.registry.getBeanDefinitionCount() - beanCountAtScanStart);
}

The doScan method is as follows:

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;
}

The doScan method of ClassPathBeanDefinitionScanner, the parent class of ClassPathMapperScanner, is

All java files under the scan package are converted to beandefinition.

processBeanDefinitions is to convert the previous BeanDefinition to MapperFactoryBean's BeanDefinition.

How does the filter work (i.e., annotation class or marker interface)? I trace the source code all the way

Finally, the filter processing is found in isCandidateComponent of ClassPathScanningCandidateComponentProvider

protected boolean isCandidateComponent(MetadataReader metadataReader) throws IOException {
   for (TypeFilter tf : this.excludeFilters) {
      if (tf.match(metadataReader, this.metadataReaderFactory)) {
         return false;
      }
   }
   for (TypeFilter tf : this.includeFilters) {
      if (tf.match(metadataReader, this.metadataReaderFactory)) {
         return isConditionMatch(metadataReader);
      }
   }
   return false;
}

Summarize the role of MapperScannerConfigurer

MapperScannerConfigurer implements the postProcessBeanDefinitionRegistry method of beanDefinitionRegistryPostProcessor

Recursively searches interfaces from the directory of the specified basePackage and registers them as MapperFactoryBean

SqlSessionFactoryBean

Class annotation

  1. Create the SqiSessionFactory of Mybatis for sharing in the Spring context.

  2. SqiSessionFactory can be injected into daos with mybatis through dependency.

  3. Datasourcetransaction manager, jtransaction manager and sqlsessionfactory want to combine to implement transactions.

Key methods of class diagram searching

SqlSessionFactoryBean implements the ApplicationListener, InitializingBean and FactoryBean interfaces. The descriptions of each interface are as follows:

  • ApplicationListener is used to listen for Spring events
  • The InitializingBean interface only includes the afterpropertieset method, which will execute when initializing the bean
  • FactoryBean: the returned object is not an instance of the specified class, but the object returned by the getObject method of the FactoryBean

You should focus on the methods of afterpropertieset and getObject.

Key method analysis

Afterpropertieset method

public void afterPropertiesSet() throws Exception {
  notNull(dataSource, "Property 'dataSource' is required");
  notNull(sqlSessionFactoryBuilder, 
              "Property 'sqlSessionFactoryBuilder' is required");
  state((configuration == null && configLocation == null) 
          || !(configuration != null && configLocation != null),
  "Property 'configuration' and 'configLocation' can not specified with together");
  this.sqlSessionFactory = buildSqlSessionFactory();
}

buildSqlSessionFactory, you can see from the method name that the SqlSessionFactory has been created here. The specific source code is not covered in detail.

getObject method

public SqlSessionFactory getObject() throws Exception {
  if (this.sqlSessionFactory == null) {
    afterPropertiesSet();
  }
  return this.sqlSessionFactory;
}

Summarize SqlSessionFactoryBean

The afterpropertieset of InitializingBean is implemented, in which SqlSessionFactory of Mybatis is created

The getObject of FactoryBean returns the created sqlSessionFactory.

Doubt

After reading this SqlSessionFactoryBean and MapperScannerConfigurer, I wonder if you have any questions! Mybatis is generally used in Spring in the following ways:

ApplicationContext context=new AnnotationConfigApplicationContext();
UsrMapper  usrMapper=context.getBean("usrMapper");
//What is actually called is
sqlSession.getMapper(UsrMapper.class);

SqlSessionFactoryBean creates the SqlSessionFactory of Mybatis. MapperScannerConfigurer converts the interface to MapperFactoryBean. Where can I call sqlSession.getMapper(UsrMapper.class)???

Mapper factorybean is the answer to all this

MapperFactoryBean description

Class annotation

BeanFactory that can inject the MyBatis mapping interface. It can set SqlSessionFactory or preconfigured SqlSessionTemplate.
Note that this factory only injects interfaces but not implementation classes

Key methods of class diagram searching

Look at the class diagram and see InitializingBean and FactoryBean again!!!

  • The InitializingBean interface only includes the afterpropertieset method, which will execute when initializing the bean
  • FactoryBean: the returned object is not an instance of the specified class, but the object returned by the getObject method of the FactoryBean

Focus on the implementation of afterpropertieset and getObject again!

Key method analysis

The implementation of afterpropertieset in the DaoSupport class is as follows:

public final void afterPropertiesSet()
     throws IllegalArgumentException, BeanInitializationException {
    this.checkDaoConfig();
    try {
        this.initDao();
    } catch (Exception var2) {
        throw
         new BeanInitializationException(
                             "Initialization of DAO failed",  var2);
    }
}

initDao is an empty implementation. checkDaoConfig is implemented in MapperFactoryBean as follows:

protected void checkDaoConfig() {
  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();
    }
  }
}

The key statement is configuration. Addmapper (this. Mappinterface), which adds the interface to the configuration of Mybatis.

The getObject method is super simple. It calls sqlSession.getMapper(UsrMapper.class);

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

Summary MapperFactoryBean

The afterpropertieset method of InitializingBean is implemented, in which the mapper interface is set to the configuration of mybatis.

Implement the getObject method of FactoryBean, call sqlSession.getMapper, and return the mapper object.

summary

Spring integrates three core classes of Mybatis:

MapperScannerConfigurer

The postProcessBeanDefinitionRegistry method of beanDefinitionRegistryPostProcessor is implemented, in which interfaces are recursively searched from the directory of the specified basePackage and registered as BeanDefinition of MapperFactoryBean type

SqlSessionFactoryBean

The afterpropertieset of InitializingBean is implemented, in which SqlSessionFactory of Mybatis is created.

The getObject of FactoryBean returns the created sqlSessionFactory.

MapperFactoryBean

The afterpropertieset method of InitializingBean is implemented, and the mapper interface is set to the configuration of mybatis.

Implement the getObject method of FactoryBean, call sqlSession.getMapper, and return the mapper object.

Posted by wilded1 on Sun, 03 Nov 2019 03:50:38 -0800