1, based on Java Config configuration
@Configuration
@MapperScan(basePackages = "org.wfs.mybatis.**.mapper",sqlSessionTemplateRef = "sqlSessionTemplate")
public class MybatisConfig {
@Bean
public SqlSessionFactoryBean sqlSessionFactory(DataSource dataSource) throws Exception {
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
bean.setDataSources(dataSource);
PathMatchingResourcePatternResolver patternResolver = new PathMatchingResourcePatternResolver();
bean.setMapperLocations(patternResolver.getResource("classpath:org/wfs/mybatis/**/mapper/*.*.xml"));
return bean;
}
@Bean
public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
return newSqlSessionTemplate(sqlSessionFactory); }}Copy the code
The DataSource of the DataSource selects itself
Mapper’s XML is stored in the package, so you need to configure the POM file to package it into the JAR:
<build>
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.xml</include>
</includes>
</resource>
</resources>
</build>
Copy the code
Of course, you can also place the Mapper file under the Resources file
2. Implementation principle of @Mapperscan
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(MapperScannerRegistrar.class)
@Repeatable(MapperScans.class)
public @interface MapperScan {
...
}
Copy the code
The spring used above can see @ Import (MapperScannerRegistrar. Class) annotations to implementation
ImportBeanDefinitionRegistrar is an interface, the realization of the interface function to parse the spring bean Configuration phase, when parsing @ the Configuration notes, Can pass ImportBeanDefinitionRegistrar interface implementation class, to add additional BeanDefinition BeanDefinitionRegistry container, need to use with @ Import
2.1, ImportBeanDefinitionRegistrar interface
You can register some custom beans based on the annotation data on the @Configuration annotation class; BeanDefinitionRegistryPostProcessor due to the limitation of life cycle is best not to register here
The previous version is registered here MapperScannerConfigurer implements BeanDefinitionRegistryPostProcessor this class, Using BeanDefinitionRegistryPostProcessorpost. ProcessBeanDefinitionRegistry to scan the registration @ Mapper interfaces
public interface ImportBeanDefinitionRegistrar {
void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry);
}
Copy the code
2.2 implementation of registerBeanDefinitions method
The first step is to get the @MapperScanner annotation data and then invoke the specific registration method
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
AnnotationAttributes mapperScanAttrs = AnnotationAttributes
.fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
if(mapperScanAttrs ! =null) { registerBeanDefinitions(mapperScanAttrs, registry); }}Copy the code
The registration process is simple:
-
Create ClassPathMapperScanner to scan configured packages and register the @mapper annotated interface with the Spring container
-
Process the annotation data and set it to ClassPathMapperScanner
-
ClassPathMapperScanner doScan method, scanning and register @ Mapper interfaces
void registerBeanDefinitions(AnnotationAttributes annoAttrs, BeanDefinitionRegistry registry) {
ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
// this check is needed in Spring 3.1
Optional.ofNullable(resourceLoader).ifPresent(scanner::setResourceLoader);
Class<? extends Annotation> annotationClass = annoAttrs.getClass("annotationClass");
if(! Annotation.class.equals(annotationClass)) { scanner.setAnnotationClass(annotationClass); } Class<? > markerInterface = annoAttrs.getClass("markerInterface");
if(! Class.class.equals(markerInterface)) { scanner.setMarkerInterface(markerInterface); } Class<? extends BeanNameGenerator> generatorClass = annoAttrs.getClass("nameGenerator");
if(! BeanNameGenerator.class.equals(generatorClass)) { scanner.setBeanNameGenerator(BeanUtils.instantiateClass(generatorClass)); } Class<? extends MapperFactoryBean> mapperFactoryBeanClass = annoAttrs.getClass("factoryBean");
if(! MapperFactoryBean.class.equals(mapperFactoryBeanClass)) { scanner.setMapperFactoryBeanClass(mapperFactoryBeanClass); } scanner.setSqlSessionTemplateBeanName(annoAttrs.getString("sqlSessionTemplateRef"));
scanner.setSqlSessionFactoryBeanName(annoAttrs.getString("sqlSessionFactoryRef"));
List<String> basePackages = new ArrayList<>();
basePackages.addAll(
Arrays.stream(annoAttrs.getStringArray("value"))
.filter(StringUtils::hasText)
.collect(Collectors.toList()));
basePackages.addAll(
Arrays.stream(annoAttrs.getStringArray("basePackages"))
.filter(StringUtils::hasText)
.collect(Collectors.toList()));
basePackages.addAll(
Arrays.stream(annoAttrs.getClassArray("basePackageClasses"))
.map(ClassUtils::getPackageName)
.collect(Collectors.toList()));
scanner.registerFilters();
scanner.doScan(StringUtils.toStringArray(basePackages));
}
Copy the code
2.3. How to scan packages and register them
ClassPathMapperScanner inheritance ClassPathBeanDefinitionScanner
See scanner.registerfilters () above; This is mainly registration filtering, scanning only the classes we need, we don’t need to study this, we will scan all classes, excluding package-info.java
2.3.1 Specific scanning method
Override the method of the parent class. In fact, the scan registry still uses the method of the parent class, but you need to customize the bean assignment
@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
Let’s define the assignment method for the Bean:
-
Walk through the bean definition
-
Set the constructor’s input parameter to the class of the interface
-
Setting beanClass MapperFactoryBean, using MapperFactoryBean. GetObject to create a proxy class
-
AddToConfig is set to true by default. This configuration will load Mapper files under the same interface package into Mybatis without setting the scan address of Mapper files
-
Set sqlSessionTemplate and sqlSessionFactory
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
definition.setBeanClass(this.mapperFactoryBeanClass);
definition.getPropertyValues().add("addToConfig".this.addToConfig);
boolean explicitFactoryUsed = false;
if (StringUtils.hasText(this.sqlSessionFactoryBeanName)) {
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.");
}
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); }}}Copy the code
3. MapperFactoryBean creates the proxy
Create and generate beans to inject into the Spring container by implementing the FactoryBean.getobject method.
Create a Mapper interface proxy by putting SqlSession into the proxy object.
So we need to look at the SqlSessionTemplate, because Spring puts the SqlSessionTemplate singleton in;
SqlSession is not a thread-safe object. How does Spring make singleton thread-safe?
It holds the agent property of a SqlSession internally
public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) {
notNull(sqlSessionFactory, "Property 'sqlSessionFactory' is required");
notNull(executorType, "Property 'executorType' is required");
this.sqlSessionFactory = sqlSessionFactory;
this.executorType = executorType;
this.exceptionTranslator = exceptionTranslator;
this.sqlSessionProxy = (SqlSession) newProxyInstance(
SqlSessionFactory.class.getClassLoader(),
new Class[] { SqlSession.class },
new SqlSessionInterceptor());
}
Copy the code
All database operations are called through the sqlSessionProxy;
Let’s look at the agent’s implementation class, which is the private inner class of the SqlSessionTemplate:
-
A thread-safe SqlSession will be obtained, essentially hosted using ThreadLocal
-
Reflection calls the SqlSession object method
private class SqlSessionInterceptor implements InvocationHandler {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
SqlSession sqlSession = getSqlSession(
SqlSessionTemplate.this.sqlSessionFactory,
SqlSessionTemplate.this.executorType,
SqlSessionTemplate.this.exceptionTranslator);
try {
Object result = method.invoke(sqlSession, args);
if(! isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
// force commit even on non-dirty sessions because some databases require
// a commit/rollback before calling close()
sqlSession.commit(true);
}
return result;
} catch (Throwable t) {
Throwable unwrapped = unwrapThrowable(t);
if (SqlSessionTemplate.this.exceptionTranslator ! =null && unwrapped instanceof PersistenceException) {
// release the connection to avoid a deadlock if the translator is no loaded. See issue #22
closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
sqlSession = null;
Throwable translated = SqlSessionTemplate.this.exceptionTranslator.translateExceptionIfPossible((PersistenceException) unwrapped);
if(translated ! =null) { unwrapped = translated; }}throw unwrapped;
} finally {
if(sqlSession ! =null) {
closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory); }}}}Copy the code