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
-
Find all registered beans of type PropertyResourceConfigurer
-
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
-
Create the SqiSessionFactory of Mybatis for sharing in the Spring context.
-
SqiSessionFactory can be injected into daos with mybatis through dependency.
-
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.