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
BeanNameAware
When 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 implementationApplicationContextAware
When subclasses are created, Spring willApplicationContext
Object is automatically injected into the current bean.InitializingBean
:InitializingBean
The interface provides the bean with a post-initialization method for the property, which only includesafterPropertiesSet
Method, which any class that inherits this interface executes after the bean’s properties are initialized.BeanDefinitionRegistryPostProcessor
: the standardBeanFactoryPostProcessor
SPI extension to allow in generalBeanFactoryPostProcessor
Register more bean definitions before the inspection begins. Usually when we want to customizeBeanDefinition
Used.MapperScannerConfigurer
The main implementation is to scan specific packages and create Mapper objects and hand them over to Spring for management.BeanFactoryPostProcessor
:BeanFactory
The rear processor,BeanFactoryPostProcessor
After the bean definition file is loaded by the Spring container and before the bean is instantiated, it can be used in thepostProcessBeanFactory()
As neededBeanDefinition
Make 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 here
sqlSessionFactory
andsqlSessionTemplateBeanName
The subsequent Mapper generation will naturally be managed by them - ClassPathMapperScanner was called
scan()
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
mapperInterface
It’s just the Mapper interface that we just scanned- If not already registered, call
configuration.addMapper()
Add it inconfiguration
In 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,
Configuration
Is the global configuration object of Mybatis. Contains various configurations for XML or annotation parsing. knownMappers
Put isMapper
andMapperProxyFactory
Indicates 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
- for
Mapper
createMapperProxyFactory
Object 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 type
knownMappers
Get the correspondingMapperProxyFactory
object - NewInstance (sqlSession) Creates a MapperProxy object to observe
getMapper
The stack call is not hard to find, and is called back when scanning Mapper injectionMapperFactoryBean
thegetObject()
Method to generate a proxy object for Mapper.MapperProxy
This ends the creation of the. MapperProxy associates SQL statements in XMl files or annotations, which we’ll discuss later.